From 99a457c5c1f36fe518e1162529f59f50ce094981 Mon Sep 17 00:00:00 2001 From: pedroigor Date: Fri, 30 Jan 2015 14:02:53 -0200 Subject: [PATCH] [KEYCLOAK-996] - Allow application to select provider. --- .../keycloak/constants/AdapterConstants.java | 3 + .../adapters/OAuthRequestAuthenticator.java | 9 ++- .../protocol/oidc/OpenIDConnectService.java | 19 ++++- .../broker/AbstractIdentityProviderTest.java | 19 +---- .../testsuite/broker/BrokerKeyCloakRule.java | 45 +++++++++++ .../broker/IdentityProviderHintTest.java | 81 +++++++++++++++++++ 6 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java diff --git a/core/src/main/java/org/keycloak/constants/AdapterConstants.java b/core/src/main/java/org/keycloak/constants/AdapterConstants.java index 976d6cb202..18f21a8881 100755 --- a/core/src/main/java/org/keycloak/constants/AdapterConstants.java +++ b/core/src/main/java/org/keycloak/constants/AdapterConstants.java @@ -29,4 +29,7 @@ public interface AdapterConstants { // Cookie used on adapter side to store token info. Used only when tokenStore is 'COOKIE' public static final String KEYCLOAK_ADAPTER_STATE_COOKIE = "KEYCLOAK_ADAPTER_STATE"; + + // Request parameter used to specify the identifier of the identity provider that should be used to authenticate an user + String K_IDP_HINT = "k_idp_hint"; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index f0f479792e..c49fa2cf45 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -1,6 +1,5 @@ package org.keycloak.adapters; -import org.apache.http.client.utils.URIBuilder; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.RSATokenVerifier; @@ -17,6 +16,8 @@ import java.io.IOException; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; +import static org.keycloak.constants.AdapterConstants.K_IDP_HINT; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -131,6 +132,9 @@ public class OAuthRequestAuthenticator { String loginHint = getQueryParamValue("login_hint"); url = UriUtils.stripQueryParam(url,"login_hint"); + String idpHint = getQueryParamValue(K_IDP_HINT); + url = UriUtils.stripQueryParam(url, K_IDP_HINT); + KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, url) @@ -139,6 +143,9 @@ public class OAuthRequestAuthenticator { if(loginHint != null && loginHint.length() > 0){ redirectUriBuilder.queryParam("login_hint",loginHint); } + if (idpHint != null && idpHint.length() > 0) { + redirectUriBuilder.queryParam(K_IDP_HINT,idpHint); + } return redirectUriBuilder.build().toString(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java index 99e1859136..d6f79dd6ba 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java @@ -76,6 +76,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static org.keycloak.constants.AdapterConstants.K_IDP_HINT; + /** * Resource class for the oauth/openid connect token service * @@ -851,7 +853,8 @@ public class OpenIDConnectService { @QueryParam(OpenIDConnect.SCOPE_PARAM) String scopeParam, @QueryParam(OpenIDConnect.STATE_PARAM) String state, @QueryParam(OpenIDConnect.PROMPT_PARAM) String prompt, - @QueryParam(OpenIDConnect.LOGIN_HINT_PARAM) String loginHint) { + @QueryParam(OpenIDConnect.LOGIN_HINT_PARAM) String loginHint, + @QueryParam(K_IDP_HINT) String idpHint) { event.event(EventType.LOGIN); FrontPageInitializer pageInitializer = new FrontPageInitializer(); pageInitializer.responseType = responseType; @@ -875,6 +878,20 @@ public class OpenIDConnectService { } String accessCode = new ClientSessionCode(realm, clientSession).getCode(); + + if (idpHint != null && !"".equals(idpHint)) { + IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint); + + if (identityProviderModel == null) { + return Flows.forms(session, realm, null, uriInfo) + .setError("Could not find an identity provider with the identifier [" + idpHint + "].") + .createErrorPage(); + } + + return Response.temporaryRedirect( + Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), identityProviderModel, realm, accessCode)).build(); + } + List requiredCredentials = realm.getRequiredCredentials(); if (requiredCredentials.isEmpty()) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index 0367bb7d15..da5d014e06 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -29,12 +29,9 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.representations.IDToken; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginUpdateProfilePage; -import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; import org.openqa.selenium.By; @@ -43,7 +40,6 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import java.io.IOException; -import java.net.URL; import java.util.List; import java.util.Set; @@ -58,20 +54,7 @@ import static org.junit.Assert.assertTrue; public abstract class AbstractIdentityProviderTest { @ClassRule - public static AbstractKeycloakRule brokerServerRule = new AbstractKeycloakRule() { - - @Override - protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { - server.importRealm(getClass().getResourceAsStream("/broker-test/test-realm-with-broker.json")); - URL url = getClass().getResource("/broker-test/test-app-keycloak.json"); - deployApplication("test-app", "/test-app", UserSessionStatusServlet.class, url.getPath(), "manager"); - } - - @Override - protected String[] getTestRealms() { - return new String[] {"realm-with-broker"}; - } - }; + public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule(); @Rule public WebRule webRule = new WebRule(this); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java new file mode 100644 index 0000000000..be9503d08b --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java @@ -0,0 +1,45 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2013 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.broker; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; + +import java.net.URL; + +/** + * @author pedroigor + */ +public class BrokerKeyCloakRule extends AbstractKeycloakRule { + + @Override + protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { + server.importRealm(getClass().getResourceAsStream("/broker-test/test-realm-with-broker.json")); + URL url = getClass().getResource("/broker-test/test-app-keycloak.json"); + deployApplication("test-app", "/test-app", UserSessionStatusServlet.class, url.getPath(), "manager"); + } + + @Override + protected String[] getTestRealms() { + return new String[] {"realm-with-broker"}; + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java new file mode 100755 index 0000000000..b46617eac4 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -0,0 +1,81 @@ +package org.keycloak.testsuite.broker; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.OAuthGrantPage; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.testutils.KeycloakServer; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author pedroigor + */ +public class IdentityProviderHintTest { + + @ClassRule + public static BrokerKeyCloakRule keycloakRule = new BrokerKeyCloakRule(); + + @ClassRule + public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() { + + @Override + protected void configureServer(KeycloakServer server) { + server.getConfig().setPort(8082); + } + + @Override + protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { + server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-kc-oidc.json")); + } + }; + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + private WebDriver driver; + + @WebResource + private LoginPage loginPage; + + @WebResource + private OAuthGrantPage grantPage; + + @Test + public void testSuccessfulRedirect() { + this.driver.navigate().to("http://localhost:8081/test-app?k_idp_hint=kc-oidc-idp"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + + // log in to identity provider + this.loginPage.login("test-user", "password"); + + // grant access to broker-app + this.grantPage.assertCurrent(); + this.grantPage.accept(); + + // authenticated and redirected to app + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + assertTrue(this.driver.getPageSource().contains("idToken")); + } + + @Test + public void testInvalidIdentityProviderHint() { + this.driver.navigate().to("http://localhost:8081/test-app?k_idp_hint=invalid-idp-id"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + + assertEquals("Could not find an identity provider with the identifier [invalid-idp-id].", this.driver.findElement(By.className("instruction")).getText()); + } +}