From 159b37197335cc56fbb2097086e96fc752da9e40 Mon Sep 17 00:00:00 2001 From: awpwb Date: Fri, 1 Jul 2016 13:35:27 +0200 Subject: [PATCH] [KEYCLOAK-1733]: introduce token as query paramter add functional tests for access token as query paramter --- .../adapters/OIDCAuthenticationError.java | 3 +- ...ueryParamterTokenRequestAuthenticator.java | 54 +++++++++ .../adapters/RequestAuthenticator.java | 26 ++++- .../testsuite/adapter/AdapterTest.java | 13 +++ .../adapter/AdapterTestStrategy.java | 103 ++++++++++++++---- .../resources/adapter-test/demorealm.json | 6 + 6 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java index 420ae93606..a58a05a87b 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java @@ -35,7 +35,8 @@ public class OIDCAuthenticationError implements AuthenticationError { CODE_TO_TOKEN_FAILURE, INVALID_TOKEN, STALE_TOKEN, - NO_AUTHORIZATION_HEADER + NO_AUTHORIZATION_HEADER, + NO_QUERY_PARAMETER_ACCESS_TOKEN } private Reason reason; diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java new file mode 100644 index 0000000000..5ee6662c1f --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapters; + +import org.jboss.logging.Logger; +import org.keycloak.adapters.spi.AuthOutcome; +import org.keycloak.adapters.spi.HttpFacade; + +/** + * @author Christian Froehlich + * @version $Revision: 1 $ + */ +public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator { + public static final String ACCESS_TOKEN = "access_token"; + protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class); + + public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) { + super(deployment); + } + + public AuthOutcome authenticate(HttpFacade exchange) { + tokenString = null; + tokenString = getAccessTokenFromQueryParamter(exchange); + if (tokenString == null || tokenString.trim().isEmpty()) { + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null); + return AuthOutcome.NOT_ATTEMPTED; + } + return (authenticateToken(exchange, tokenString)); + } + + String getAccessTokenFromQueryParamter(HttpFacade exchange) { + try { + if (exchange != null && exchange.getRequest() != null) { + return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN); + } + } catch (Exception ignore) { + } + return null; + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java index 2cd1261fc1..c59087c966 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java @@ -61,7 +61,7 @@ public abstract class RequestAuthenticator { if (log.isTraceEnabled()) { log.trace("try bearer"); } - + AuthOutcome outcome = bearer.authenticate(facade); if (outcome == AuthOutcome.FAILED) { challenge = bearer.getChallenge(); @@ -74,12 +74,28 @@ public abstract class RequestAuthenticator { return AuthOutcome.AUTHENTICATED; } + QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator(); + if (log.isTraceEnabled()) { + log.trace("try query paramter auth"); + } + + outcome = queryParamAuth.authenticate(facade); + if (outcome == AuthOutcome.FAILED) { + challenge = queryParamAuth.getChallenge(); + log.debug("QueryParamAuth auth FAILED"); + return AuthOutcome.FAILED; + } else if (outcome == AuthOutcome.AUTHENTICATED) { + log.debug("QueryParamAuth AUTHENTICATED"); + completeAuthentication(queryParamAuth, "KEYCLOAK"); + return AuthOutcome.AUTHENTICATED; + } + if (deployment.isEnableBasicAuth()) { BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator(); if (log.isTraceEnabled()) { log.trace("try basic auth"); } - + outcome = basicAuth.authenticate(facade); if (outcome == AuthOutcome.FAILED) { challenge = basicAuth.getChallenge(); @@ -150,6 +166,10 @@ public abstract class RequestAuthenticator { return new BasicAuthRequestAuthenticator(deployment); } + protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() { + return new QueryParamterTokenRequestAuthenticator(deployment); + } + protected void completeAuthentication(OAuthRequestAuthenticator oauth) { RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken()); final KeycloakPrincipal principal = new KeycloakPrincipal(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session); @@ -158,10 +178,12 @@ public abstract class RequestAuthenticator { } protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal); + protected abstract void completeBearerAuthentication(KeycloakPrincipal principal, String method); /** * After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation + * * @param create * @return */ diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 15b48fae97..a602ffd636 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -203,4 +203,17 @@ public class AdapterTest { testStrategy.testAccountManagementSessionsLogout(); } + /** + * KEYCLOAK-1733 + */ + @Test + public void testNullQueryParameterAccessToken() throws Exception { + testStrategy.testNullQueryParameterAccessToken(); + } + + @Test + public void testRestCallWithAccessTokenAsQueryParameter() throws Exception { + testStrategy.testRestCallWithAccessTokenAsQueryParameter(); + + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index c5790dc64b..507c0fee58 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -17,34 +17,29 @@ package org.keycloak.testsuite.adapter; import org.apache.http.conn.params.ConnManagerParams; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.Assert; import org.junit.rules.ExternalResource; import org.keycloak.OAuth2Constants; import org.keycloak.adapters.OIDCAuthenticationError; -import org.keycloak.common.Version; -import org.keycloak.representations.VersionRepresentation; import org.keycloak.admin.client.Keycloak; +import org.keycloak.common.Version; +import org.keycloak.common.util.Time; import org.keycloak.constants.AdapterConstants; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.representations.VersionRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; +import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.pages.AccountSessionsPage; import org.keycloak.testsuite.pages.LoginPage; -import org.keycloak.testsuite.rule.AbstractKeycloakRule; -import org.keycloak.testsuite.rule.ErrorServlet; -import org.keycloak.testsuite.rule.KeycloakRule; -import org.keycloak.testsuite.rule.WebResource; -import org.keycloak.testsuite.rule.WebRule; -import org.keycloak.testsuite.KeycloakServer; +import org.keycloak.testsuite.rule.*; import org.keycloak.util.BasicAuthHelper; -import org.keycloak.common.util.Time; import org.openqa.selenium.WebDriver; import javax.ws.rs.client.Client; @@ -59,7 +54,6 @@ import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import org.keycloak.representations.idm.UserRepresentation; /** * Tests Undertow Adapter @@ -144,7 +138,8 @@ public class AdapterTestStrategy extends ExternalResource { System.out.println("insecure: "); System.out.println(driver.getPageSource()); Assert.assertTrue(driver.getPageSource().contains("Insecure Page")); - if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal")); + if (System.getProperty("insecure.user.principal.unsupported") == null) + Assert.assertTrue(driver.getPageSource().contains("UserPrincipal")); // test logout @@ -384,6 +379,26 @@ public class AdapterTestStrategy extends ExternalResource { } + /** + * KEYCLOAK-1733 + * + * @throws Exception + */ + public void testNullQueryParameterAccessToken() throws Exception { + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db/"); + Response response = target.request().get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + target = client.target(APP_SERVER_BASE_URL + "/customer-db?access_token="); + response = target.request().get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + client.close(); + } + /** * KEYCLOAK-1368 * @throws Exception @@ -406,7 +421,7 @@ public class AdapterTestStrategy extends ExternalResource { Assert.assertTrue(errorPageResponse.contains("Error Page")); response.close(); Assert.assertNotNull(ErrorServlet.authError); - OIDCAuthenticationError error = (OIDCAuthenticationError)ErrorServlet.authError; + OIDCAuthenticationError error = (OIDCAuthenticationError) ErrorServlet.authError; Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason()); ErrorServlet.authError = null; @@ -422,7 +437,7 @@ public class AdapterTestStrategy extends ExternalResource { Assert.assertTrue(errorPageResponse.contains("Error Page")); response.close(); Assert.assertNotNull(ErrorServlet.authError); - error = (OIDCAuthenticationError)ErrorServlet.authError; + error = (OIDCAuthenticationError) ErrorServlet.authError; Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason()); client.close(); @@ -464,8 +479,8 @@ public class AdapterTestStrategy extends ExternalResource { String header = BasicAuthHelper.createHeader("customer-portal", "password"); Form form = new Form(); form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) - .param("username", "monkey@redhat.com") - .param("password", "password"); + .param("username", "monkey@redhat.com") + .param("password", "password"); Response response = target.request() .header(HttpHeaders.AUTHORIZATION, header) .post(Entity.form(form)); @@ -496,7 +511,6 @@ public class AdapterTestStrategy extends ExternalResource { } - public void testAuthenticated() throws Exception { // test login to customer-portal which does a bearer request to customer-db driver.navigate().to(APP_SERVER_BASE_URL + "/secure-portal"); @@ -521,6 +535,53 @@ public class AdapterTestStrategy extends ExternalResource { Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); } + /** + * KEYCLOAK-1733 + * + * @throws Exception + */ + public void testRestCallWithAccessTokenAsQueryParameter() throws Exception { + String accessToken = getAccessToken(); + Client client = ClientBuilder.newClient(); + try { + // test without token + Response response = client.target(APP_SERVER_BASE_URL + "/customer-db").request().get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + // test with access_token as QueryParamter + response = client.target(APP_SERVER_BASE_URL + "/customer-db").queryParam("access_token", accessToken).request().get(); + Assert.assertEquals(200, response.getStatus()); + response.close(); + } finally { + client.close(); + } + } + + private String getAccessToken() throws JSONException { + String tokenUrl = AUTH_SERVER_URL + "/realms/demo/protocol/openid-connect/token"; + + Client client = ClientBuilder.newClient(); + try { + WebTarget webTarget = client.target(tokenUrl); + + Form form = new Form(); + form.param("grant_type", "password"); + form.param("client_id", "customer-portal-public"); + form.param("username", "bburke@redhat.com"); + form.param("password", "password"); + Response response = webTarget.request().post(Entity.form(form)); + + Assert.assertEquals(200, response.getStatus()); + + JSONObject jsonObject = new JSONObject(response.readEntity(String.class)); + System.out.println(jsonObject); + response.close(); + return jsonObject.getString("access_token"); + } finally { + client.close(); + } + } + /** * KEYCLOAK-732 * diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json index 70dc85aae4..aaac871bd2 100755 --- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json @@ -156,6 +156,12 @@ "http://localhost" ] }, + { + "name": "customer-portal-public", + "enabled": true, + "publicClient": true, + "directAccessGrantsEnabled": true + }, { "name": "product-portal", "enabled": true,