Repository navigation
Migrate OIDC/SAML broker config-variant and infrastructure-dependent tests to the new testsuite #51949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+4,828
−2,875
Closed
Migrate OIDC/SAML broker config-variant and infrastructure-dependent tests to the new testsuite #51949
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
7167796
Migrate basic broker login tests to new testsuite
sguilhen ef85a89
Move OIDC config-variant and parameter-forwarding tests to new testsu…
sguilhen 8f7d18e
Migrate OIDC broker config-variant and parameter-forwarding tests
sguilhen 68f970c
Move client-secret-jwt, private-key-jwt, and sub-mismatch tests to ne…
sguilhen b616b01
Migrate client-secret-jwt, private-key-jwt, and sub-mismatch broker t…
sguilhen ff0e86b
Move IDP hint and nonce parameter tests to new testsuite oidc package
sguilhen 7d15211
Migrate IDP hint and nonce parameter broker tests
sguilhen 4e2b00d
Move JWE and ECDH-ES broker tests to new testsuite oidc package
sguilhen 1ed23c3
Migrate JWE and ECDH-ES broker tests to new testsuite
sguilhen 3ff93df
Move custom broker providers to tests/custom-providers
sguilhen c4eea53
Port custom broker providers to tests/custom-providers and remove leg…
sguilhen 86b5ae0
Move KcOidcBrokerIdpPublicKeyMissingUseTest to new testsuite oidc pac…
sguilhen 1374566
Migrate KcOidcBrokerIdpPublicKeyMissingUseTest to new testsuite
sguilhen 459fffa
Move private-key-jwt custom provider tests to new testsuite oidc package
sguilhen 2922b0e
Migrate private-key-jwt custom provider broker tests to new testsuite
sguilhen 63dadac
Move KcOidcBrokerVaultTest to new testsuite oidc package
sguilhen 69e2a38
Migrate KcOidcBrokerVaultTest to new testsuite
sguilhen aef26cd
Move KcOidcBrokerPassMaxAgeTest to new testsuite oidc package
sguilhen 98d2b6a
Migrate KcOidcBrokerPassMaxAgeTest to new testsuite
sguilhen 56e5e18
Move SAML broker tests to new testsuite saml package
sguilhen 30f8dea
Migrate KcSamlSignedDocumentOnlyBrokerTest to new testsuite
sguilhen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
206 changes: 206 additions & 0 deletions
206
tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfigSupport.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| package org.keycloak.tests.broker; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; | ||
| import org.keycloak.models.AuthenticationExecutionModel; | ||
| import org.keycloak.models.UserModel; | ||
| import org.keycloak.models.utils.DefaultAuthenticationFlows; | ||
| import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; | ||
| import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; | ||
| import org.keycloak.representations.idm.FederatedIdentityRepresentation; | ||
| import org.keycloak.representations.idm.IdentityProviderRepresentation; | ||
| import org.keycloak.representations.idm.RequiredActionProviderRepresentation; | ||
| import org.keycloak.representations.idm.UserRepresentation; | ||
| import org.keycloak.testframework.oauth.OAuthClient; | ||
| import org.keycloak.testframework.realm.ManagedRealm; | ||
| import org.keycloak.testframework.ui.page.IdpReviewUserProfilePage; | ||
| import org.keycloak.testframework.ui.page.LoginPage; | ||
| import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; | ||
| import org.keycloak.testsuite.util.AccountHelper; | ||
| import org.keycloak.testsuite.util.userprofile.UserProfileUtil; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.openqa.selenium.TimeoutException; | ||
|
|
||
| public interface BrokerConfigSupport { | ||
|
|
||
| String PROVIDER_REALM = "provider"; | ||
| String CONSUMER_REALM = "consumer"; | ||
| String USER_LOGIN = "testuser"; | ||
| String USER_EMAIL = "user@localhost.com"; | ||
| String USER_PASSWORD = "password"; | ||
| String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app"; | ||
| String CONSUMER_BROKER_APP_SECRET = "broker-app-secret"; | ||
|
|
||
| ManagedRealm getProviderRealm(); | ||
| ManagedRealm getConsumerRealm(); | ||
| OAuthClient getOAuthClient(); | ||
| ManagedWebDriver getWebDriver(); | ||
| LoginPage getLoginPage(); | ||
| IdpReviewUserProfilePage getUpdateProfilePage(); | ||
|
|
||
| String getIdpAlias(); | ||
|
|
||
| // The provider user is created without a first/last name so the consumer's first-broker-login | ||
| // review-profile page appears. The new-testsuite provider realm enables the VERIFY_PROFILE required | ||
| // action by default, which would otherwise stop the incomplete user at the provider; disable it so | ||
| // the incomplete profile reaches the consumer (the legacy suite did not enable VERIFY_PROFILE). | ||
| @BeforeEach | ||
| default void relaxProviderProfileVerification() { | ||
| for (RequiredActionProviderRepresentation action : getProviderRealm().admin().flows().getRequiredActions()) { | ||
| if (UserModel.RequiredAction.VERIFY_PROFILE.name().equals(action.getAlias())) { | ||
| action.setEnabled(false); | ||
| getProviderRealm().admin().flows().updateRequiredAction(action.getAlias(), action); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @BeforeEach | ||
| default void enableUnmanagedAttributes() { | ||
| UserProfileUtil.enableUnmanagedAttributes(getConsumerRealm().admin().users().userProfile()); | ||
| UserProfileUtil.enableUnmanagedAttributes(getProviderRealm().admin().users().userProfile()); | ||
| } | ||
|
|
||
| default String getUserLogin() { | ||
| return USER_LOGIN; | ||
| } | ||
|
|
||
| default String getUserPassword() { | ||
| return USER_PASSWORD; | ||
| } | ||
|
|
||
| default String getUserEmail() { | ||
| return USER_EMAIL; | ||
| } | ||
|
|
||
| default void logInWithBroker() { | ||
| LoginPage loginPage = getLoginPage(); | ||
| loginPage.clickSocial(getIdpAlias()); | ||
| } | ||
|
|
||
| default void logInWithIdp() { | ||
| logInWithBroker(); | ||
| } | ||
|
|
||
| default void logInAsUserInIDP() { | ||
| OAuthClient oauth = getOAuthClient(); | ||
| oauth.openLoginForm(); | ||
| logInWithBroker(); | ||
| logInAsUserInIDPForFirstTime(); | ||
| } | ||
|
|
||
| default void logInAsUserInIDPForFirstTime() { | ||
| LoginPage loginPage = getLoginPage(); | ||
| loginPage.fillLogin(getUserLogin(), getUserPassword()); | ||
| loginPage.submit(); | ||
| } | ||
|
|
||
| // Drives the consumer's first-broker-login review-profile page, which must appear the first time a | ||
| // user logs in through a broker because the imported user is missing its first and last name. Fails | ||
| // the test if the page does not show, mirroring the legacy assertCurrent() on the update-profile page. | ||
| default void updateAccountInformation() { | ||
| Assertions.assertTrue(profilePageAppeared(), | ||
| "The first-broker-login review-profile page was expected but did not appear"); | ||
| getUpdateProfilePage().update("Firstname", "Lastname"); | ||
| } | ||
|
|
||
| // Tolerant variant for flows that intentionally skip the review-profile page (e.g. when | ||
| // update-profile-on-first-login is turned off). Fills the page only if it actually appears. | ||
| default void updateAccountInformationIfPresent() { | ||
| if (profilePageAppeared()) { | ||
| getUpdateProfilePage().update("Firstname", "Lastname"); | ||
| } | ||
| } | ||
|
|
||
| private boolean profilePageAppeared() { | ||
| Set<String> profilePageIds = Set.of("login-login-update-profile", "login-idp-review-user-profile"); | ||
| ManagedWebDriver driver = getWebDriver(); | ||
| try { | ||
| driver.waiting().until(d -> { | ||
| String currentPageId = driver.page().getCurrentPageId(); | ||
| if (currentPageId != null && profilePageIds.contains(currentPageId)) { | ||
| return true; | ||
| } | ||
| return null; | ||
| }); | ||
| return true; | ||
| } catch (TimeoutException e) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| default UserRepresentation createUser(String username, String email) { | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
| UserRepresentation user = new UserRepresentation(); | ||
| user.setUsername(username); | ||
| user.setEmail(email); | ||
| user.setEmailVerified(true); | ||
| user.setEnabled(true); | ||
| consumerRealm.admin().users().create(user).close(); | ||
| return user; | ||
| } | ||
|
|
||
| // Mid-test helper used by testSingleLogout() to verify login state after logout. | ||
| default void logoutFromConsumerRealm() { | ||
| AccountHelper.logout(getConsumerRealm().admin(), getUserLogin()); | ||
| } | ||
|
|
||
| default void assertUserCreatedInConsumerRealm() { | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
| UserRepresentation userRep = AccountHelper.getUserRepresentation( | ||
| consumerRealm.admin(), getUserLogin()); | ||
| Assertions.assertNotNull(userRep, "There must be user " + getUserLogin() + " in consumer realm"); | ||
| // The review-profile page filled in these names via the UI; assert they were actually persisted | ||
| // rather than overwriting them here, so a broken UI flow is caught instead of masked. | ||
| Assertions.assertEquals("Firstname", userRep.getFirstName(), | ||
| "First name should have been persisted by the review-profile page"); | ||
| Assertions.assertEquals("Lastname", userRep.getLastName(), | ||
| "Last name should have been persisted by the review-profile page"); | ||
|
|
||
| int userCount = consumerRealm.admin().users().count(); | ||
| Assertions.assertTrue(userCount > 0, "There must be at least one user"); | ||
|
|
||
| // userRep was fetched above via an exact username search, so assert its email directly instead of | ||
| // scanning the (paginated) users().list(), which could miss the user beyond the first page. | ||
| Assertions.assertEquals(getUserEmail(), userRep.getEmail(), | ||
| "There must be user " + getUserLogin() + " with the expected email in consumer realm"); | ||
| } | ||
|
|
||
| default void disableUpdateProfileOnFirstLogin() { | ||
| var flows = getConsumerRealm().admin().flows(); | ||
| for (AuthenticationExecutionInfoRepresentation execution : | ||
| flows.getExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW)) { | ||
| if (IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID.equals(execution.getProviderId())) { | ||
| execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name()); | ||
| flows.updateExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, execution); | ||
| } else if (execution.getAlias() != null | ||
| && execution.getAlias().equals(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS)) { | ||
| AuthenticatorConfigRepresentation config = flows.getAuthenticatorConfig(execution.getAuthenticationConfig()); | ||
| config.getConfig().put("update.profile.on.first.login", IdentityProviderRepresentation.UPFLM_OFF); | ||
| flows.updateAuthenticatorConfig(config.getId(), config); | ||
| } | ||
| } | ||
| // Turning off the review-profile step is not enough: the new-testsuite consumer realm enables the | ||
| // VERIFY_PROFILE required action by default, which would still intercept the incomplete imported user | ||
| // (the provider user has no first/last name on purpose). Disable it so login reaches the callback, | ||
| // mirroring the legacy suite, which did not enable VERIFY_PROFILE at all. | ||
| for (RequiredActionProviderRepresentation action : getConsumerRealm().admin().flows().getRequiredActions()) { | ||
| if (UserModel.RequiredAction.VERIFY_PROFILE.name().equals(action.getAlias())) { | ||
| action.setEnabled(false); | ||
| getConsumerRealm().admin().flows().updateRequiredAction(action.getAlias(), action); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| default void assertNumFederatedIdentities(String username, int expected) { | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
| List<UserRepresentation> users = consumerRealm.admin().users().search(username, true); | ||
| Assertions.assertEquals(1, users.size(), "Expected exactly one user with username " + username); | ||
| List<FederatedIdentityRepresentation> fedIdentities = | ||
| consumerRealm.admin().users().get(users.get(0).getId()).getFederatedIdentity(); | ||
| Assertions.assertEquals(expected, fedIdentities.size()); | ||
| } | ||
| } |
65 changes: 65 additions & 0 deletions
65
tests/base/src/test/java/org/keycloak/tests/broker/BrokerLoginTest.java
10BC8
View file
Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package org.keycloak.tests.broker; | ||
|
|
||
| import org.keycloak.testframework.oauth.OAuthClient; | ||
| import org.keycloak.testframework.realm.ManagedRealm; | ||
| import org.keycloak.testframework.ui.page.LoginPage; | ||
| import org.keycloak.testsuite.util.AccountHelper; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public interface BrokerLoginTest extends BrokerConfigSupport { | ||
|
|
||
| @Test | ||
| default void testLogInAsUserInIDP() { | ||
| loginUser(); | ||
| testSingleLogout(); | ||
| } | ||
|
|
||
| default void loginUser() { | ||
| logInAsUserInIDP(); | ||
| updateAccountInformation(); | ||
| assertUserCreatedInConsumerRealm(); | ||
| } | ||
|
|
||
| default void testSingleLogout() { | ||
| OAuthClient oauth = getOAuthClient(); | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
| ManagedRealm providerRealm = getProviderRealm(); | ||
|
|
||
| oauth.openLoginForm(); | ||
| Assertions.assertTrue(oauth.parseLoginResponse().isSuccess(), "Should be logged in"); | ||
|
|
||
| AccountHelper.logout(consumerRealm.admin(), getUserLogin()); | ||
| AccountHelper.logout(providerRealm.admin(), getUserLogin()); | ||
|
|
||
| oauth.openLoginForm(); | ||
| LoginPage loginPage = getLoginPage(); | ||
| loginPage.assertCurrent(); | ||
| } | ||
|
|
||
| @Test | ||
| default void loginWithExistingUser() { | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
| OAuthClient oauth = getOAuthClient(); | ||
|
|
||
| int userCountBefore = consumerRealm.admin().users().count(); | ||
|
|
||
| testLogInAsUserInIDP(); | ||
|
|
||
| int userCount = consumerRealm.admin().users().count(); | ||
| Assertions.assertEquals(userCountBefore + 1, userCount, | ||
| "First broker login should have created exactly one user in the consumer realm"); | ||
|
|
||
| oauth.openLoginForm(); | ||
|
|
||
| logInWithBroker(); | ||
|
|
||
| LoginPage loginPage = getLoginPage(); | ||
| loginPage.fillLogin(getUserLogin(), getUserPassword()); | ||
| loginPage.submit(); | ||
|
|
||
| Assertions.assertTrue(oauth.parseLoginResponse().isSuccess()); | ||
| Assertions.assertEquals(userCount, consumerRealm.admin().users().count()); | ||
| } | ||
| } |
38 changes: 38 additions & 0 deletions
38
tests/base/src/test/java/org/keycloak/tests/broker/EcdhEsJweBrokerConfigSupport.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.keycloak.tests.broker; | ||
|
|
||
| import org.keycloak.common.util.MultivaluedHashMap; | ||
| import org.keycloak.keys.KeyProvider; | ||
| import org.keycloak.models.utils.DefaultKeyProviders; | ||
| import org.keycloak.representations.idm.ComponentRepresentation; | ||
|
|
||
| public interface EcdhEsJweBrokerConfigSupport extends JweBrokerConfigSupport { | ||
|
|
||
| String getCurve(); | ||
|
|
||
| @Override | ||
| default ComponentRepresentation getProviderKeyComponent() { | ||
| ComponentRepresentation component = new ComponentRepresentation(); | ||
| component.setName("ecdsa-generated"); | ||
| < 8B2C /td> | component.setProviderId("ecdsa-generated"); | |
| component.setProviderType(KeyProvider.class.getName()); | ||
| MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); | ||
| config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY); | ||
| config.putSingle("ecdsaEllipticCurveKey", getCurve()); | ||
| component.setConfig(config); | ||
| return component; | ||
| } | ||
|
|
||
| @Override | ||
| default ComponentRepresentation getConsumerKeyComponent() { | ||
| ComponentRepresentation component = new ComponentRepresentation(); | ||
| component.setName("ecdh-generated"); | ||
| component.setProviderId("ecdh-generated"); | ||
| component.setProviderType(KeyProvider.class.getName()); | ||
| MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); | ||
| config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY); | ||
| config.putSingle("ecdhAlgorithm", getEncAlg()); | ||
| config.putSingle("ecdhEllipticCurveKey", getCurve()); | ||
| component.setConfig(config); | ||
| return component; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.