From 3217780c2b1925a6482317baa16ea872bf2e9750 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 15 Sep 2026 16:33:55 +0200 Subject: [PATCH 1/3] Migrate KcOidcFirstBrokerLoginDetectExistingUserTest to tests/base Add BrokerConfiguration-based broker test infrastructure for first-broker-login scenarios that still need programmatic realm setup, and migrate the detect-existing- user OIDC broker tests as the first batch of the broker package migration for #50755. Supersedes the monolithic #51881 approach with a focused follow-up aligned to the incremental broker migrations already on main. Signed-off-by: Erik Jan de Wit --- .../testframework/ui/page/LoginPage.java | 10 + .../tests/broker/AbstractBaseBrokerTest.java | 197 ++++++++++++ .../AbstractInitializedBaseBrokerTest.java | 123 ++++++++ .../tests/broker/BrokerConfiguration.java | 76 +++++ .../broker/BrokerFirstLoginFlowMutators.java | 33 ++ .../tests/broker/BrokerServerConfig.java | 13 + .../tests/broker/BrokerTestConstants.java | 86 ++++++ .../tests/broker/BrokerTestTools.java | 132 ++++++++ .../broker/KcOidcBrokerConfiguration.java | 290 ++++++++++++++++++ ...irstBrokerLoginDetectExistingUserTest.java | 49 ++- 10 files changed, 982 insertions(+), 27 deletions(-) create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java (79%) diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java index 292a396020df..7e026ec824f4 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java @@ -54,6 +54,16 @@ public void fillPassword(String password) { passwordInput.sendKeys(password); } + public void login(String username, String password) { + fillLogin(username, password); + submit(); + } + + public void login(String password) { + fillPassword(password); + submit(); + } + public void submit() { submitButton.click(); } diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java new file mode 100644 index 000000000000..a288e7ee04fe --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java @@ -0,0 +1,197 @@ +package org.keycloak.tests.broker; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testframework.annotations.InjectAdminClient; +import org.keycloak.testframework.annotations.InjectKeycloakUrls; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.TestApp; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectTestApp; +import org.keycloak.testframework.realm.UserBuilder; +import org.keycloak.testframework.server.KeycloakUrls; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.annotations.InjectWebDriver; +import org.keycloak.testframework.ui.page.ErrorPage; +import org.keycloak.testframework.ui.page.LoginPage; +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; +import org.keycloak.testsuite.util.userprofile.UserProfileUtil; + +import org.jboss.logging.Logger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.openqa.selenium.By; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.support.ui.WebDriverWait; + +import static org.keycloak.tests.broker.BrokerTestConstants.USER_EMAIL; +import static org.keycloak.tests.broker.BrokerTestTools.waitForPage; +import static org.keycloak.tests.utils.admin.AdminApiUtil.createUserWithAdminClient; +import static org.keycloak.tests.utils.admin.AdminApiUtil.resetUserPassword; + +/** + * Base for broker tests that set up provider/consumer realms programmatically via {@link BrokerConfiguration}. + */ +public abstract class AbstractBaseBrokerTest { + + protected final Logger log = Logger.getLogger(getClass()); + + @InjectAdminClient + protected Keycloak adminClient; + + @InjectWebDriver + protected ManagedWebDriver driver; + + @InjectOAuthClient + protected OAuthClient oauth; + + @InjectTestApp + protected TestApp testApp; + + @InjectKeycloakUrls + protected KeycloakUrls keycloakUrls; + + @InjectPage + protected LoginPage loginPage; + + @InjectPage + protected ErrorPage errorPage; + + protected BrokerConfiguration bc = getBrokerConfiguration(); + + protected abstract BrokerConfiguration getBrokerConfiguration(); + + @BeforeEach + public void beforeBrokerTest() { + BrokerTestTools.setServerRoot(keycloakUrls.getBase()); + + RealmRepresentation consumerRealm = bc.createConsumerRealm(); + RealmRepresentation providerRealm = bc.createProviderRealm(); + importRealm(consumerRealm); + importRealm(providerRealm); + + UserProfileUtil.enableUnmanagedAttributes(adminClient.realm(consumerRealm.getRealm()).users().userProfile()); + UserProfileUtil.enableUnmanagedAttributes(adminClient.realm(providerRealm.getRealm()).users().userProfile()); + } + + @AfterEach + public void afterBrokerTest() { + driver.driver().manage().deleteAllCookies(); + removeRealm(bc.consumerRealmName()); + removeRealm(bc.providerRealmName()); + } + + protected void importRealm(RealmRepresentation realmRepresentation) { + removeRealm(realmRepresentation.getRealm()); + adminClient.realms().create(realmRepresentation); + } + + protected void removeRealm(String realmName) { + try { + adminClient.realm(realmName).remove(); + } catch (NotFoundException ignored) { + } + } + + protected void addClientsToProviderAndConsumer() { + RealmResource providerRealm = adminClient.realm(bc.providerRealmName()); + for (ClientRepresentation client : bc.createProviderClients()) { + Response response = providerRealm.clients().create(client); + response.close(); + } + + List consumerClients = bc.createConsumerClients(); + if (consumerClients != null) { + RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); + for (ClientRepresentation client : consumerClients) { + if (KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_CLIENT_ID.equals(client.getClientId())) { + List redirectUris = new ArrayList<>(client.getRedirectUris()); + redirectUris.add(testApp.getRedirectionUri()); + client.setRedirectUris(redirectUris); + } + Response response = consumerRealm.clients().create(client); + response.close(); + } + } + } + + public String createUser(String realm, String username, String password, String... requiredActions) { + UserRepresentation user = UserBuilder.create().username(username).enabled(true).build(); + user.setRequiredActions(Arrays.asList(requiredActions)); + String createdUserId = createUserWithAdminClient(adminClient.realm(realm), user); + resetUserPassword(adminClient.realm(realm).users().get(createdUserId), password, false); + return createdUserId; + } + + protected String createUser(String realm, String username, String password, String firstName, String lastName, String email) { + UserRepresentation newUser = UserBuilder.create() + .username(username) + .email(email) + .firstName(firstName) + .lastName(lastName) + .enabled(true) + .build(); + String createdUserId = createUserWithAdminClient(adminClient.realm(realm), newUser); + resetUserPassword(adminClient.realm(realm).users().get(createdUserId), password, false); + return createdUserId; + } + + protected String createUser(String username, String email) { + UserRepresentation newUser = UserBuilder.create().username(username).email(email).enabled(true).build(); + String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser); + resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false); + return userId; + } + + protected String createUser(String username) { + return createUser(username, USER_EMAIL); + } + + protected void openConsumerBrokerLoginForm() { + oauth.client(KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_CLIENT_ID, + KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET); + oauth.realm(bc.consumerRealmName()); + oauth.redirectUri(testApp.getRedirectionUri()); + oauth.openLoginForm(); + } + + protected void logInWithIdp(String idpAlias, String username, String password) { + waitForPage(driver, "sign in to", true); + String initialUrl = driver.getCurrentUrl(); + log.debug("Clicking social " + idpAlias); + loginPage.clickSocial(idpAlias); + try { + new WebDriverWait(driver.driver(), Duration.ofSeconds(10)).until(webDriver -> + !webDriver.getCurrentUrl().equals(initialUrl)); + + String brokerLoginSegment = "/broker/" + idpAlias + "/login"; + if (driver.getCurrentUrl().contains(brokerLoginSegment)) { + List forms = driver.driver().findElements(By.tagName("form")); + if (!forms.isEmpty()) { + forms.get(0).submit(); + } + new WebDriverWait(driver.driver(), Duration.ofSeconds(10)).until(webDriver -> + !webDriver.getCurrentUrl().contains(brokerLoginSegment)); + } + } catch (TimeoutException e) { + Assertions.fail("Timed out waiting for social redirect for " + idpAlias + ". URL: " + driver.getCurrentUrl()); + } + if (loginPage.isUsernameInputPresent()) { + loginPage.login(username, password); + } else if (loginPage.isPasswordInputPresent()) { + loginPage.login(password); + } + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java new file mode 100644 index 000000000000..416ef758c992 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java @@ -0,0 +1,123 @@ +package org.keycloak.tests.broker; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import jakarta.ws.rs.core.Response; + +import org.keycloak.admin.client.resource.AuthenticationManagementResource; +import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.admin.client.resource.IdentityProviderResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.protocol.oidc.OIDCConfigAttributes; +import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import org.junit.jupiter.api.BeforeEach; + +import static org.keycloak.broker.oidc.OAuth2IdentityProviderConfig.TOKEN_ENDPOINT_URL; +import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_ID; +import static org.keycloak.tests.broker.BrokerTestConstants.IDP_SAML_ALIAS; +import static org.keycloak.tests.utils.admin.AdminApiUtil.createUserWithAdminClient; +import static org.keycloak.tests.utils.admin.AdminApiUtil.resetUserPassword; + +public abstract class AbstractInitializedBaseBrokerTest extends AbstractBaseBrokerTest { + + protected IdentityProviderResource identityProviderResource; + protected String userId; + + protected void postInitializeUser(UserRepresentation user) { + } + + @Override + @BeforeEach + public void beforeBrokerTest() { + super.beforeBrokerTest(); + log.debug("creating user for realm " + bc.providerRealmName()); + + UserRepresentation user = new UserRepresentation(); + user.setUsername(bc.getUserLogin()); + user.setEmail(bc.getUserEmail()); + if (IDP_SAML_ALIAS.equals(bc.getIDPAlias())) { + user.setFirstName("Firstname"); + user.setLastName("Lastname"); + } + user.setEmailVerified(true); + user.setEnabled(true); + postInitializeUser(user); + + RealmResource realmResource = adminClient.realm(bc.providerRealmName()); + userId = createUserWithAdminClient(realmResource, user); + resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false); + + log.debug("adding identity provider to realm " + bc.consumerRealmName()); + RealmResource realm = adminClient.realm(bc.consumerRealmName()); + Response response = realm.identityProviders().create(bc.setUpIdentityProvider()); + if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { + String error = null; + try { + error = response.readEntity(String.class); + } catch (Exception ignored) { + } + throw new IllegalStateException("Failed to add identity provider " + bc.getIDPAlias() + " to realm " + + bc.consumerRealmName() + ": " + response.getStatus() + + (error != null && !error.isBlank() ? " - " + error : "")); + } + response.close(); + identityProviderResource = realm.identityProviders().get(bc.getIDPAlias()); + + addClientsToProviderAndConsumer(); + configureBrokerEndpoints(); + } + + protected void configureBrokerEndpoints() { + String providerBaseUrl = realmBaseUrl(bc.providerRealmName()); + IdentityProviderRepresentation idp = adminClient.realm(bc.consumerRealmName()) + .identityProviders().get(bc.getIDPAlias()).toRepresentation(); + Map config = idp.getConfig(); + config.put(OIDCIdentityProviderConfig.ISSUER, providerBaseUrl); + config.put("authorizationUrl", providerBaseUrl + "/protocol/openid-connect/auth"); + config.put(TOKEN_ENDPOINT_URL, providerBaseUrl + "/protocol/openid-connect/token"); + config.put("logoutUrl", providerBaseUrl + "/protocol/openid-connect/logout"); + config.put("userInfoUrl", providerBaseUrl + "/protocol/openid-connect/userinfo"); + config.put(OIDCIdentityProviderConfig.JWKS_URL, providerBaseUrl + "/protocol/openid-connect/certs"); + config.put(OIDCIdentityProviderConfig.USE_JWKS_URL, "true"); + config.put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, "true"); + adminClient.realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias()).update(idp); + + String consumerBaseUrl = realmBaseUrl(bc.consumerRealmName()); + String consumerBrokerEndpoint = consumerBaseUrl + "/broker/" + bc.getIDPAlias() + "/endpoint"; + ClientsResource providerClients = adminClient.realm(bc.providerRealmName()).clients(); + List clients = providerClients.findByClientId(CLIENT_ID); + if (!clients.isEmpty()) { + ClientRepresentation client = clients.get(0); + client.setRedirectUris(List.of(consumerBrokerEndpoint + "/*")); + client.setAdminUrl(consumerBrokerEndpoint); + Map attributes = client.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + client.setAttributes(attributes); + } + attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, + consumerBaseUrl + "/protocol/openid-connect/logout/backchannel-logout"); + providerClients.get(client.getId()).update(client); + } + } + + private String realmBaseUrl(String realmName) { + return keycloakUrls.getBase() + "/realms/" + realmName; + } + + protected void updateExecutions(BiConsumer action) { + AuthenticationManagementResource flows = adminClient.realm(bc.consumerRealmName()).flows(); + for (AuthenticationExecutionInfoRepresentation execution : flows.getExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW)) { + action.accept(execution, flows); + } + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java new file mode 100644 index 000000000000..0aea74286e24 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java @@ -0,0 +1,76 @@ +package org.keycloak.tests.broker; + +import java.util.List; + +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; + +/** + * + * @author hmlnarik + */ +public interface BrokerConfiguration { + + /** + * @return Representation of the realm at the identity provider side. + */ + RealmRepresentation createProviderRealm(); + + /** + * @return Representation of the realm at the broker side. + */ + RealmRepresentation createConsumerRealm(); + + List createProviderClients(); + + List createConsumerClients(); + + /** + * @return Representation of the identity provider for declaration in the broker + */ + default IdentityProviderRepresentation setUpIdentityProvider() { + return setUpIdentityProvider(IdentityProviderSyncMode.IMPORT); + } + + /** + * @return Representation of the identity provider for declaration in the broker + */ + IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode force); + + /** + * @return Name of realm containing identity provider. Must be consistent with {@link #createProviderRealm()} + */ + String providerRealmName(); + + /** + * @return Realm name of the broker. Must be consistent with {@link #createConsumerRealm()} + */ + String consumerRealmName(); + + /** + * @return Client ID of the identity provider as set in provider realm. + */ + String getIDPClientIdInProviderRealm(); + + /** + * @return User login name of the brokered user + */ + String getUserLogin(); + + /** + * @return Password of the brokered user + */ + String getUserPassword(); + + /** + * @return E-mail of the brokered user + */ + String getUserEmail(); + + /** + * @return Alias of the identity provider as defined in the broker realm + */ + String getIDPAlias(); +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java new file mode 100644 index 000000000000..68b165c0cfbb --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java @@ -0,0 +1,33 @@ +package org.keycloak.tests.broker; + +import org.keycloak.admin.client.resource.AuthenticationManagementResource; +import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; +import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; + +import static org.keycloak.models.utils.DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS; + +/** + * Static helpers for mutating the first-broker-login flow during tests. + */ +public final class BrokerFirstLoginFlowMutators { + + private BrokerFirstLoginFlowMutators() { + } + + public static void disableUpdateProfileOnFirstLogin(AuthenticationExecutionInfoRepresentation execution, + AuthenticationManagementResource flows) { + if (execution.getProviderId() != null + && execution.getProviderId().equals(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID)) { + execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name()); + flows.updateExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, execution); + } else if (execution.getAlias() != null && execution.getAlias().equals(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); + } + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java new file mode 100644 index 000000000000..b1ef0832aebb --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java @@ -0,0 +1,13 @@ +package org.keycloak.tests.broker; + +import org.keycloak.testframework.server.KeycloakServerConfig; +import org.keycloak.testframework.server.KeycloakServerConfigBuilder; + +public class BrokerServerConfig implements KeycloakServerConfig { + + @Override + public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder builder) { + return builder + .dependency("org.keycloak.tests", "keycloak-tests-custom-providers"); + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java new file mode 100644 index 000000000000..6cc25b452547 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java @@ -0,0 +1,86 @@ +package org.keycloak.tests.broker; + +public class BrokerTestConstants { + + public final static String REALM_PROV_NAME = "provider"; + public final static String REALM_CONS_NAME = "consumer"; + + public final static String IDP_OIDC_ALIAS = "kc-oidc-idp"; + public final static String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; + + public final static String IDP_SAML_ALIAS = "kc-saml-idp"; + public final static String IDP_SAML_PROVIDER_ID = "saml"; + + public final static String CLIENT_ID = "brokerapp"; + public final static String CLIENT_SECRET = "secret"; + public final static String VAULT_CLIENT_SECRET = "${vault.oidc_idp}"; + + public final static String USER_LOGIN = "testuser"; + public final static String USER_EMAIL = "user@localhost.com"; + public final static String USER_PASSWORD = "password"; + + public final static String IDP_SAML_SIGN_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygc" + + "DfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb4" + + "0tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SV" + + "Er55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3" + + "og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8" + + "ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKp" + + "r/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus" + + "7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiC" + + "KpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9" + + "BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzU" + + "alm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58c" + + "kAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZ" + + "V9wN85V0xZXWsw=="; + + public final static String IDP_SAML_SIGN_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG" + + "9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDV" + + "QQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDg" + + "YDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4" + + "GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQ" + + "MA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93b" + + "jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKr" + + "GTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfm" + + "m2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1F" + + "Vqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNG" + + "txwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBr" + + "cAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFl" + + "cNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD" + + "0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8" + + "C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW" + + "7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8Uei" + + "KAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKm" + + "CV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"; + + public final static String REALM_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwg" + + "gSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eA" + + "guHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3Iyw" + + "JYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK" + + "218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWw" + + "Y2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58S" + + "MDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfY" + + "P/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmq" + + "eFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1y" + + "ltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ38152" + + "7ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruV" + + "XXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh" + + "7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0l" + + "LcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaa" + + "PnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv" + + "2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt" + + "7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2I" + + "Y+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUA" + + "YqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQC" + + "ahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDs" + + "uqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIO" + + "PO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBP" + + "spPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GF" + + "Cp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn"; + + public final static String REALM_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgK" + + "CAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF0" + + "0U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlE" + + "Gq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmR" + + "bX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHO" + + "yyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB"; +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java new file mode 100644 index 000000000000..4102828f74cc --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java @@ -0,0 +1,132 @@ +package org.keycloak.tests.broker; + +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.apache.http.client.utils.URIBuilder; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; + +import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; + +public final class BrokerTestTools { + + private static String serverRoot; + + private BrokerTestTools() { + } + + public static void setServerRoot(String baseUrl) { + serverRoot = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; + } + + public static String getProviderRoot() { + String host2 = System.getProperty("auth.server.host2"); + if (host2 == null || host2.isBlank()) { + return getConsumerRoot(); + } + return new URIBuilder(URI.create(getConsumerRoot())) + .setHost(host2) + .toString(); + } + + public static String getConsumerRoot() { + if (serverRoot != null && !serverRoot.isBlank()) { + return serverRoot; + } + throw new IllegalStateException("BrokerTestTools server root not initialized"); + } + + public static String getAuthPath() { + String path = URI.create(getConsumerRoot()).getPath(); + if (path == null || path.isBlank() || "/".equals(path)) { + return ""; + } + return path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + } + + public static String encodeUrl(String url) { + return URLEncoder.encode(url, StandardCharsets.UTF_8); + } + + public static IdentityProviderRepresentation createIdentityProvider(String alias, String providerId) { + IdentityProviderRepresentation idp = new IdentityProviderRepresentation(); + idp.setAlias(alias); + idp.setProviderId(providerId); + idp.setEnabled(true); + idp.setTrustEmail(true); + idp.setStoreToken(false); + idp.setAddReadTokenRoleOnCreate(false); + return idp; + } + + public static void waitForPage(final WebDriver driver, final String title, final boolean isHtmlTitle) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); + ExpectedCondition condition = new ExpectedCondition() { + private String actualTitle = null; + + public Boolean apply(final WebDriver input) { + if (input == null) { + return false; + } + actualTitle = isHtmlTitle ? input.getTitle() : input.findElement(By.id("kc-page-title")).getText(); + return actualTitle != null && actualTitle.toLowerCase().contains(title.toLowerCase()); + } + + public String toString() { + return "page title to contain '" + title + "'. Actual title: '" + actualTitle + "'"; + } + }; + wait.until(condition); + } + + public static void waitForPage(final ManagedWebDriver driver, final String title, final boolean isHtmlTitle) { + waitForPage(driver.driver(), title, isHtmlTitle); + } + + public static void waitForElementEnabled(WebDriver driver, final String elementName) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); + ExpectedCondition condition = (WebDriver input) -> { + List elements = input.findElements(By.name(elementName)); + return (!elements.isEmpty()) && elements.get(0).isEnabled(); + }; + wait.until(condition); + } + + public static void waitForElementEnabled(ManagedWebDriver driver, final String elementName) { + waitForElementEnabled(driver.driver(), elementName); + } + + public static ClientRepresentation findClientByClientId(List clients, String clientId) { + return clients.stream() + .filter(client -> clientId.equals(client.getClientId())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Client not found: " + clientId)); + } + + public static IdentityProviderRepresentation findIdentityProvider(List providers, String alias) { + return providers.stream() + .filter(provider -> alias.equals(provider.getAlias())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Identity provider not found: " + alias)); + } + + public static boolean isOidcProvider(IdentityProviderRepresentation provider) { + return IDP_OIDC_PROVIDER_ID.equals(provider.getProviderId()); + } + + public static Map config(IdentityProviderRepresentation provider) { + return provider.getConfig(); + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java new file mode 100644 index 000000000000..490cf27b3149 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java @@ -0,0 +1,290 @@ +package org.keycloak.tests.broker; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper; +import org.keycloak.protocol.oidc.mappers.HardcodedClaim; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; +import org.keycloak.protocol.oidc.mappers.UserPropertyMapper; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; + +import static org.keycloak.broker.oidc.OAuth2IdentityProviderConfig.TOKEN_ENDPOINT_URL; +import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_ID; +import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_SECRET; +import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_ALIAS; +import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; +import static org.keycloak.tests.broker.BrokerTestConstants.REALM_CONS_NAME; +import static org.keycloak.tests.broker.BrokerTestConstants.REALM_PROV_NAME; +import static org.keycloak.tests.broker.BrokerTestConstants.USER_EMAIL; +import static org.keycloak.tests.broker.BrokerTestConstants.USER_LOGIN; +import static org.keycloak.tests.broker.BrokerTestConstants.USER_PASSWORD; +import static org.keycloak.tests.broker.BrokerTestTools.createIdentityProvider; +import static org.keycloak.tests.broker.BrokerTestTools.getAuthPath; +import static org.keycloak.tests.broker.BrokerTestTools.getConsumerRoot; +import static org.keycloak.tests.broker.BrokerTestTools.getProviderRoot; + +/** + * @author hmlnarik + */ +public class KcOidcBrokerConfiguration implements BrokerConfiguration { + + public static final KcOidcBrokerConfiguration INSTANCE = new KcOidcBrokerConfiguration(); + + protected static final String ATTRIBUTE_TO_MAP_NAME = "user-attribute"; + protected static final String ATTRIBUTE_TO_MAP_NAME_2 = "user-attribute-2"; + public static final String USER_INFO_CLAIM = "user-claim"; + public static final String HARDOCDED_CLAIM = "test"; + public static final String HARDOCDED_VALUE = "value"; + public static final String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app"; + public static final String CONSUMER_BROKER_APP_SECRET = "broker-app-secret"; + + @Override + public RealmRepresentation createProviderRealm() { + RealmRepresentation realm = new RealmRepresentation(); + realm.setRealm(REALM_PROV_NAME); + realm.setEnabled(true); + realm.setEventsListeners(Collections.singletonList("jboss-logging")); + realm.setEventsEnabled(true); + realm.setInternationalizationEnabled(true); + realm.setSupportedLocales(Set.of("en", "hu")); + + return realm; + } + + @Override + public RealmRepresentation createConsumerRealm() { + RealmRepresentation realm = new RealmRepresentation(); + realm.setRealm(REALM_CONS_NAME); + realm.setEnabled(true); + realm.setResetPasswordAllowed(true); + realm.setEventsListeners(Collections.singletonList("jboss-logging")); + realm.setEventsEnabled(true); + realm.setInternationalizationEnabled(true); + realm.setSupportedLocales(Set.of("en", "hu")); + + return realm; + } + + @Override + public List createProviderClients() { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(getIDPClientIdInProviderRealm()); + client.setName(CLIENT_ID); + client.setSecret(CLIENT_SECRET); + + client.setRedirectUris(Collections.singletonList(getConsumerRoot() + + getAuthPath() + "/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint/*")); + + client.setAdminUrl(getConsumerRoot() + + getAuthPath() + "/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint"); + + OIDCAdvancedConfigWrapper oidcClient = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); + oidcClient.setPostLogoutRedirectUris(Collections.singletonList("+")); + + oidcClient.setBackchannelLogoutUrl(getConsumerRoot() + + getAuthPath() + "/realms/" + consumerRealmName() + "/protocol/openid-connect/logout/backchannel-logout"); + + ProtocolMapperRepresentation emailMapper = new ProtocolMapperRepresentation(); + emailMapper.setName("email"); + emailMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + emailMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID); + + Map emailMapperConfig = emailMapper.getConfig(); + emailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "email"); + emailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "email"); + emailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + + ProtocolMapperRepresentation nestedAttrMapper = new ProtocolMapperRepresentation(); + nestedAttrMapper.setName("attribute - nested claim"); + nestedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + nestedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + + Map nestedEmailMapperConfig = nestedAttrMapper.getConfig(); + nestedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "nested.email"); + nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "nested.email"); + nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + + ProtocolMapperRepresentation dottedAttrMapper = new ProtocolMapperRepresentation(); + dottedAttrMapper.setName("attribute - claim with dot in name"); + dottedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + dottedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + + Map dottedEmailMapperConfig = dottedAttrMapper.getConfig(); + dottedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "dotted.email"); + dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "dotted\\.email"); + dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + + ProtocolMapperRepresentation userAttrMapper = new ProtocolMapperRepresentation(); + userAttrMapper.setName("attribute - name"); + userAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + + Map userAttrMapperConfig = userAttrMapper.getConfig(); + userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME); + userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME); + userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true"); + + ProtocolMapperRepresentation userAttrMapper2 = new ProtocolMapperRepresentation(); + userAttrMapper2.setName("attribute - name - 2"); + userAttrMapper2.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + userAttrMapper2.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + + Map userAttrMapperConfig2 = userAttrMapper2.getConfig(); + userAttrMapperConfig2.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME_2); + userAttrMapperConfig2.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME_2); + userAttrMapperConfig2.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + userAttrMapperConfig2.put(ProtocolMapperUtils.MULTIVALUED, "true"); + + ProtocolMapperRepresentation hardcodedJsonClaim = new ProtocolMapperRepresentation(); + hardcodedJsonClaim.setName("json-mapper"); + hardcodedJsonClaim.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + hardcodedJsonClaim.setProtocolMapper(HardcodedClaim.PROVIDER_ID); + + Map hardcodedJsonClaimMapperConfig = hardcodedJsonClaim.getConfig(); + hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, KcOidcBrokerConfiguration.USER_INFO_CLAIM); + hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, "JSON"); + hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + hardcodedJsonClaimMapperConfig.put(HardcodedClaim.CLAIM_VALUE, "{\"" + HARDOCDED_CLAIM + "\": \"" + HARDOCDED_VALUE + "\"}"); + + ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation(); + audienceMapper.setName("audience-brokerapp"); + audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); + + Map audienceMapperConfig = audienceMapper.getConfig(); + audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CLIENT_ID); + audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + + client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper, hardcodedJsonClaim, audienceMapper)); + + return Collections.singletonList(client); + } + + @Override + public List createConsumerClients() { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(CONSUMER_BROKER_APP_CLIENT_ID); + client.setName("broker-app"); + client.setSecret(CONSUMER_BROKER_APP_SECRET); + client.setEnabled(true); + client.setDirectAccessGrantsEnabled(true); + + client.setRedirectUris(Collections.singletonList(getConsumerRoot() + + getAuthPath() + "/*")); + + client.setBaseUrl(getConsumerRoot() + + getAuthPath() + "/realms/" + REALM_CONS_NAME + "/app"); + + OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+")); + OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseRefreshTokenForClientCredentialsGrant(true); + + ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation(); + audienceMapper.setName("audience-broker-app"); + audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); + + Map audienceMapperConfig = audienceMapper.getConfig(); + audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CONSUMER_BROKER_APP_CLIENT_ID); + audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + + client.setProtocolMappers(Collections.singletonList(audienceMapper)); + + return Collections.singletonList(client); + } + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) { + IdentityProviderRepresentation idp = createIdentityProvider(getIDPAlias(), IDP_OIDC_PROVIDER_ID); + + Map config = idp.getConfig(); + applyDefaultConfiguration(config, syncMode); + + return idp; + } + + protected void applyDefaultConfiguration(final Map config, IdentityProviderSyncMode syncMode) { + config.put(IdentityProviderModel.SYNC_MODE, syncMode.toString()); + config.put("clientId", CLIENT_ID); + config.put("clientSecret", CLIENT_SECRET); + config.put("prompt", "login"); + config.put("loginHint", "true"); + config.put(OIDCIdentityProviderConfig.ISSUER, getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME); + config.put("authorizationUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/auth"); + config.put(TOKEN_ENDPOINT_URL, getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/token"); + config.put("logoutUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/logout"); + config.put("userInfoUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo"); + config.put("defaultScope", "email profile"); + config.put("backchannelSupported", "true"); + config.put(OIDCIdentityProviderConfig.JWKS_URL, + getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/certs"); + config.put(OIDCIdentityProviderConfig.USE_JWKS_URL, "true"); + config.put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, "true"); + } + + @Override + public String getUserLogin() { + return USER_LOGIN; + } + + @Override + public String getIDPClientIdInProviderRealm() { + return CLIENT_ID; + } + + @Override + public String getUserPassword() { + return USER_PASSWORD; + } + + @Override + public String getUserEmail() { + return USER_EMAIL; + } + + @Override + public String providerRealmName() { + return REALM_PROV_NAME; + } + + @Override + public String consumerRealmName() { + return REALM_CONS_NAME; + } + + @Override + public String getIDPAlias() { + return IDP_OIDC_ALIAS; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java similarity index 79% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java rename to tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java index 5e79263cb7d7..4eb717569e0d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.broker; +package org.keycloak.tests.broker; import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.IdentityProviderResource; @@ -12,16 +12,18 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.realm.AuthenticationExecutionBuilder; import org.keycloak.testsuite.util.AccountHelper; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +@KeycloakIntegrationTest(config = BrokerServerConfig.class) public class KcOidcFirstBrokerLoginDetectExistingUserTest extends AbstractInitializedBaseBrokerTest { @Override @@ -30,7 +32,7 @@ protected BrokerConfiguration getBrokerConfiguration() { } @Override - @Before + @BeforeEach public void beforeBrokerTest() { super.beforeBrokerTest(); log.debug("creating detect existing user flow for realm " + bc.providerRealmName()); @@ -38,28 +40,27 @@ public void beforeBrokerTest() { final RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); AuthenticationManagementResource authMgmtResource = consumerRealm.flows(); - // Creates detectExistingUserFlow String detectExistingFlowAlias = "detectExistingUserFlow"; - final AuthenticationFlowRepresentation authenticationFlowRepresentation = newFlow(detectExistingFlowAlias, detectExistingFlowAlias, "basic-flow", true, false); + final AuthenticationFlowRepresentation authenticationFlowRepresentation = + newFlow(detectExistingFlowAlias, detectExistingFlowAlias, "basic-flow", true, false); authMgmtResource.createFlow(authenticationFlowRepresentation); AuthenticationFlowRepresentation authenticationFlowRepresentation1 = getFlow(authMgmtResource, detectExistingFlowAlias); assertNotNull(authenticationFlowRepresentation1, "The authentication flow must exist"); - String flowId = authenticationFlowRepresentation1.getId(); // retrieves the id of the newly created flow + String flowId = authenticationFlowRepresentation1.getId(); - // Adds executions to the flow addExecution(authMgmtResource, flowId, IdpDetectExistingBrokerUserAuthenticatorFactory.PROVIDER_ID, 10); addExecution(authMgmtResource, flowId, IdpAutoLinkAuthenticatorFactory.PROVIDER_ID, 20); - // Updates the FirstBrokerLoginFlowAlias for the identity provider IdentityProviderResource identityConsumerResource = consumerRealm.identityProviders().get(bc.getIDPAlias()); IdentityProviderRepresentation identityProviderRepresentation = consumerRealm.identityProviders().findAll().get(0); identityProviderRepresentation.setFirstBrokerLoginFlowAlias(detectExistingFlowAlias); identityProviderRepresentation.getConfig().put(IdentityProviderModel.SYNC_MODE, IdentityProviderSyncMode.FORCE.toString()); identityConsumerResource.update(identityProviderRepresentation); - assertEquals(2, getFlow(authMgmtResource, detectExistingFlowAlias).getAuthenticationExecutions().size(), "Two executions must have been created"); + assertEquals(2, getFlow(authMgmtResource, detectExistingFlowAlias).getAuthenticationExecutions().size(), + "Two executions must have been created"); } private void addExecution(AuthenticationManagementResource authMgmtResource, String flowId, String providerId, int priority) { @@ -79,9 +80,8 @@ private AuthenticationFlowRepresentation getFlow(AuthenticationManagementResourc .findFirst().get(); } - private AuthenticationFlowRepresentation newFlow(String alias, String description, - String providerId, boolean topLevel, boolean builtIn) { + String providerId, boolean topLevel, boolean builtIn) { AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation(); flow.setAlias(alias); flow.setDescription(description); @@ -93,28 +93,25 @@ private AuthenticationFlowRepresentation newFlow(String alias, String descriptio @Test public void loginWhenUserDoesNotExistOnConsumer() { - - updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin); + updateExecutions(BrokerFirstLoginFlowMutators::disableUpdateProfileOnFirstLogin); String firstname = "Firstname"; String lastname = "Lastname"; String username = "firstandlastname"; createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, "firstnamelastname@example.org"); - oauth.client("broker-app"); - oauth.realm(bc.consumerRealmName()); - oauth.openLoginForm(); + openConsumerBrokerLoginForm(); logInWithIdp(bc.getIDPAlias(), username, BrokerTestConstants.USER_PASSWORD); errorPage.assertCurrent(); - - assertEquals("User " + username + " authenticated with identity provider " + bc.getIDPAlias() + " does not exist. Please contact your administrator.", loginPage.getInstruction()); + assertEquals("User " + username + " authenticated with identity provider " + bc.getIDPAlias() + + " does not exist. Please contact your administrator.", errorPage.getError()); } @Test public void loginWhenUserExistsOnConsumer() { - updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin); + updateExecutions(BrokerFirstLoginFlowMutators::disableUpdateProfileOnFirstLogin); final String firstname = "Firstname_loginWhenUserExistsOnConsumer"; final String lastname = "Lastname_loginWhenUserExistsOnConsumer"; @@ -123,18 +120,16 @@ public void loginWhenUserExistsOnConsumer() { createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, email); createUser(bc.consumerRealmName(), username, "THIS PASSWORD IS USELESS", null, null, email); - oauth.client("broker-app"); - oauth.realm(bc.consumerRealmName()); - oauth.openLoginForm(); + openConsumerBrokerLoginForm(); logInWithIdp(bc.getIDPAlias(), username, BrokerTestConstants.USER_PASSWORD); - assertTrue(driver.getTitle().contains("AUTH_RESPONSE")); + assertTrue(oauth.parseLoginResponse().isSuccess(), "Broker login should complete successfully"); UserRepresentation userRepresentation = AccountHelper.getUserRepresentation( adminClient.realm(bc.consumerRealmName()), username); - assertEquals(userRepresentation.getEmail(), email, "Email is not correct"); - assertEquals(userRepresentation.getFirstName(), firstname, "Firstname is not correct"); - assertEquals(userRepresentation.getLastName(), lastname, "Lastname is not correct"); + assertEquals(email, userRepresentation.getEmail(), "Email is not correct"); + assertEquals(firstname, userRepresentation.getFirstName(), "Firstname is not correct"); + assertEquals(lastname, userRepresentation.getLastName(), "Lastname is not correct"); } } From 71ec5a6f52b1cdbcd69f1ef02624857136e7e31a Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 16 Sep 2026 09:43:17 +0200 Subject: [PATCH 2/3] Address Copilot review feedback for broker test infrastructure Initialize identity-provider and protocol-mapper config maps before use, fail fast when the provider broker client is missing, use ThreadLocal for broker server-root state, and tidy BrokerTestConstants modifier order. Signed-off-by: Erik Jan de Wit --- .../tests/broker/AbstractBaseBrokerTest.java | 1 + .../AbstractInitializedBaseBrokerTest.java | 30 +++++++++------ .../tests/broker/BrokerTestConstants.java | 37 ++++++++++--------- .../tests/broker/BrokerTestTools.java | 11 +++++- .../broker/KcOidcBrokerConfiguration.java | 25 +++++++++---- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java index a288e7ee04fe..a3b543aec2a4 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java @@ -91,6 +91,7 @@ public void afterBrokerTest() { driver.driver().manage().deleteAllCookies(); removeRealm(bc.consumerRealmName()); removeRealm(bc.providerRealmName()); + BrokerTestTools.clearServerRoot(); } protected void importRealm(RealmRepresentation realmRepresentation) { diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java index 416ef758c992..5a4f151f7d4f 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java @@ -95,19 +95,25 @@ protected void configureBrokerEndpoints() { String consumerBrokerEndpoint = consumerBaseUrl + "/broker/" + bc.getIDPAlias() + "/endpoint"; ClientsResource providerClients = adminClient.realm(bc.providerRealmName()).clients(); List clients = providerClients.findByClientId(CLIENT_ID); - if (!clients.isEmpty()) { - ClientRepresentation client = clients.get(0); - client.setRedirectUris(List.of(consumerBrokerEndpoint + "/*")); - client.setAdminUrl(consumerBrokerEndpoint); - Map attributes = client.getAttributes(); - if (attributes == null) { - attributes = new HashMap<>(); - client.setAttributes(attributes); - } - attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, - consumerBaseUrl + "/protocol/openid-connect/logout/backchannel-logout"); - providerClients.get(client.getId()).update(client); + if (clients.isEmpty()) { + throw new IllegalStateException("Provider client '" + CLIENT_ID + "' not found in realm " + + bc.providerRealmName()); + } + if (clients.size() > 1) { + throw new IllegalStateException("Multiple provider clients found for clientId '" + CLIENT_ID + "' in realm " + + bc.providerRealmName()); + } + ClientRepresentation client = clients.get(0); + client.setRedirectUris(List.of(consumerBrokerEndpoint + "/*")); + client.setAdminUrl(consumerBrokerEndpoint); + Map attributes = client.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + client.setAttributes(attributes); } + attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, + consumerBaseUrl + "/protocol/openid-connect/logout/backchannel-logout"); + providerClients.get(client.getId()).update(client); } private String realmBaseUrl(String realmName) { diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java index 6cc25b452547..1031867d8d71 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java @@ -1,25 +1,28 @@ package org.keycloak.tests.broker; -public class BrokerTestConstants { +public final class BrokerTestConstants { - public final static String REALM_PROV_NAME = "provider"; - public final static String REALM_CONS_NAME = "consumer"; + private BrokerTestConstants() { + } - public final static String IDP_OIDC_ALIAS = "kc-oidc-idp"; - public final static String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; + public static final String REALM_PROV_NAME = "provider"; + public static final String REALM_CONS_NAME = "consumer"; - public final static String IDP_SAML_ALIAS = "kc-saml-idp"; - public final static String IDP_SAML_PROVIDER_ID = "saml"; + public static final String IDP_OIDC_ALIAS = "kc-oidc-idp"; + public static final String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; - public final static String CLIENT_ID = "brokerapp"; - public final static String CLIENT_SECRET = "secret"; - public final static String VAULT_CLIENT_SECRET = "${vault.oidc_idp}"; + public static final String IDP_SAML_ALIAS = "kc-saml-idp"; + public static final String IDP_SAML_PROVIDER_ID = "saml"; - public final static String USER_LOGIN = "testuser"; - public final static String USER_EMAIL = "user@localhost.com"; - public final static String USER_PASSWORD = "password"; + public static final String CLIENT_ID = "brokerapp"; + public static final String CLIENT_SECRET = "secret"; + public static final String VAULT_CLIENT_SECRET = "${vault.oidc_idp}"; - public final static String IDP_SAML_SIGN_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygc" + + public static final String USER_LOGIN = "testuser"; + public static final String USER_EMAIL = "user@localhost.com"; + public static final String USER_PASSWORD = "password"; + + public static final String IDP_SAML_SIGN_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygc" + "DfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb4" + "0tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SV" + "Er55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3" + @@ -33,7 +36,7 @@ public class BrokerTestConstants { "kAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZ" + "V9wN85V0xZXWsw=="; - public final static String IDP_SAML_SIGN_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG" + + public static final String IDP_SAML_SIGN_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG" + "9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDV" + "QQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDg" + "YDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4" + @@ -52,7 +55,7 @@ public class BrokerTestConstants { "KAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKm" + "CV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"; - public final static String REALM_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwg" + + public static final String REALM_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwg" + "gSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eA" + "guHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3Iyw" + "JYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK" + @@ -77,7 +80,7 @@ public class BrokerTestConstants { "spPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GF" + "Cp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn"; - public final static String REALM_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgK" + + public static final String REALM_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgK" + "CAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF0" + "0U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlE" + "Gq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmR" + diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java index 4102828f74cc..4b4495ef6ecb 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java @@ -4,6 +4,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,13 +23,17 @@ public final class BrokerTestTools { - private static String serverRoot; + private static final ThreadLocal SERVER_ROOT = new ThreadLocal<>(); private BrokerTestTools() { } public static void setServerRoot(String baseUrl) { - serverRoot = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; + SERVER_ROOT.set(baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl); + } + + public static void clearServerRoot() { + SERVER_ROOT.remove(); } public static String getProviderRoot() { @@ -42,6 +47,7 @@ public static String getProviderRoot() { } public static String getConsumerRoot() { + String serverRoot = SERVER_ROOT.get(); if (serverRoot != null && !serverRoot.isBlank()) { return serverRoot; } @@ -68,6 +74,7 @@ public static IdentityProviderRepresentation createIdentityProvider(String alias idp.setTrustEmail(true); idp.setStoreToken(false); idp.setAddReadTokenRoleOnCreate(false); + idp.setConfig(new HashMap<>()); return idp; } diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java index 490cf27b3149..820a7357b719 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -104,46 +105,49 @@ public List createProviderClients() { emailMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); emailMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID); - Map emailMapperConfig = emailMapper.getConfig(); + Map emailMapperConfig = new HashMap<>(); emailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "email"); emailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "email"); emailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + emailMapper.setConfig(emailMapperConfig); ProtocolMapperRepresentation nestedAttrMapper = new ProtocolMapperRepresentation(); nestedAttrMapper.setName("attribute - nested claim"); nestedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); nestedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - Map nestedEmailMapperConfig = nestedAttrMapper.getConfig(); + Map nestedEmailMapperConfig = new HashMap<>(); nestedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "nested.email"); nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "nested.email"); nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + nestedAttrMapper.setConfig(nestedEmailMapperConfig); ProtocolMapperRepresentation dottedAttrMapper = new ProtocolMapperRepresentation(); dottedAttrMapper.setName("attribute - claim with dot in name"); dottedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); dottedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - Map dottedEmailMapperConfig = dottedAttrMapper.getConfig(); + Map dottedEmailMapperConfig = new HashMap<>(); dottedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "dotted.email"); dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "dotted\\.email"); dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + dottedAttrMapper.setConfig(dottedEmailMapperConfig); ProtocolMapperRepresentation userAttrMapper = new ProtocolMapperRepresentation(); userAttrMapper.setName("attribute - name"); userAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - Map userAttrMapperConfig = userAttrMapper.getConfig(); + Map userAttrMapperConfig = new HashMap<>(); userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME); userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME); userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); @@ -151,13 +155,14 @@ public List createProviderClients() { userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true"); + userAttrMapper.setConfig(userAttrMapperConfig); ProtocolMapperRepresentation userAttrMapper2 = new ProtocolMapperRepresentation(); userAttrMapper2.setName("attribute - name - 2"); userAttrMapper2.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); userAttrMapper2.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - Map userAttrMapperConfig2 = userAttrMapper2.getConfig(); + Map userAttrMapperConfig2 = new HashMap<>(); userAttrMapperConfig2.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME_2); userAttrMapperConfig2.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME_2); userAttrMapperConfig2.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); @@ -165,26 +170,29 @@ public List createProviderClients() { userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); userAttrMapperConfig2.put(ProtocolMapperUtils.MULTIVALUED, "true"); + userAttrMapper2.setConfig(userAttrMapperConfig2); ProtocolMapperRepresentation hardcodedJsonClaim = new ProtocolMapperRepresentation(); hardcodedJsonClaim.setName("json-mapper"); hardcodedJsonClaim.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); hardcodedJsonClaim.setProtocolMapper(HardcodedClaim.PROVIDER_ID); - Map hardcodedJsonClaimMapperConfig = hardcodedJsonClaim.getConfig(); + Map hardcodedJsonClaimMapperConfig = new HashMap<>(); hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, KcOidcBrokerConfiguration.USER_INFO_CLAIM); hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, "JSON"); hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); hardcodedJsonClaimMapperConfig.put(HardcodedClaim.CLAIM_VALUE, "{\"" + HARDOCDED_CLAIM + "\": \"" + HARDOCDED_VALUE + "\"}"); + hardcodedJsonClaim.setConfig(hardcodedJsonClaimMapperConfig); ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation(); audienceMapper.setName("audience-brokerapp"); audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); - Map audienceMapperConfig = audienceMapper.getConfig(); + Map audienceMapperConfig = new HashMap<>(); audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CLIENT_ID); audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + audienceMapper.setConfig(audienceMapperConfig); client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper, hardcodedJsonClaim, audienceMapper)); @@ -214,9 +222,10 @@ public List createConsumerClients() { audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); - Map audienceMapperConfig = audienceMapper.getConfig(); + Map audienceMapperConfig = new HashMap<>(); audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CONSUMER_BROKER_APP_CLIENT_ID); audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + audienceMapper.setConfig(audienceMapperConfig); client.setProtocolMappers(Collections.singletonList(audienceMapper)); From d5291b87dccf1f4573ac51382332da41df5bd95d Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 24 Sep 2026 15:43:31 +0200 Subject: [PATCH 3/3] Reuse AbstractKcOidcBrokerTest for detect-existing broker login test Drop the parallel BrokerConfiguration-based test hierarchy and unused helpers, addressing review feedback about duplication and dead code. Signed-off-by: Erik Jan de Wit --- .../testframework/ui/page/LoginPage.java | 10 - .../tests/broker/AbstractBaseBrokerTest.java | 198 ------------ .../AbstractInitializedBaseBrokerTest.java | 129 -------- .../tests/broker/BrokerConfiguration.java | 76 ----- .../broker/BrokerFirstLoginFlowMutators.java | 33 -- .../tests/broker/BrokerServerConfig.java | 13 - .../tests/broker/BrokerTestConstants.java | 89 ------ .../tests/broker/BrokerTestTools.java | 139 -------- .../broker/KcOidcBrokerConfiguration.java | 299 ------------------ ...irstBrokerLoginDetectExistingUserTest.java | 131 +++++--- 10 files changed, 80 insertions(+), 1037 deletions(-) delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java delete mode 100644 tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java index 7e026ec824f4..292a396020df 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java @@ -54,16 +54,6 @@ public void fillPassword(String password) { passwordInput.sendKeys(password); } - public void login(String username, String password) { - fillLogin(username, password); - submit(); - } - - public void login(String password) { - fillPassword(password); - submit(); - } - public void submit() { submitButton.click(); } diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java deleted file mode 100644 index a3b543aec2a4..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractBaseBrokerTest.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.keycloak.tests.broker; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.core.Response; - -import org.keycloak.admin.client.Keycloak; -import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.testframework.annotations.InjectAdminClient; -import org.keycloak.testframework.annotations.InjectKeycloakUrls; -import org.keycloak.testframework.oauth.OAuthClient; -import org.keycloak.testframework.oauth.TestApp; -import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; -import org.keycloak.testframework.oauth.annotations.InjectTestApp; -import org.keycloak.testframework.realm.UserBuilder; -import org.keycloak.testframework.server.KeycloakUrls; -import org.keycloak.testframework.ui.annotations.InjectPage; -import org.keycloak.testframework.ui.annotations.InjectWebDriver; -import org.keycloak.testframework.ui.page.ErrorPage; -import org.keycloak.testframework.ui.page.LoginPage; -import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; -import org.keycloak.testsuite.util.userprofile.UserProfileUtil; - -import org.jboss.logging.Logger; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.openqa.selenium.By; -import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.support.ui.WebDriverWait; - -import static org.keycloak.tests.broker.BrokerTestConstants.USER_EMAIL; -import static org.keycloak.tests.broker.BrokerTestTools.waitForPage; -import static org.keycloak.tests.utils.admin.AdminApiUtil.createUserWithAdminClient; -import static org.keycloak.tests.utils.admin.AdminApiUtil.resetUserPassword; - -/** - * Base for broker tests that set up provider/consumer realms programmatically via {@link BrokerConfiguration}. - */ -public abstract class AbstractBaseBrokerTest { - - protected final Logger log = Logger.getLogger(getClass()); - - @InjectAdminClient - protected Keycloak adminClient; - - @InjectWebDriver - protected ManagedWebDriver driver; - - @InjectOAuthClient - protected OAuthClient oauth; - - @InjectTestApp - protected TestApp testApp; - - @InjectKeycloakUrls - protected KeycloakUrls keycloakUrls; - - @InjectPage - protected LoginPage loginPage; - - @InjectPage - protected ErrorPage errorPage; - - protected BrokerConfiguration bc = getBrokerConfiguration(); - - protected abstract BrokerConfiguration getBrokerConfiguration(); - - @BeforeEach - public void beforeBrokerTest() { - BrokerTestTools.setServerRoot(keycloakUrls.getBase()); - - RealmRepresentation consumerRealm = bc.createConsumerRealm(); - RealmRepresentation providerRealm = bc.createProviderRealm(); - importRealm(consumerRealm); - importRealm(providerRealm); - - UserProfileUtil.enableUnmanagedAttributes(adminClient.realm(consumerRealm.getRealm()).users().userProfile()); - UserProfileUtil.enableUnmanagedAttributes(adminClient.realm(providerRealm.getRealm()).users().userProfile()); - } - - @AfterEach - public void afterBrokerTest() { - driver.driver().manage().deleteAllCookies(); - removeRealm(bc.consumerRealmName()); - removeRealm(bc.providerRealmName()); - BrokerTestTools.clearServerRoot(); - } - - protected void importRealm(RealmRepresentation realmRepresentation) { - removeRealm(realmRepresentation.getRealm()); - adminClient.realms().create(realmRepresentation); - } - - protected void removeRealm(String realmName) { - try { - adminClient.realm(realmName).remove(); - } catch (NotFoundException ignored) { - } - } - - protected void addClientsToProviderAndConsumer() { - RealmResource providerRealm = adminClient.realm(bc.providerRealmName()); - for (ClientRepresentation client : bc.createProviderClients()) { - Response response = providerRealm.clients().create(client); - response.close(); - } - - List consumerClients = bc.createConsumerClients(); - if (consumerClients != null) { - RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); - for (ClientRepresentation client : consumerClients) { - if (KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_CLIENT_ID.equals(client.getClientId())) { - List redirectUris = new ArrayList<>(client.getRedirectUris()); - redirectUris.add(testApp.getRedirectionUri()); - client.setRedirectUris(redirectUris); - } - Response response = consumerRealm.clients().create(client); - response.close(); - } - } - } - - public String createUser(String realm, String username, String password, String... requiredActions) { - UserRepresentation user = UserBuilder.create().username(username).enabled(true).build(); - user.setRequiredActions(Arrays.asList(requiredActions)); - String createdUserId = createUserWithAdminClient(adminClient.realm(realm), user); - resetUserPassword(adminClient.realm(realm).users().get(createdUserId), password, false); - return createdUserId; - } - - protected String createUser(String realm, String username, String password, String firstName, String lastName, String email) { - UserRepresentation newUser = UserBuilder.create() - .username(username) - .email(email) - .firstName(firstName) - .lastName(lastName) - .enabled(true) - .build(); - String createdUserId = createUserWithAdminClient(adminClient.realm(realm), newUser); - resetUserPassword(adminClient.realm(realm).users().get(createdUserId), password, false); - return createdUserId; - } - - protected String createUser(String username, String email) { - UserRepresentation newUser = UserBuilder.create().username(username).email(email).enabled(true).build(); - String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser); - resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false); - return userId; - } - - protected String createUser(String username) { - return createUser(username, USER_EMAIL); - } - - protected void openConsumerBrokerLoginForm() { - oauth.client(KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_CLIENT_ID, - KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET); - oauth.realm(bc.consumerRealmName()); - oauth.redirectUri(testApp.getRedirectionUri()); - oauth.openLoginForm(); - } - - protected void logInWithIdp(String idpAlias, String username, String password) { - waitForPage(driver, "sign in to", true); - String initialUrl = driver.getCurrentUrl(); - log.debug("Clicking social " + idpAlias); - loginPage.clickSocial(idpAlias); - try { - new WebDriverWait(driver.driver(), Duration.ofSeconds(10)).until(webDriver -> - !webDriver.getCurrentUrl().equals(initialUrl)); - - String brokerLoginSegment = "/broker/" + idpAlias + "/login"; - if (driver.getCurrentUrl().contains(brokerLoginSegment)) { - List forms = driver.driver().findElements(By.tagName("form")); - if (!forms.isEmpty()) { - forms.get(0).submit(); - } - new WebDriverWait(driver.driver(), Duration.ofSeconds(10)).until(webDriver -> - !webDriver.getCurrentUrl().contains(brokerLoginSegment)); - } - } catch (TimeoutException e) { - Assertions.fail("Timed out waiting for social redirect for " + idpAlias + ". URL: " + driver.getCurrentUrl()); - } - if (loginPage.isUsernameInputPresent()) { - loginPage.login(username, password); - } else if (loginPage.isPasswordInputPresent()) { - loginPage.login(password); - } - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java deleted file mode 100644 index 5a4f151f7d4f..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/AbstractInitializedBaseBrokerTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.keycloak.tests.broker; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - -import jakarta.ws.rs.core.Response; - -import org.keycloak.admin.client.resource.AuthenticationManagementResource; -import org.keycloak.admin.client.resource.ClientsResource; -import org.keycloak.admin.client.resource.IdentityProviderResource; -import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; -import org.keycloak.models.utils.DefaultAuthenticationFlows; -import org.keycloak.protocol.oidc.OIDCConfigAttributes; -import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.representations.idm.UserRepresentation; - -import org.junit.jupiter.api.BeforeEach; - -import static org.keycloak.broker.oidc.OAuth2IdentityProviderConfig.TOKEN_ENDPOINT_URL; -import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_ID; -import static org.keycloak.tests.broker.BrokerTestConstants.IDP_SAML_ALIAS; -import static org.keycloak.tests.utils.admin.AdminApiUtil.createUserWithAdminClient; -import static org.keycloak.tests.utils.admin.AdminApiUtil.resetUserPassword; - -public abstract class AbstractInitializedBaseBrokerTest extends AbstractBaseBrokerTest { - - protected IdentityProviderResource identityProviderResource; - protected String userId; - - protected void postInitializeUser(UserRepresentation user) { - } - - @Override - @BeforeEach - public void beforeBrokerTest() { - super.beforeBrokerTest(); - log.debug("creating user for realm " + bc.providerRealmName()); - - UserRepresentation user = new UserRepresentation(); - user.setUsername(bc.getUserLogin()); - user.setEmail(bc.getUserEmail()); - if (IDP_SAML_ALIAS.equals(bc.getIDPAlias())) { - user.setFirstName("Firstname"); - user.setLastName("Lastname"); - } - user.setEmailVerified(true); - user.setEnabled(true); - postInitializeUser(user); - - RealmResource realmResource = adminClient.realm(bc.providerRealmName()); - userId = createUserWithAdminClient(realmResource, user); - resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false); - - log.debug("adding identity provider to realm " + bc.consumerRealmName()); - RealmResource realm = adminClient.realm(bc.consumerRealmName()); - Response response = realm.identityProviders().create(bc.setUpIdentityProvider()); - if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { - String error = null; - try { - error = response.readEntity(String.class); - } catch (Exception ignored) { - } - throw new IllegalStateException("Failed to add identity provider " + bc.getIDPAlias() + " to realm " - + bc.consumerRealmName() + ": " + response.getStatus() - + (error != null && !error.isBlank() ? " - " + error : "")); - } - response.close(); - identityProviderResource = realm.identityProviders().get(bc.getIDPAlias()); - - addClientsToProviderAndConsumer(); - configureBrokerEndpoints(); - } - - protected void configureBrokerEndpoints() { - String providerBaseUrl = realmBaseUrl(bc.providerRealmName()); - IdentityProviderRepresentation idp = adminClient.realm(bc.consumerRealmName()) - .identityProviders().get(bc.getIDPAlias()).toRepresentation(); - Map config = idp.getConfig(); - config.put(OIDCIdentityProviderConfig.ISSUER, providerBaseUrl); - config.put("authorizationUrl", providerBaseUrl + "/protocol/openid-connect/auth"); - config.put(TOKEN_ENDPOINT_URL, providerBaseUrl + "/protocol/openid-connect/token"); - config.put("logoutUrl", providerBaseUrl + "/protocol/openid-connect/logout"); - config.put("userInfoUrl", providerBaseUrl + "/protocol/openid-connect/userinfo"); - config.put(OIDCIdentityProviderConfig.JWKS_URL, providerBaseUrl + "/protocol/openid-connect/certs"); - config.put(OIDCIdentityProviderConfig.USE_JWKS_URL, "true"); - config.put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, "true"); - adminClient.realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias()).update(idp); - - String consumerBaseUrl = realmBaseUrl(bc.consumerRealmName()); - String consumerBrokerEndpoint = consumerBaseUrl + "/broker/" + bc.getIDPAlias() + "/endpoint"; - ClientsResource providerClients = adminClient.realm(bc.providerRealmName()).clients(); - List clients = providerClients.findByClientId(CLIENT_ID); - if (clients.isEmpty()) { - throw new IllegalStateException("Provider client '" + CLIENT_ID + "' not found in realm " - + bc.providerRealmName()); - } - if (clients.size() > 1) { - throw new IllegalStateException("Multiple provider clients found for clientId '" + CLIENT_ID + "' in realm " - + bc.providerRealmName()); - } - ClientRepresentation client = clients.get(0); - client.setRedirectUris(List.of(consumerBrokerEndpoint + "/*")); - client.setAdminUrl(consumerBrokerEndpoint); - Map attributes = client.getAttributes(); - if (attributes == null) { - attributes = new HashMap<>(); - client.setAttributes(attributes); - } - attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, - consumerBaseUrl + "/protocol/openid-connect/logout/backchannel-logout"); - providerClients.get(client.getId()).update(client); - } - - private String realmBaseUrl(String realmName) { - return keycloakUrls.getBase() + "/realms/" + realmName; - } - - protected void updateExecutions(BiConsumer action) { - AuthenticationManagementResource flows = adminClient.realm(bc.consumerRealmName()).flows(); - for (AuthenticationExecutionInfoRepresentation execution : flows.getExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW)) { - action.accept(execution, flows); - } - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java deleted file mode 100644 index 0aea74286e24..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerConfiguration.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.keycloak.tests.broker; - -import java.util.List; - -import org.keycloak.models.IdentityProviderSyncMode; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; - -/** - * - * @author hmlnarik - */ -public interface BrokerConfiguration { - - /** - * @return Representation of the realm at the identity provider side. - */ - RealmRepresentation createProviderRealm(); - - /** - * @return Representation of the realm at the broker side. - */ - RealmRepresentation createConsumerRealm(); - - List createProviderClients(); - - List createConsumerClients(); - - /** - * @return Representation of the identity provider for declaration in the broker - */ - default IdentityProviderRepresentation setUpIdentityProvider() { - return setUpIdentityProvider(IdentityProviderSyncMode.IMPORT); - } - - /** - * @return Representation of the identity provider for declaration in the broker - */ - IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode force); - - /** - * @return Name of realm containing identity provider. Must be consistent with {@link #createProviderRealm()} - */ - String providerRealmName(); - - /** - * @return Realm name of the broker. Must be consistent with {@link #createConsumerRealm()} - */ - String consumerRealmName(); - - /** - * @return Client ID of the identity provider as set in provider realm. - */ - String getIDPClientIdInProviderRealm(); - - /** - * @return User login name of the brokered user - */ - String getUserLogin(); - - /** - * @return Password of the brokered user - */ - String getUserPassword(); - - /** - * @return E-mail of the brokered user - */ - String getUserEmail(); - - /** - * @return Alias of the identity provider as defined in the broker realm - */ - String getIDPAlias(); -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java deleted file mode 100644 index 68b165c0cfbb..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerFirstLoginFlowMutators.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.keycloak.tests.broker; - -import org.keycloak.admin.client.resource.AuthenticationManagementResource; -import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.utils.DefaultAuthenticationFlows; -import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; -import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; - -import static org.keycloak.models.utils.DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS; - -/** - * Static helpers for mutating the first-broker-login flow during tests. - */ -public final class BrokerFirstLoginFlowMutators { - - private BrokerFirstLoginFlowMutators() { - } - - public static void disableUpdateProfileOnFirstLogin(AuthenticationExecutionInfoRepresentation execution, - AuthenticationManagementResource flows) { - if (execution.getProviderId() != null - && execution.getProviderId().equals(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID)) { - execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name()); - flows.updateExecutions(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, execution); - } else if (execution.getAlias() != null && execution.getAlias().equals(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); - } - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java deleted file mode 100644 index b1ef0832aebb..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerServerConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.keycloak.tests.broker; - -import org.keycloak.testframework.server.KeycloakServerConfig; -import org.keycloak.testframework.server.KeycloakServerConfigBuilder; - -public class BrokerServerConfig implements KeycloakServerConfig { - - @Override - public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder builder) { - return builder - .dependency("org.keycloak.tests", "keycloak-tests-custom-providers"); - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java deleted file mode 100644 index 1031867d8d71..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestConstants.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.keycloak.tests.broker; - -public final class BrokerTestConstants { - - private BrokerTestConstants() { - } - - public static final String REALM_PROV_NAME = "provider"; - public static final String REALM_CONS_NAME = "consumer"; - - public static final String IDP_OIDC_ALIAS = "kc-oidc-idp"; - public static final String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; - - public static final String IDP_SAML_ALIAS = "kc-saml-idp"; - public static final String IDP_SAML_PROVIDER_ID = "saml"; - - public static final String CLIENT_ID = "brokerapp"; - public static final String CLIENT_SECRET = "secret"; - public static final String VAULT_CLIENT_SECRET = "${vault.oidc_idp}"; - - public static final String USER_LOGIN = "testuser"; - public static final String USER_EMAIL = "user@localhost.com"; - public static final String USER_PASSWORD = "password"; - - public static final String IDP_SAML_SIGN_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygc" + - "DfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb4" + - "0tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SV" + - "Er55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3" + - "og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8" + - "ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKp" + - "r/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus" + - "7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiC" + - "KpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9" + - "BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzU" + - "alm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58c" + - "kAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZ" + - "V9wN85V0xZXWsw=="; - - public static final String IDP_SAML_SIGN_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG" + - "9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDV" + - "QQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDg" + - "YDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4" + - "GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQ" + - "MA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93b" + - "jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKr" + - "GTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfm" + - "m2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1F" + - "Vqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNG" + - "txwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBr" + - "cAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFl" + - "cNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD" + - "0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8" + - "C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW" + - "7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8Uei" + - "KAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKm" + - "CV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"; - - public static final String REALM_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwg" + - "gSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eA" + - "guHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3Iyw" + - "JYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK" + - "218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWw" + - "Y2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58S" + - "MDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfY" + - "P/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmq" + - "eFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1y" + - "ltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ38152" + - "7ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruV" + - "XXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh" + - "7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0l" + - "LcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaa" + - "PnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv" + - "2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt" + - "7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2I" + - "Y+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUA" + - "YqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQC" + - "ahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDs" + - "uqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIO" + - "PO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBP" + - "spPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GF" + - "Cp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn"; - - public static final String REALM_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgK" + - "CAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF0" + - "0U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlE" + - "Gq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmR" + - "bX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHO" + - "yyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB"; -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java b/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java deleted file mode 100644 index 4b4495ef6ecb..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/BrokerTestTools.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.keycloak.tests.broker; - -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; - -import org.apache.http.client.utils.URIBuilder; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedCondition; -import org.openqa.selenium.support.ui.WebDriverWait; - -import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; - -public final class BrokerTestTools { - - private static final ThreadLocal SERVER_ROOT = new ThreadLocal<>(); - - private BrokerTestTools() { - } - - public static void setServerRoot(String baseUrl) { - SERVER_ROOT.set(baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl); - } - - public static void clearServerRoot() { - SERVER_ROOT.remove(); - } - - public static String getProviderRoot() { - String host2 = System.getProperty("auth.server.host2"); - if (host2 == null || host2.isBlank()) { - return getConsumerRoot(); - } - return new URIBuilder(URI.create(getConsumerRoot())) - .setHost(host2) - .toString(); - } - - public static String getConsumerRoot() { - String serverRoot = SERVER_ROOT.get(); - if (serverRoot != null && !serverRoot.isBlank()) { - return serverRoot; - } - throw new IllegalStateException("BrokerTestTools server root not initialized"); - } - - public static String getAuthPath() { - String path = URI.create(getConsumerRoot()).getPath(); - if (path == null || path.isBlank() || "/".equals(path)) { - return ""; - } - return path.endsWith("/") ? path.substring(0, path.length() - 1) : path; - } - - public static String encodeUrl(String url) { - return URLEncoder.encode(url, StandardCharsets.UTF_8); - } - - public static IdentityProviderRepresentation createIdentityProvider(String alias, String providerId) { - IdentityProviderRepresentation idp = new IdentityProviderRepresentation(); - idp.setAlias(alias); - idp.setProviderId(providerId); - idp.setEnabled(true); - idp.setTrustEmail(true); - idp.setStoreToken(false); - idp.setAddReadTokenRoleOnCreate(false); - idp.setConfig(new HashMap<>()); - return idp; - } - - public static void waitForPage(final WebDriver driver, final String title, final boolean isHtmlTitle) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); - ExpectedCondition condition = new ExpectedCondition() { - private String actualTitle = null; - - public Boolean apply(final WebDriver input) { - if (input == null) { - return false; - } - actualTitle = isHtmlTitle ? input.getTitle() : input.findElement(By.id("kc-page-title")).getText(); - return actualTitle != null && actualTitle.toLowerCase().contains(title.toLowerCase()); - } - - public String toString() { - return "page title to contain '" + title + "'. Actual title: '" + actualTitle + "'"; - } - }; - wait.until(condition); - } - - public static void waitForPage(final ManagedWebDriver driver, final String title, final boolean isHtmlTitle) { - waitForPage(driver.driver(), title, isHtmlTitle); - } - - public static void waitForElementEnabled(WebDriver driver, final String elementName) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); - ExpectedCondition condition = (WebDriver input) -> { - List elements = input.findElements(By.name(elementName)); - return (!elements.isEmpty()) && elements.get(0).isEnabled(); - }; - wait.until(condition); - } - - public static void waitForElementEnabled(ManagedWebDriver driver, final String elementName) { - waitForElementEnabled(driver.driver(), elementName); - } - - public static ClientRepresentation findClientByClientId(List clients, String clientId) { - return clients.stream() - .filter(client -> clientId.equals(client.getClientId())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Client not found: " + clientId)); - } - - public static IdentityProviderRepresentation findIdentityProvider(List providers, String alias) { - return providers.stream() - .filter(provider -> alias.equals(provider.getAlias())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Identity provider not found: " + alias)); - } - - public static boolean isOidcProvider(IdentityProviderRepresentation provider) { - return IDP_OIDC_PROVIDER_ID.equals(provider.getProviderId()); - } - - public static Map config(IdentityProviderRepresentation provider) { - return provider.getConfig(); - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java deleted file mode 100644 index 820a7357b719..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcBrokerConfiguration.java +++ /dev/null @@ -1,299 +0,0 @@ -package org.keycloak.tests.broker; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.IdentityProviderSyncMode; -import org.keycloak.protocol.ProtocolMapperUtils; -import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper; -import org.keycloak.protocol.oidc.mappers.HardcodedClaim; -import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; -import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; -import org.keycloak.protocol.oidc.mappers.UserPropertyMapper; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.representations.idm.ProtocolMapperRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; - -import static org.keycloak.broker.oidc.OAuth2IdentityProviderConfig.TOKEN_ENDPOINT_URL; -import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_ID; -import static org.keycloak.tests.broker.BrokerTestConstants.CLIENT_SECRET; -import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_ALIAS; -import static org.keycloak.tests.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; -import static org.keycloak.tests.broker.BrokerTestConstants.REALM_CONS_NAME; -import static org.keycloak.tests.broker.BrokerTestConstants.REALM_PROV_NAME; -import static org.keycloak.tests.broker.BrokerTestConstants.USER_EMAIL; -import static org.keycloak.tests.broker.BrokerTestConstants.USER_LOGIN; -import static org.keycloak.tests.broker.BrokerTestConstants.USER_PASSWORD; -import static org.keycloak.tests.broker.BrokerTestTools.createIdentityProvider; -import static org.keycloak.tests.broker.BrokerTestTools.getAuthPath; -import static org.keycloak.tests.broker.BrokerTestTools.getConsumerRoot; -import static org.keycloak.tests.broker.BrokerTestTools.getProviderRoot; - -/** - * @author hmlnarik - */ -public class KcOidcBrokerConfiguration implements BrokerConfiguration { - - public static final KcOidcBrokerConfiguration INSTANCE = new KcOidcBrokerConfiguration(); - - protected static final String ATTRIBUTE_TO_MAP_NAME = "user-attribute"; - protected static final String ATTRIBUTE_TO_MAP_NAME_2 = "user-attribute-2"; - public static final String USER_INFO_CLAIM = "user-claim"; - public static final String HARDOCDED_CLAIM = "test"; - public static final String HARDOCDED_VALUE = "value"; - public static final String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app"; - public static final String CONSUMER_BROKER_APP_SECRET = "broker-app-secret"; - - @Override - public RealmRepresentation createProviderRealm() { - RealmRepresentation realm = new RealmRepresentation(); - realm.setRealm(REALM_PROV_NAME); - realm.setEnabled(true); - realm.setEventsListeners(Collections.singletonList("jboss-logging")); - realm.setEventsEnabled(true); - realm.setInternationalizationEnabled(true); - realm.setSupportedLocales(Set.of("en", "hu")); - - return realm; - } - - @Override - public RealmRepresentation createConsumerRealm() { - RealmRepresentation realm = new RealmRepresentation(); - realm.setRealm(REALM_CONS_NAME); - realm.setEnabled(true); - realm.setResetPasswordAllowed(true); - realm.setEventsListeners(Collections.singletonList("jboss-logging")); - realm.setEventsEnabled(true); - realm.setInternationalizationEnabled(true); - realm.setSupportedLocales(Set.of("en", "hu")); - - return realm; - } - - @Override - public List createProviderClients() { - ClientRepresentation client = new ClientRepresentation(); - client.setClientId(getIDPClientIdInProviderRealm()); - client.setName(CLIENT_ID); - client.setSecret(CLIENT_SECRET); - - client.setRedirectUris(Collections.singletonList(getConsumerRoot() + - getAuthPath() + "/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint/*")); - - client.setAdminUrl(getConsumerRoot() + - getAuthPath() + "/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint"); - - OIDCAdvancedConfigWrapper oidcClient = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); - oidcClient.setPostLogoutRedirectUris(Collections.singletonList("+")); - - oidcClient.setBackchannelLogoutUrl(getConsumerRoot() + - getAuthPath() + "/realms/" + consumerRealmName() + "/protocol/openid-connect/logout/backchannel-logout"); - - ProtocolMapperRepresentation emailMapper = new ProtocolMapperRepresentation(); - emailMapper.setName("email"); - emailMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - emailMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID); - - Map emailMapperConfig = new HashMap<>(); - emailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "email"); - emailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "email"); - emailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); - emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - emailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); - emailMapper.setConfig(emailMapperConfig); - - ProtocolMapperRepresentation nestedAttrMapper = new ProtocolMapperRepresentation(); - nestedAttrMapper.setName("attribute - nested claim"); - nestedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - nestedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - - Map nestedEmailMapperConfig = new HashMap<>(); - nestedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "nested.email"); - nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "nested.email"); - nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); - nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - nestedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); - nestedAttrMapper.setConfig(nestedEmailMapperConfig); - - ProtocolMapperRepresentation dottedAttrMapper = new ProtocolMapperRepresentation(); - dottedAttrMapper.setName("attribute - claim with dot in name"); - dottedAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - dottedAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - - Map dottedEmailMapperConfig = new HashMap<>(); - dottedEmailMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, "dotted.email"); - dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "dotted\\.email"); - dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); - dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - dottedEmailMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); - dottedAttrMapper.setConfig(dottedEmailMapperConfig); - - ProtocolMapperRepresentation userAttrMapper = new ProtocolMapperRepresentation(); - userAttrMapper.setName("attribute - name"); - userAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - - Map userAttrMapperConfig = new HashMap<>(); - userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME); - userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME); - userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); - userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); - userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true"); - userAttrMapper.setConfig(userAttrMapperConfig); - - ProtocolMapperRepresentation userAttrMapper2 = new ProtocolMapperRepresentation(); - userAttrMapper2.setName("attribute - name - 2"); - userAttrMapper2.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - userAttrMapper2.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - - Map userAttrMapperConfig2 = new HashMap<>(); - userAttrMapperConfig2.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME_2); - userAttrMapperConfig2.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME_2); - userAttrMapperConfig2.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); - userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); - userAttrMapperConfig2.put(ProtocolMapperUtils.MULTIVALUED, "true"); - userAttrMapper2.setConfig(userAttrMapperConfig2); - - ProtocolMapperRepresentation hardcodedJsonClaim = new ProtocolMapperRepresentation(); - hardcodedJsonClaim.setName("json-mapper"); - hardcodedJsonClaim.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - hardcodedJsonClaim.setProtocolMapper(HardcodedClaim.PROVIDER_ID); - - Map hardcodedJsonClaimMapperConfig = new HashMap<>(); - hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, KcOidcBrokerConfiguration.USER_INFO_CLAIM); - hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, "JSON"); - hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - hardcodedJsonClaimMapperConfig.put(HardcodedClaim.CLAIM_VALUE, "{\"" + HARDOCDED_CLAIM + "\": \"" + HARDOCDED_VALUE + "\"}"); - hardcodedJsonClaim.setConfig(hardcodedJsonClaimMapperConfig); - - ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation(); - audienceMapper.setName("audience-brokerapp"); - audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); - - Map audienceMapperConfig = new HashMap<>(); - audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CLIENT_ID); - audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - audienceMapper.setConfig(audienceMapperConfig); - - client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper, hardcodedJsonClaim, audienceMapper)); - - return Collections.singletonList(client); - } - - @Override - public List createConsumerClients() { - ClientRepresentation client = new ClientRepresentation(); - client.setClientId(CONSUMER_BROKER_APP_CLIENT_ID); - client.setName("broker-app"); - client.setSecret(CONSUMER_BROKER_APP_SECRET); - client.setEnabled(true); - client.setDirectAccessGrantsEnabled(true); - - client.setRedirectUris(Collections.singletonList(getConsumerRoot() + - getAuthPath() + "/*")); - - client.setBaseUrl(getConsumerRoot() + - getAuthPath() + "/realms/" + REALM_CONS_NAME + "/app"); - - OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+")); - OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseRefreshTokenForClientCredentialsGrant(true); - - ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation(); - audienceMapper.setName("audience-broker-app"); - audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID); - - Map audienceMapperConfig = new HashMap<>(); - audienceMapperConfig.put(AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, CONSUMER_BROKER_APP_CLIENT_ID); - audienceMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - audienceMapper.setConfig(audienceMapperConfig); - - client.setProtocolMappers(Collections.singletonList(audienceMapper)); - - return Collections.singletonList(client); - } - - @Override - public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) { - IdentityProviderRepresentation idp = createIdentityProvider(getIDPAlias(), IDP_OIDC_PROVIDER_ID); - - Map config = idp.getConfig(); - applyDefaultConfiguration(config, syncMode); - - return idp; - } - - protected void applyDefaultConfiguration(final Map config, IdentityProviderSyncMode syncMode) { - config.put(IdentityProviderModel.SYNC_MODE, syncMode.toString()); - config.put("clientId", CLIENT_ID); - config.put("clientSecret", CLIENT_SECRET); - config.put("prompt", "login"); - config.put("loginHint", "true"); - config.put(OIDCIdentityProviderConfig.ISSUER, getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME); - config.put("authorizationUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/auth"); - config.put(TOKEN_ENDPOINT_URL, getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/token"); - config.put("logoutUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/logout"); - config.put("userInfoUrl", getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo"); - config.put("defaultScope", "email profile"); - config.put("backchannelSupported", "true"); - config.put(OIDCIdentityProviderConfig.JWKS_URL, - getProviderRoot() + getAuthPath() + "/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/certs"); - config.put(OIDCIdentityProviderConfig.USE_JWKS_URL, "true"); - config.put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, "true"); - } - - @Override - public String getUserLogin() { - return USER_LOGIN; - } - - @Override - public String getIDPClientIdInProviderRealm() { - return CLIENT_ID; - } - - @Override - public String getUserPassword() { - return USER_PASSWORD; - } - - @Override - public String getUserEmail() { - return USER_EMAIL; - } - - @Override - public String providerRealmName() { - return REALM_PROV_NAME; - } - - @Override - public String consumerRealmName() { - return REALM_CONS_NAME; - } - - @Override - public String getIDPAlias() { - return IDP_OIDC_ALIAS; - } - -} diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java index 4eb717569e0d..ff1753fe135a 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/KcOidcFirstBrokerLoginDetectExistingUserTest.java @@ -1,5 +1,7 @@ package org.keycloak.tests.broker; +import jakarta.ws.rs.core.Response; + import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.RealmResource; @@ -14,52 +16,63 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.realm.AuthenticationExecutionBuilder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.UserBuilder; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.page.ErrorPage; +import org.keycloak.testframework.util.ApiUtil; import org.keycloak.testsuite.util.AccountHelper; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@KeycloakIntegrationTest(config = BrokerServerConfig.class) -public class KcOidcFirstBrokerLoginDetectExistingUserTest extends AbstractInitializedBaseBrokerTest { +@KeycloakIntegrationTest +public class KcOidcFirstBrokerLoginDetectExistingUserTest extends AbstractKcOidcBrokerTest { - @Override - protected BrokerConfiguration getBrokerConfiguration() { - return new KcOidcBrokerConfiguration(); - } + @InjectPage + ErrorPage errorPage; - @Override @BeforeEach - public void beforeBrokerTest() { - super.beforeBrokerTest(); - log.debug("creating detect existing user flow for realm " + bc.providerRealmName()); - - final RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); - AuthenticationManagementResource authMgmtResource = consumerRealm.flows(); + void setupDetectExistingUserFlow() { + RealmResource consumer = consumerRealm.admin(); + AuthenticationManagementResource authMgmtResource = consumer.flows(); String detectExistingFlowAlias = "detectExistingUserFlow"; - final AuthenticationFlowRepresentation authenticationFlowRepresentation = - newFlow(detectExistingFlowAlias, detectExistingFlowAlias, "basic-flow", true, false); - authMgmtResource.createFlow(authenticationFlowRepresentation); - - AuthenticationFlowRepresentation authenticationFlowRepresentation1 = getFlow(authMgmtResource, detectExistingFlowAlias); - assertNotNull(authenticationFlowRepresentation1, "The authentication flow must exist"); - - String flowId = authenticationFlowRepresentation1.getId(); + AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation(); + flow.setAlias(detectExistingFlowAlias); + flow.setDescription(detectExistingFlowAlias); + flow.setProviderId("basic-flow"); + flow.setTopLevel(true); + flow.setBuiltIn(false); + authMgmtResource.createFlow(flow); + + AuthenticationFlowRepresentation created = authMgmtResource.getFlows().stream() + .filter(v -> detectExistingFlowAlias.equals(v.getAlias())) + .findFirst() + .orElse(null); + assertNotNull(created, "The authentication flow must exist"); + String flowId = created.getId(); addExecution(authMgmtResource, flowId, IdpDetectExistingBrokerUserAuthenticatorFactory.PROVIDER_ID, 10); addExecution(authMgmtResource, flowId, IdpAutoLinkAuthenticatorFactory.PROVIDER_ID, 20); - IdentityProviderResource identityConsumerResource = consumerRealm.identityProviders().get(bc.getIDPAlias()); - IdentityProviderRepresentation identityProviderRepresentation = consumerRealm.identityProviders().findAll().get(0); + IdentityProviderResource identityConsumerResource = consumer.identityProviders().get(IDP_OIDC_ALIAS); + IdentityProviderRepresentation identityProviderRepresentation = consumer.identityProviders().findAll().get(0); identityProviderRepresentation.setFirstBrokerLoginFlowAlias(detectExistingFlowAlias); identityProviderRepresentation.getConfig().put(IdentityProviderModel.SYNC_MODE, IdentityProviderSyncMode.FORCE.toString()); identityConsumerResource.update(identityProviderRepresentation); - assertEquals(2, getFlow(authMgmtResource, detectExistingFlowAlias).getAuthenticationExecutions().size(), + assertEquals(2, authMgmtResource.getFlows().stream() + .filter(v -> detectExistingFlowAlias.equals(v.getAlias())) + .findFirst() + .orElseThrow() + .getAuthenticationExecutions() + .size(), "Two executions must have been created"); } @@ -74,59 +87,75 @@ private void addExecution(AuthenticationManagementResource authMgmtResource, Str authMgmtResource.addExecution(exec); } - private AuthenticationFlowRepresentation getFlow(AuthenticationManagementResource authMgmtResource, String detectExistingFlowAlias) { - return authMgmtResource.getFlows().stream() - .filter(v -> detectExistingFlowAlias.equals(v.getAlias())) - .findFirst().get(); + private void createUser(ManagedRealm realm, String username, String password, String firstName, String lastName, String email) { + UserBuilder builder = UserBuilder.create(username) + .email(email) + .password(password) + .enabled(true); + if (firstName != null) { + builder.firstName(firstName); + } + if (lastName != null) { + builder.lastName(lastName); + } + try (Response response = realm.admin().users().create(builder.build())) { + assertEquals(201, response.getStatus()); + String userId = ApiUtil.getCreatedId(response); + realm.cleanup().add(r -> r.users().get(userId).remove()); + } } - private AuthenticationFlowRepresentation newFlow(String alias, String description, - String providerId, boolean topLevel, boolean builtIn) { - AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation(); - flow.setAlias(alias); - flow.setDescription(description); - flow.setProviderId(providerId); - flow.setTopLevel(topLevel); - flow.setBuiltIn(builtIn); - return flow; + private void logInWithIdp(String username, String password) { + oauth.openLoginForm(); + logInWithBroker(); + loginPage.fillLogin(username, password); + loginPage.submit(); + } + + // Default broker login scenarios do not apply with the custom detect-existing first-broker-login flow. + @Override + @Test + @Disabled("Uses a custom detect-existing first-broker-login flow") + public void testLogInAsUserInIDP() { + } + + @Override + @Test + @Disabled("Uses a custom detect-existing first-broker-login flow") + public void testLoginWithExistingUser() { } @Test public void loginWhenUserDoesNotExistOnConsumer() { - updateExecutions(BrokerFirstLoginFlowMutators::disableUpdateProfileOnFirstLogin); + disableUpdateProfileOnFirstLogin(); String firstname = "Firstname"; String lastname = "Lastname"; String username = "firstandlastname"; - createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, "firstnamelastname@example.org"); + createUser(providerRealm, username, USER_PASSWORD, firstname, lastname, "firstnamelastname@example.org"); - openConsumerBrokerLoginForm(); - - logInWithIdp(bc.getIDPAlias(), username, BrokerTestConstants.USER_PASSWORD); + logInWithIdp(username, USER_PASSWORD); errorPage.assertCurrent(); - assertEquals("User " + username + " authenticated with identity provider " + bc.getIDPAlias() + assertEquals("User " + username + " authenticated with identity provider " + IDP_OIDC_ALIAS + " does not exist. Please contact your administrator.", errorPage.getError()); } @Test public void loginWhenUserExistsOnConsumer() { - updateExecutions(BrokerFirstLoginFlowMutators::disableUpdateProfileOnFirstLogin); + disableUpdateProfileOnFirstLogin(); final String firstname = "Firstname_loginWhenUserExistsOnConsumer"; final String lastname = "Lastname_loginWhenUserExistsOnConsumer"; final String username = "firstandlastname"; final String email = "firstnamelastname@example.org"; - createUser(bc.providerRealmName(), username, BrokerTestConstants.USER_PASSWORD, firstname, lastname, email); - createUser(bc.consumerRealmName(), username, "THIS PASSWORD IS USELESS", null, null, email); - - openConsumerBrokerLoginForm(); + createUser(providerRealm, username, USER_PASSWORD, firstname, lastname, email); + createUser(consumerRealm, username, "THIS PASSWORD IS USELESS", null, null, email); - logInWithIdp(bc.getIDPAlias(), username, BrokerTestConstants.USER_PASSWORD); + logInWithIdp(username, USER_PASSWORD); assertTrue(oauth.parseLoginResponse().isSuccess(), "Broker login should complete successfully"); - UserRepresentation userRepresentation = AccountHelper.getUserRepresentation( - adminClient.realm(bc.consumerRealmName()), username); + UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(consumerRealm.admin(), username); assertEquals(email, userRepresentation.getEmail(), "Email is not correct"); assertEquals(firstname, userRepresentation.getFirstName(), "Firstname is not correct");