KEYCLOAK-7201 OIDC Identity Brokering with Client parameter forward

Forward "custom" (non-standard) query parameters to external IDP
This commit is contained in:
Takashi Mogi 2018-06-01 14:39:03 +09:00 committed by Stian Thorgersen
parent 40cc826586
commit 959e7b1b01
5 changed files with 128 additions and 1 deletions

View file

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.broker.oidc.OIDCIdentityProvider.OIDCEndpoint;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
@ -42,6 +43,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.messages.Messages;
@ -59,6 +61,9 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -327,6 +332,15 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
if (acr != null) {
uriBuilder.queryParam(OAuth2Constants.ACR_VALUES, acr);
}
String forwardParameterConfig = getConfig().getForwardParameters() != null ? getConfig().getForwardParameters(): "";
List<String> forwardParameters = Arrays.asList(forwardParameterConfig.split("\\s*,\\s*"));
for(String forwardParameter: forwardParameters) {
String name = AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + forwardParameter.trim();
String parameter = request.getAuthenticationSession().getClientNote(name);
if(parameter != null && !parameter.isEmpty()) {
uriBuilder.queryParam(forwardParameter, parameter);
}
}
return uriBuilder;
}

View file

@ -86,4 +86,12 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public String getPrompt() {
return getConfig().get("prompt");
}
public String getForwardParameters() {
return getConfig().get("forwardParameters");
}
public void setForwardParameters(String forwardParameters) {
getConfig().put("forwardParameters", forwardParameters);
}
}

View file

@ -0,0 +1,97 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
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.BrokerTestTools.createIdentityProvider;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import java.util.List;
import java.util.Map;
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 KcOidcBrokerParameterForwardTest extends KcOidcBrokerTest {
private static final String FORWARDED_PARAMETER = "forwarded_parameter";
private static final String FORWARDED_PARAMETER_VALUE = "forwarded_value";
private static final String PARAMETER_NOT_SET = "parameter_not_set";
private static final String PARAMETER_NOT_FORWARDED = "parameter_not_forwarded";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfigurationWithParameterForward();
}
private class KcOidcBrokerConfigurationWithParameterForward 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("forwardParameters", FORWARDED_PARAMETER +", " + PARAMETER_NOT_SET);
return idp;
}
}
@Override
protected void loginUser() {
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
String queryString = "&" + FORWARDED_PARAMETER + "=" + FORWARDED_PARAMETER_VALUE + "&" + PARAMETER_NOT_FORWARDED + "=" + "value";
driver.navigate().to(driver.getCurrentUrl() + queryString);
log.debug("Clicking social " + bc.getIDPAlias());
accountLoginPage.clickSocial(bc.getIDPAlias());
waitForPage(driver, "log in to", true);
Assert.assertThat("Driver should be on the provider realm page right now",
driver.getCurrentUrl(), containsString("/auth/realms/" + bc.providerRealmName() + "/"));
Assert.assertThat(FORWARDED_PARAMETER + "=" + FORWARDED_PARAMETER_VALUE + " should be part of the url",
driver.getCurrentUrl(), containsString(FORWARDED_PARAMETER + "=" + FORWARDED_PARAMETER_VALUE));
Assert.assertThat("\"" + PARAMETER_NOT_SET + "\"" + " should NOT be part of the url",
driver.getCurrentUrl(), not(containsString(PARAMETER_NOT_SET)));
Assert.assertThat("\"" + PARAMETER_NOT_FORWARDED +"\"" + " should be NOT part of the url",
driver.getCurrentUrl(), not(containsString(PARAMETER_NOT_FORWARDED)));
accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword());
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertThat("We must be on correct realm right now",
driver.getCurrentUrl(), containsString("/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);
}
}

View file

@ -572,6 +572,8 @@ validating-public-key-id=Validating Public Key Id
identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave blank if the key above should be used always, regardless of key ID specified by external IDP; set it if the key should only be used for verifying if key ID from external IDP matches.
allowed-clock-skew=Allowed clock skew
identity-provider.allowed-clock-skew.tooltip=Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero.
forwarded-query-parameters=Forwarded Query Parameters
identity-provider.forwarded-query-parameters.tooltip=Non OpenID Connect/OAuth standard query parameters to be forwarded to external IDP from the initial application request to Authorization Endpoint. Multiple parameters can be entered, separated by comma (,).
import-external-idp-config=Import External IDP Config
import-external-idp-config.tooltip=Allows you to load external IDP metadata from a config file or to download it from a URL.
import-from-url=Import from URL

View file

@ -253,7 +253,13 @@
</div>
<kc-tooltip>{{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="forwardParameters">{{:: 'forwarded-query-parameters' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="forwardParameters" type="text" ng-model="identityProvider.config.forwardParameters"/>
</div>
<kc-tooltip>{{:: 'identity-provider.forwarded-query-parameters.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset data-ng-show="newIdentityProvider">
<legend uncollapsed><span class="text">{{:: 'import-external-idp-config' | translate}}</span> <kc-tooltip>{{:: 'import-external-idp-config.tooltip' | translate}}</kc-tooltip></legend>