Repository navigation
Migrate basic broker infrastructure to new testsuite #51859
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
Merged
+824
−0
Merged
Changes from all commits
Commits
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
89 changes: 89 additions & 0 deletions
89
tests/base/src/test/java/org/keycloak/tests/broker/AbstractBrokerLoginTest.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,89 @@ | ||
| package org.keycloak.tests.broker; | ||
|
|
||
| import org.keycloak.representations.idm.UserRepresentation; | ||
| import org.keycloak.testframework.realm.ManagedRealm; | ||
| import org.keycloak.testsuite.util.AccountHelper; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** | ||
| * Protocol-agnostic broker login scenarios. Concrete OIDC/SAML tests get these {@code @Test} methods by | ||
| * extending a protocol-specific subclass that supplies the realms and broker configuration. | ||
| */ | ||
| public abstract class AbstractBrokerLoginTest extends AbstractBrokerTest { | ||
|
|
||
| @Test | ||
| public void testLogInAsUserInIDP() { | ||
| loginThroughIdpAndLogout(); | ||
| } | ||
|
|
||
| // Shared brokered login + single-logout round-trip, used both as its own test and as the setup step for | ||
| // testLoginWithExistingUser(), so neither test invokes the other's @Test method as a subroutine. | ||
| protected void loginThroughIdpAndLogout() { | ||
| loginUser(); | ||
| singleLogout(); | ||
| } | ||
|
sguilhen marked this conversation as resolved.
Resolved
|
||
|
|
||
| protected void loginUser() { | ||
| logInAsUserInIDP(); | ||
|
|
||
| updateAccountInformation(); | ||
|
|
||
| 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"); | ||
| } | ||
|
|
||
| protected void singleLogout() { | ||
| 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.assertCurrent(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testLoginWithExistingUser() { | ||
| ManagedRealm consumerRealm = getConsumerRealm(); | ||
|
|
||
| int userCountBefore = consumerRealm.admin().users().count(); | ||
|
|
||
| loginThroughIdpAndLogout(); | ||
|
|
||
| int userCount = consumerRealm.admin().users().count(); | ||
| Assertions.assertEquals(userCountBefore + 1, userCount, | ||
| "First broker login should have created exactly one user in the consumer realm"); | ||
|
sguilhen marked this conversation as resolved.
Resolved
sguilhen marked this conversation as resolved.
Resolved
|
||
|
|
||
| oauth.openLoginForm(); | ||
|
|
||
| logInWithBroker(); | ||
|
|
||
| loginPage.fillLogin(getUserLogin(), getUserPassword()); | ||
| loginPage.submit(); | ||
|
|
||
| Assertions.assertTrue(oauth.parseLoginResponse().isSuccess()); | ||
| Assertions.assertEquals(userCount, consumerRealm.admin().users().count()); | ||
| } | ||
| } | ||
162 changes: 162 additions & 0 deletions
162
tests/base/src/test/java/org/keycloak/tests/broker/AbstractBrokerTest.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,162 @@ | ||
| package org.keycloak.tests.broker; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| import org.keycloak.models.UserModel; | ||
| import org.keycloak.representations.idm.FederatedIdentityRepresentation; | ||
| import org.keycloak.representations.idm.RequiredActionProviderRepresentation; | ||
| import org.keycloak.representations.idm.UserRepresentation; | ||
| import org.keycloak.testframework.oauth.OAuthClient; | ||
| import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; | ||
| import org.keycloak.testframework.realm.ManagedRealm; | ||
| import org.keycloak.testframework.ui.annotations.InjectPage; | ||
| import org.keycloak.testframework.ui.annotations.InjectWebDriver; | ||
| import org.keycloak.testframework.ui.page.IdpReviewUserProfilePage; | ||
|
sguilhen marked this conversation as resolved.
Resolved
|
||
| import org.keycloak.testframework.ui.page.LoginPage; | ||
| import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; | ||
| import org.keycloak.testsuite.util.AccountHelper; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.openqa.selenium.TimeoutException; | ||
|
|
||
| /** | ||
| * Protocol-agnostic base for the broker tests. Holds the injected clients/pages that are identical across | ||
| * every broker test and the shared login/first-broker-login helpers. The realms differ per test (each | ||
| * concrete test injects its own provider/consumer realm with a protocol-specific config), so they are left | ||
| * as abstract accessors; everything else is inherited directly as protected fields. | ||
| */ | ||
| public abstract class AbstractBrokerTest { | ||
|
|
||
| protected static final String PROVIDER_REALM = "provider"; | ||
| protected static final String CONSUMER_REALM = "consumer"; | ||
| protected static final String USER_LOGIN = "testuser"; | ||
| protected static final String USER_EMAIL = "user@localhost.com"; | ||
| protected static final String USER_PASSWORD = "password"; | ||
| protected static final String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app"; | ||
| protected static final String CONSUMER_BROKER_APP_SECRET = "broker-app-secret"; | ||
|
|
||
| // Drive the login flows against the migrated broker-app client (as the legacy suite did) rather than the | ||
| // framework default test-app; the supplier creates it in the consumer realm from BrokerAppClientConfig. | ||
| @InjectOAuthClient(realmRef = "consumer", config = BrokerAppClientConfig.class) | ||
| protected OAuthClient oauth; | ||
|
|
||
| @InjectWebDriver | ||
| protected ManagedWebDriver webDriver; | ||
|
|
||
| @InjectPage | ||
| protected LoginPage loginPage; | ||
|
|
||
| @InjectPage | ||
| protected IdpReviewUserProfilePage updateProfilePage; | ||
|
|
||
| protected abstract ManagedRealm getProviderRealm(); | ||
|
|
||
| protected abstract ManagedRealm getConsumerRealm(); | ||
|
sguilhen marked this conversation as resolved.
Resolved
|
||
|
|
||
| protected abstract 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 | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| protected String getUserLogin() { | ||
| return USER_LOGIN; | ||
| } | ||
|
|
||
| protected String getUserPassword() { | ||
| return USER_PASSWORD; | ||
| } | ||
|
|
||
| protected String getUserEmail() { | ||
| return USER_EMAIL; | ||
| } | ||
|
|
||
| protected void logInWithBroker() { | ||
| loginPage.clickSocial(getIdpAlias()); | ||
| } | ||
|
|
||
| protected void logInWithIdp() { | ||
| logInWithBroker(); | ||
| } | ||
|
|
||
| protected void logInAsUserInIDP() { | ||
| oauth.openLoginForm(); | ||
| logInWithBroker(); | ||
| logInAsUserInIDPForFirstTime(); | ||
| } | ||
|
|
||
| protected void logInAsUserInIDPForFirstTime() { | ||
| 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. | ||
| protected void updateAccountInformation() { | ||
| Assertions.assertTrue(profilePageAppeared(), | ||
| "The first-broker-login review-profile page was expected but did not appear"); | ||
| updateProfilePage.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. | ||
| protected void updateAccountInformationIfPresent() { | ||
| if (profilePageAppeared()) { | ||
| updateProfilePage.update("Firstname", "Lastname"); | ||
| } | ||
| } | ||
|
|
||
| private boolean profilePageAppeared() { | ||
| Set<String> profilePageIds = Set.of("login-login-update-profile", "login-idp-review-user-profile"); | ||
|
|
||
| try { | ||
| webDriver.waiting().until(d -> { | ||
| String currentPageId = webDriver.page().getCurrentPageId(); | ||
| if (currentPageId != null && profilePageIds.contains(currentPageId)) { | ||
| return true; | ||
| } | ||
| return null; | ||
| }); | ||
| return true; | ||
| } catch (TimeoutException e) { | ||
| return false; | ||
| } | ||
|
sguilhen marked this conversation as resolved.
Resolved
|
||
| } | ||
|
sguilhen marked this conversation as resolved.
Resolved
|
||
|
|
||
| protected 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 singleLogout() to verify login state after logout. | ||
| protected void logoutFromConsumerRealm() { | ||
| AccountHelper.logout(getConsumerRealm().admin(), getUserLogin()); | ||
| } | ||
|
|
||
| protected 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()); | ||
| } | ||
| } | ||
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.