[KEYCLOAK-4900] Pass login_hint parameter to idp & review (#4421)
This commit is contained in:
1 parent
e173bf33ba
commit
d58c6ad4e0
8 files changed
+239
-6
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -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> {
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+114
@@ -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);
|
||||
}
|
||||
}
|
||||
+85
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+7
@@ -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">
|
||||
|
||||
Reference in new issue
Block a user