keycloak-uncached
Changes
services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java 13(+10 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java 7(+5 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLoginHintTest.java 114(+114 -0)
Details
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 8431493..7c754c2 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<C extends OAuth2IdentityPro
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
- return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
+ final UriBuilder uriBuilder = UriBuilder.fromUri(getConfig().getAuthorizationUrl())
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
.queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncodedState())
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
+
+ String loginHint = request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+ if (getConfig().isLoginHint() && loginHint != null) {
+ uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
+ }
+ return uriBuilder;
}
/**
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
index 12a3745..13dbcdd 100644
--- a/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
@@ -74,4 +74,12 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public void setDefaultScope(String defaultScope) {
getConfig().put("defaultScope", defaultScope);
}
+
+ public boolean isLoginHint() {
+ return Boolean.valueOf(getConfig().get("loginHint"));
+ }
+
+ public void setLoginHint(boolean loginHint) {
+ getConfig().put("loginHint", String.valueOf(loginHint));
+ }
}
\ No newline at end of file
diff --git a/services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java b/services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java
index e706f54..3c88286 100755
--- a/services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java
+++ b/services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java
@@ -24,7 +24,6 @@ import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
import java.io.IOException;
import java.util.HashMap;
@@ -121,10 +120,18 @@ public class AbstractOAuth2IdentityProviderTest {
Assert.assertEquals("458rtf", fi.getId());
}
- private TestProvider getTested() {
+ private TestProvider getTested() {
+ return new TestProvider(getConfig(null, null, null, Boolean.FALSE));
+ }
+
+ private OAuth2IdentityProviderConfig getConfig(final String autorizationUrl, final String defaultScope, final String clientId, final Boolean isLoginHint) {
IdentityProviderModel model = new IdentityProviderModel();
OAuth2IdentityProviderConfig config = new OAuth2IdentityProviderConfig(model);
- return new TestProvider(config);
+ config.setAuthorizationUrl(autorizationUrl);
+ config.setDefaultScope(defaultScope);
+ config.setClientId(clientId);
+ config.setLoginHint(isLoginHint);
+ return config;
}
private static class TestProvider extends AbstractOAuth2IdentityProvider<OAuth2IdentityProviderConfig> {
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 61664a9..2f15ff6 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<String, String> config = idp.getConfig();
+ applyDefaultConfiguration(suiteContext, config);
+ return idp;
+ }
+
+ protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> 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 0000000..49ed362
--- /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<IdentityProviderMapperRepresentation> createIdentityProviderMappers() {
+ IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation();
+ attrMapper1.setName("manager-role-mapper");
+ attrMapper1.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
+ attrMapper1.setConfig(ImmutableMap.<String,String>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.<String,String>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<String, String> 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<UserRepresentation> 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 0000000..d812ea4
--- /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<String, String> 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<UserRepresentation> 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 b857d94..04b7f7a 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 68a35ef..ee815a3 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 @@
</div>
<kc-tooltip>{{:: 'authorization-url.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="loginHint">{{:: 'loginHint' | translate}}</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.config.loginHint" id="loginHint" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'loginHint.tooltip' | translate}}</kc-tooltip>
+ </div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="tokenUrl"><span class="required">*</span> {{:: 'token-url' | translate}}</label>
<div class="col-md-6">