KEYCLOAK-5032 Forward request parameters to another IdP
Forwarding of prompt and acr_values, if provided in the authorization request. If prompt is set in the configuration for the identity provider, the configuration overrules the request parameter.
This commit is contained in:
parent
ab8f4720d6
commit
50dd07217d
11 changed files with 230 additions and 14 deletions
|
@ -79,6 +79,7 @@ public interface OAuth2Constants {
|
||||||
String UI_LOCALES_PARAM = "ui_locales";
|
String UI_LOCALES_PARAM = "ui_locales";
|
||||||
|
|
||||||
String PROMPT = "prompt";
|
String PROMPT = "prompt";
|
||||||
|
String ACR_VALUES = "acr_values";
|
||||||
|
|
||||||
String MAX_AGE = "max_age";
|
String MAX_AGE = "max_age";
|
||||||
|
|
||||||
|
|
|
@ -301,6 +301,19 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
if (getConfig().isLoginHint() && loginHint != null) {
|
if (getConfig().isLoginHint() && loginHint != null) {
|
||||||
uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
|
uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String prompt = getConfig().getPrompt();
|
||||||
|
if (prompt == null || prompt.isEmpty()) {
|
||||||
|
prompt = request.getAuthenticationSession().getClientNote(OAuth2Constants.PROMPT);
|
||||||
|
}
|
||||||
|
if (prompt != null) {
|
||||||
|
uriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
String acr = request.getAuthenticationSession().getClientNote(OAuth2Constants.ACR_VALUES);
|
||||||
|
if (acr != null) {
|
||||||
|
uriBuilder.queryParam(OAuth2Constants.ACR_VALUES, acr);
|
||||||
|
}
|
||||||
return uriBuilder;
|
return uriBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,4 +82,8 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
|
||||||
public void setLoginHint(boolean loginHint) {
|
public void setLoginHint(boolean loginHint) {
|
||||||
getConfig().put("loginHint", String.valueOf(loginHint));
|
getConfig().put("loginHint", String.valueOf(loginHint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPrompt() {
|
||||||
|
return getConfig().get("prompt");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -71,7 +71,6 @@ import java.security.PublicKey;
|
||||||
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> implements ExchangeExternalToken {
|
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> implements ExchangeExternalToken {
|
||||||
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
|
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
|
||||||
|
|
||||||
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
|
|
||||||
public static final String SCOPE_OPENID = "openid";
|
public static final String SCOPE_OPENID = "openid";
|
||||||
public static final String FEDERATED_ID_TOKEN = "FEDERATED_ID_TOKEN";
|
public static final String FEDERATED_ID_TOKEN = "FEDERATED_ID_TOKEN";
|
||||||
public static final String USER_INFO = "UserInfo";
|
public static final String USER_INFO = "UserInfo";
|
||||||
|
@ -212,18 +211,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
|
||||||
UriBuilder authorizationUrl = super.createAuthorizationUrl(request);
|
|
||||||
String prompt = getConfig().getPrompt();
|
|
||||||
|
|
||||||
if (prompt != null && !prompt.isEmpty()) {
|
|
||||||
authorizationUrl.queryParam(OAUTH2_PARAMETER_PROMPT, prompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorizationUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processAccessTokenResponse(BrokeredIdentityContext context, AccessTokenResponse response) {
|
protected void processAccessTokenResponse(BrokeredIdentityContext context, AccessTokenResponse response) {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
public static final String REQUEST_URI_PARAM = "request_uri";
|
public static final String REQUEST_URI_PARAM = "request_uri";
|
||||||
public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
|
public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
|
||||||
public static final String CLAIMS_PARAM = "claims";
|
public static final String CLAIMS_PARAM = "claims";
|
||||||
|
public static final String ACR_PARAM = "acr_values";
|
||||||
|
|
||||||
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
|
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
|
||||||
public static final String ISSUER = "iss";
|
public static final String ISSUER = "iss";
|
||||||
|
|
|
@ -434,6 +434,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
|
if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
|
||||||
if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
|
if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
|
||||||
if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims());
|
if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims());
|
||||||
|
if (request.getAcr() != null) authenticationSession.setClientNote(OIDCLoginProtocol.ACR_PARAM, request.getAcr());
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4
|
// https://tools.ietf.org/html/rfc7636#section-4
|
||||||
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
|
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
|
||||||
|
|
|
@ -43,6 +43,12 @@ public class AuthorizationEndpointRequest {
|
||||||
String codeChallenge;
|
String codeChallenge;
|
||||||
String codeChallengeMethod;
|
String codeChallengeMethod;
|
||||||
|
|
||||||
|
String acr;
|
||||||
|
|
||||||
|
public String getAcr() {
|
||||||
|
return acr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
return clientId;
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ abstract class AuthzEndpointRequestParser {
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLAIMS_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.ACR_PARAM);
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
|
||||||
|
@ -89,6 +90,7 @@ abstract class AuthzEndpointRequestParser {
|
||||||
request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
|
request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
|
||||||
request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
|
request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
|
||||||
request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM));
|
request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM));
|
||||||
|
request.acr = replaceIfNotNull(request.acr, getParameter(OIDCLoginProtocol.ACR_PARAM));
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||||
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
|
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
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.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.Assert;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
public class KcOidcBrokerAcrParameterTest extends AbstractBrokerTest {
|
||||||
|
|
||||||
|
private static final String ACR_VALUES = "acr_values";
|
||||||
|
private static final String ACR_3 = "3";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return KcOidcBrokerConfiguration.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loginUser() {
|
||||||
|
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
|
||||||
|
|
||||||
|
driver.navigate().to(driver.getCurrentUrl() + "&" + ACR_VALUES + "=" + ACR_3);
|
||||||
|
|
||||||
|
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(ACR_VALUES + "=" + ACR_3 + " should be part of the url",
|
||||||
|
driver.getCurrentUrl().contains(ACR_VALUES + "=" + ACR_3));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.keycloak.admin.client.resource.UsersResource;
|
||||||
|
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.Assert;
|
||||||
|
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
public class KcOidcBrokerPromptParameterTest extends AbstractBrokerTest {
|
||||||
|
|
||||||
|
private static final String PROMPT_CONSENT = "consent";
|
||||||
|
private static final String PROMPT_LOGIN = "login";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return new KcOidcBrokerConfiguration2();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loginUser() {
|
||||||
|
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
|
||||||
|
|
||||||
|
driver.navigate().to(driver.getCurrentUrl() + "&" + OIDCLoginProtocol.PROMPT_PARAM + "=" + PROMPT_CONSENT);
|
||||||
|
|
||||||
|
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.assertFalse(OIDCLoginProtocol.PROMPT_PARAM + "=" + PROMPT_LOGIN + " should not be part of the url",
|
||||||
|
driver.getCurrentUrl().contains(OIDCLoginProtocol.PROMPT_PARAM + "=" + PROMPT_LOGIN));
|
||||||
|
|
||||||
|
Assert.assertTrue(OIDCLoginProtocol.PROMPT_PARAM + "=" + PROMPT_CONSENT + " should be part of the url",
|
||||||
|
driver.getCurrentUrl().contains(OIDCLoginProtocol.PROMPT_PARAM + "=" + PROMPT_CONSENT));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KcOidcBrokerConfiguration2 extends KcOidcBrokerConfiguration {
|
||||||
|
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config) {
|
||||||
|
super.applyDefaultConfiguration(suiteContext, config);
|
||||||
|
config.remove("prompt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -558,7 +558,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
Retry.execute(new Runnable() {
|
Retry.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-enc/");
|
assertEquals(APP_SERVER_BASE_URL + "/sales-post-enc/", driver.getCurrentUrl());
|
||||||
}
|
}
|
||||||
}, 10, 100);
|
}, 10, 100);
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
|
|
Loading…
Reference in a new issue