keycloak-uncached

KEYCLOAK-7201 OIDC Identity Brokering with Client parameter

6/1/2018 2:39:03 AM

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 ef4cb85..18e6243 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -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;
     }
 
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 6cc6750..6c84340 100644
--- a/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
@@ -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);
+    }
 }
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerParameterForwardTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerParameterForwardTest.java
new file mode 100644
index 0000000..4b94bf6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerParameterForwardTest.java
@@ -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);
+    }
+
+}
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 4924faa..6f45c53 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
@@ -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
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 ac32db7..825ed15 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
@@ -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>