From d58c6ad4e03ea3adae52291c4844d5a2ebbdd758 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 25 Aug 2017 10:14:38 +0200 Subject: [PATCH] [KEYCLOAK-4900] Pass login_hint parameter to idp & review (#4421) --- .../oidc/AbstractOAuth2IdentityProvider.java | 9 +- .../oidc/OAuth2IdentityProviderConfig.java | 8 ++ .../AbstractOAuth2IdentityProviderTest.java | 13 +- .../broker/KcOidcBrokerConfiguration.java | 7 +- .../broker/KcOidcBrokerLoginHintTest.java | 114 ++++++++++++++++++ .../broker/KcOidcBrokerNoLoginHintTest.java | 85 +++++++++++++ .../messages/admin-messages_en.properties | 2 + .../realm-identity-provider-oidc.html | 7 ++ 8 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLoginHintTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNoLoginHintTest.java diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 84314930fc..7c754c2b21 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -39,6 +39,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.services.ErrorPage; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; @@ -225,12 +226,18 @@ public abstract class AbstractOAuth2IdentityProvider { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java index 61664a9fc4..2f15ff6842 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java @@ -105,7 +105,12 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration { IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); Map config = idp.getConfig(); + applyDefaultConfiguration(suiteContext, config); + return idp; + } + + protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map config) { config.put("clientId", CLIENT_ID); config.put("clientSecret", CLIENT_SECRET); config.put("prompt", "login"); @@ -115,8 +120,6 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration { config.put("userInfoUrl", getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo"); config.put("defaultScope", "email profile"); config.put("backchannelSupported", "true"); - - return idp; } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLoginHintTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLoginHintTest.java new file mode 100644 index 0000000000..49ed362374 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLoginHintTest.java @@ -0,0 +1,114 @@ +package org.keycloak.testsuite.broker; + +import java.util.List; +import java.util.Map; + +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS; +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; +import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL; +import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider; +import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper; +import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.arquillian.SuiteContext; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +public class KcOidcBrokerLoginHintTest extends AbstractBrokerTest { + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfigurationWithLoginHint(); + } + + @Override + protected String getAccountUrl(String realmName) { + return BrokerTestTools.getAuthRoot(suiteContext) + "/auth/realms/" + realmName + "/account"; + } + + @Override + protected Iterable createIdentityProviderMappers() { + IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation(); + attrMapper1.setName("manager-role-mapper"); + attrMapper1.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); + attrMapper1.setConfig(ImmutableMap.builder() + .put("external.role", "manager") + .put("role", "manager") + .build()); + + IdentityProviderMapperRepresentation attrMapper2 = new IdentityProviderMapperRepresentation(); + attrMapper2.setName("user-role-mapper"); + attrMapper2.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); + attrMapper2.setConfig(ImmutableMap.builder() + .put("external.role", "user") + .put("role", "user") + .build()); + + return Lists.newArrayList(attrMapper1, attrMapper2); + } + + private class KcOidcBrokerConfigurationWithLoginHint extends KcOidcBrokerConfiguration { + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + + Map config = idp.getConfig(); + applyDefaultConfiguration(suiteContext, config); + config.put("loginHint", "true"); + return idp; + } + } + + @Override + protected void loginUser() { + driver.navigate().to(getAccountUrl(bc.consumerRealmName())); + + driver.navigate().to(driver.getCurrentUrl() + "&login_hint=" + USER_EMAIL); + + log.debug("Clicking social " + bc.getIDPAlias()); + accountLoginPage.clickSocial(bc.getIDPAlias()); + + waitForPage(driver, "log in to"); + + Assert.assertTrue("Driver should be on the provider realm page right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/")); + + Assert.assertTrue("User identifiant should be fullfilled", + accountLoginPage.getUsername().equalsIgnoreCase(USER_EMAIL)); + + log.debug("Logging in"); + accountLoginPage.login(bc.getUserPassword()); + + waitForPage(driver, "update account information"); + + updateAccountInformationPage.assertCurrent(); + Assert.assertTrue("We must be on correct realm right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/")); + + log.debug("Updating info on updateAccount page"); + updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname"); + + UsersResource consumerUsers = adminClient.realm(bc.consumerRealmName()).users(); + + int userCount = consumerUsers.count(); + Assert.assertTrue("There must be at least one user", userCount > 0); + + List users = consumerUsers.search("", 0, userCount); + + boolean isUserFound = false; + for (UserRepresentation user : users) { + if (user.getUsername().equals(bc.getUserLogin()) && user.getEmail().equals(bc.getUserEmail())) { + isUserFound = true; + break; + } + } + + Assert.assertTrue("There must be user " + bc.getUserLogin() + " in realm " + bc.consumerRealmName(), + isUserFound); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNoLoginHintTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNoLoginHintTest.java new file mode 100644 index 0000000000..d812ea4f2a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNoLoginHintTest.java @@ -0,0 +1,85 @@ +package org.keycloak.testsuite.broker; + +import java.util.List; +import java.util.Map; + +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS; +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; +import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL; +import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider; +import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; +import org.apache.commons.lang3.StringUtils; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.arquillian.SuiteContext; + +public class KcOidcBrokerNoLoginHintTest extends KcOidcBrokerLoginHintTest { + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfigurationWithNoLoginHint(); + } + + private class KcOidcBrokerConfigurationWithNoLoginHint extends KcOidcBrokerConfiguration { + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + + Map config = idp.getConfig(); + applyDefaultConfiguration(suiteContext, config); + config.put("loginHint", "false"); + return idp; + } + } + + @Override + protected void loginUser() { + driver.navigate().to(getAccountUrl(bc.consumerRealmName())); + + driver.navigate().to(driver.getCurrentUrl() + "&login_hint=" + USER_EMAIL); + + log.debug("Clicking social " + bc.getIDPAlias()); + accountLoginPage.clickSocial(bc.getIDPAlias()); + + waitForPage(driver, "log in to"); + + Assert.assertTrue("Driver should be on the provider realm page right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/")); + + Assert.assertTrue("User identifiant should not be fullfilled", + StringUtils.isBlank(accountLoginPage.getUsername())); + + log.debug("Logging in"); + accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword()); + + waitForPage(driver, "update account information"); + + updateAccountInformationPage.assertCurrent(); + Assert.assertTrue("We must be on correct realm right now", + driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/")); + + log.debug("Updating info on updateAccount page"); + updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname"); + + UsersResource consumerUsers = adminClient.realm(bc.consumerRealmName()).users(); + + int userCount = consumerUsers.count(); + Assert.assertTrue("There must be at least one user", userCount > 0); + + List users = consumerUsers.search("", 0, userCount); + + boolean isUserFound = false; + for (UserRepresentation user : users) { + if (user.getUsername().equals(bc.getUserLogin()) && user.getEmail().equals(bc.getUserEmail())) { + isUserFound = true; + break; + } + } + + Assert.assertTrue("There must be user " + bc.getUserLogin() + " in realm " + bc.consumerRealmName(), + isUserFound); + } +} diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index b857d94d06..04b7f7aa0d 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -504,6 +504,8 @@ authorization-url=Authorization URL authorization-url.tooltip=The Authorization Url. token-url=Token URL token-url.tooltip=The Token URL. +loginHint=Pass login_hint +loginHint.tooltip=Pass login_hint to identity provider. logout-url=Logout URL identity-provider.logout-url.tooltip=End session endpoint to use to logout user from external IDP. backchannel-logout=Backchannel Logout diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 68a35ef8ba..ee815a3b3c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -120,6 +120,13 @@ {{:: 'authorization-url.tooltip' | translate}} +
+ +
+ +
+ {{:: 'loginHint.tooltip' | translate}} +