From 159b37197335cc56fbb2097086e96fc752da9e40 Mon Sep 17 00:00:00 2001 From: awpwb Date: Fri, 1 Jul 2016 13:35:27 +0200 Subject: [PATCH 1/6] [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, From e708c537303df28a0152eea7f81e10344766ae84 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 15 Jul 2016 12:56:31 +0200 Subject: [PATCH 2/6] KEYCLOAK-3302 Allow logout with expired refresh token --- .../keycloak/protocol/oidc/TokenManager.java | 17 ++- .../oidc/endpoints/LogoutEndpoint.java | 2 +- .../keycloak/testsuite/oauth/LogoutTest.java | 100 ++++++++++++++++++ .../keycloak/testsuite/util/RealmBuilder.java | 5 + .../base/src/test/resources/testrealm.json | 2 +- 5 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 54bf9ae689..4fedd8323d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -244,16 +244,23 @@ public class TokenManager { } public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException { + return verifyRefreshToken(realm, encodedRefreshToken, true); + } + + public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken, boolean checkExpiration) throws OAuthErrorException { try { RefreshToken refreshToken = toRefreshToken(realm, encodedRefreshToken); - if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) { - throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired"); + if (checkExpiration) { + if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired"); + } + + if (refreshToken.getIssuedAt() < realm.getNotBefore()) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token"); + } } - if (refreshToken.getIssuedAt() < realm.getNotBefore()) { - throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token"); - } return refreshToken; } catch (JWSInputException e) { throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 3f8d32aace..85fea77501 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -187,7 +187,7 @@ public class LogoutEndpoint { throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST); } try { - RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken); + RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken, false); UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState()); if (userSessionModel != null) { logout(userSessionModel); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java new file mode 100644 index 0000000000..38dde746dc --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java @@ -0,0 +1,100 @@ +/* + * 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.testsuite.oauth; + +import org.apache.http.HttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.common.util.Time; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.RealmBuilder; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +/** + * @author Stian Thorgersen + */ +public class LogoutTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + } + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + RealmBuilder realm = RealmBuilder.edit(realmRepresentation).testEventListener(); + + testRealms.add(realm.build()); + } + + @Test + public void postLogout() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + oauth.clientSessionState("client-session"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); + String refreshTokenString = tokenResponse.getRefreshToken(); + + HttpResponse response = oauth.doLogout(refreshTokenString, "password"); + assertEquals(204, response.getStatusLine().getStatusCode()); + + assertNotNull(testingClient.testApp().getAdminLogoutAction()); + } + + @Test + public void postLogoutExpiredRefreshToken() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + oauth.clientSessionState("client-session"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); + String refreshTokenString = tokenResponse.getRefreshToken(); + + adminClient.realm("test").update(RealmBuilder.create().notBefore(Time.currentTime() + 1).build()); + + // Logout should succeed with expired refresh token, see KEYCLOAK-3302 + HttpResponse response = oauth.doLogout(refreshTokenString, "password"); + assertEquals(204, response.getStatusLine().getStatusCode()); + + assertNotNull(testingClient.testApp().getAdminLogoutAction()); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java index c9746d2ca6..3653065d66 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java @@ -132,6 +132,11 @@ public class RealmBuilder { return this; } + public RealmBuilder notBefore(int i) { + rep.setNotBefore(i); + return this; + } + public RealmBuilder otpLookAheadWindow(int i) { rep.setOtpPolicyLookAheadWindow(i); return this; diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json index 542707551d..11e25d3f6f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -105,7 +105,7 @@ "redirectUris": [ "http://localhost:8180/auth/realms/master/app/auth/*" ], - "adminUrl": "http://localhost:8180/auth/realms/master/app/logout", + "adminUrl": "http://localhost:8180/auth/realms/master/app/admin", "secret": "password" }, { From f574173ed3479839018de4b215c356f2accc0e41 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 18 Jul 2016 10:00:24 +0200 Subject: [PATCH 3/6] KEYCLOAK-3301 Add support for Undertow in Spring Boot Adapter We now support using Undertow in combination with the Spring Boot Adapter. --- adapters/oidc/spring-boot/pom.xml | 18 +++++++ .../KeycloakSpringBootConfiguration.java | 51 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index a60441d26e..1d24a547ac 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -88,6 +88,24 @@ provided + + io.undertow + undertow-servlet + provided + + + + io.undertow + undertow-core + provided + + + + org.keycloak + keycloak-undertow-adapter-spi + provided + + junit junit diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java index 68c750c973..5c1b54678c 100755 --- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java @@ -17,6 +17,8 @@ package org.keycloak.adapters.springboot; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.WebResourceCollection; import org.apache.catalina.Context; import org.apache.tomcat.util.descriptor.web.LoginConfig; import org.apache.tomcat.util.descriptor.web.SecurityCollection; @@ -28,6 +30,7 @@ import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.webapp.WebAppContext; import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator; import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve; +import org.keycloak.adapters.undertow.KeycloakServletExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -108,7 +111,53 @@ public class KeycloakSpringBootConfiguration { @Bean @ConditionalOnClass(name = {"io.undertow.Undertow"}) public UndertowDeploymentInfoCustomizer undertowKeycloakContextCustomizer() { - throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented"); + return new KeycloakUndertowDeploymentInfoCustomizer(keycloakProperties); + } + + static class KeycloakUndertowDeploymentInfoCustomizer implements UndertowDeploymentInfoCustomizer { + + private final KeycloakSpringBootProperties keycloakProperties; + + public KeycloakUndertowDeploymentInfoCustomizer(KeycloakSpringBootProperties keycloakProperties) { + this.keycloakProperties = keycloakProperties; + } + + @Override + public void customize(DeploymentInfo deploymentInfo) { + + io.undertow.servlet.api.LoginConfig loginConfig = new io.undertow.servlet.api.LoginConfig(keycloakProperties.getRealm()); + loginConfig.addFirstAuthMethod("KEYCLOAK"); + + deploymentInfo.setLoginConfig(loginConfig); + + deploymentInfo.addInitParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName()); + deploymentInfo.addSecurityConstraints(getSecurityConstraints()); + + deploymentInfo.addServletExtension(new KeycloakServletExtension()); + } + + private List getSecurityConstraints() { + + List undertowSecurityConstraints = new ArrayList(); + for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) { + + for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) { + + io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint(); + undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles()); + + WebResourceCollection webResourceCollection = new WebResourceCollection(); + webResourceCollection.addHttpMethods(collectionDefinition.getMethods()); + webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods()); + webResourceCollection.addUrlPatterns(collectionDefinition.getPatterns()); + + undertowSecurityConstraint.addWebResourceCollections(webResourceCollection); + + undertowSecurityConstraints.add(undertowSecurityConstraint); + } + } + return undertowSecurityConstraints; + } } static class KeycloakJettyServerCustomizer implements JettyServerCustomizer { From a913b8a158547bf11e08d129c4d7668c23a79ecd Mon Sep 17 00:00:00 2001 From: mhajas Date: Mon, 18 Jul 2016 14:21:28 +0200 Subject: [PATCH 4/6] Fix invalidRequesterMessage in tests --- .../adapter/servlet/AbstractSAMLServletsAdapterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java index 23c365f9bd..cef65dc3c5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java @@ -253,7 +253,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd @Test public void badClientSalesPostSigTest() { badClientSalesPostSigServletPage.navigateTo(); - waitUntilElement(By.xpath("//body")).text().contains("invalidRequesterMessage"); + waitUntilElement(By.xpath("//body")).text().contains("Invalid requester"); } @Test From 0e470d75e9dfcca74238d9a9108d6771e233f335 Mon Sep 17 00:00:00 2001 From: Pavel Drozd Date: Tue, 19 Jul 2016 14:22:49 +0200 Subject: [PATCH 5/6] KEYCLOAK-3336 Arquillian testuite: Added possibility to install server patch --- .../jboss/common/install-patch.bat | 7 ++++ .../auth-server/jboss/common/install-patch.sh | 17 ++++++++ .../servers/auth-server/jboss/pom.xml | 41 ++++++++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat create mode 100755 testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat new file mode 100644 index 0000000000..d3a5cc4f4b --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat @@ -0,0 +1,7 @@ +set NOPAUSE=true + +call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %PATCH_ZIP%" + +if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL% +exit /b %ERROR% + diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh new file mode 100755 index 0000000000..4a44294d03 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh @@ -0,0 +1,17 @@ +#!/bin/bash +echo "JBOSS_HOME=$JBOSS_HOME" + +if [ ! -d "$JBOSS_HOME/bin" ] ; then + >&2 echo "JBOSS_HOME/bin doesn't exist" + exit 1 +fi + +cd $JBOSS_HOME/bin + +RESULT=0 +./jboss-cli.sh --command="patch apply $PATCH_ZIP" +if [ $? -ne 0 ]; then RESULT=1; fi + exit $RESULT +fi + +exit 1 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index bf6b3e0bc2..00c9fd3f74 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -292,6 +292,10 @@ + + org.codehaus.mojo + exec-maven-plugin + maven-assembly-plugin @@ -587,7 +591,42 @@ - + + auth-server-apply-patch + + + auth.server.patch.zip + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + install-patch + process-resources + + exec + + + + + ${common.resources}/install-patch.${script.suffix} + ${auth.server.home}/bin + + ${auth.server.java.home} + ${auth.server.home} + ${auth.server.patch.zip} + + + + + + + auth-server-cluster From 7571dc07f9dd948bcc3a39ef39c803f2d6b11574 Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 20 Jul 2016 11:04:31 +0200 Subject: [PATCH 6/6] KEYCLOAK-1733 added verifySSL checks for 'basic' and 'query' authentication --- .../main/java/org/keycloak/adapters/RequestAuthenticator.java | 2 ++ examples/basic-auth/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 c59087c966..c04f21c050 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 @@ -85,6 +85,7 @@ public abstract class RequestAuthenticator { log.debug("QueryParamAuth auth FAILED"); return AuthOutcome.FAILED; } else if (outcome == AuthOutcome.AUTHENTICATED) { + if (verifySSL()) return AuthOutcome.FAILED; log.debug("QueryParamAuth AUTHENTICATED"); completeAuthentication(queryParamAuth, "KEYCLOAK"); return AuthOutcome.AUTHENTICATED; @@ -102,6 +103,7 @@ public abstract class RequestAuthenticator { log.debug("BasicAuth FAILED"); return AuthOutcome.FAILED; } else if (outcome == AuthOutcome.AUTHENTICATED) { + if (verifySSL()) return AuthOutcome.FAILED; log.debug("BasicAuth AUTHENTICATED"); completeAuthentication(basicAuth, "BASIC"); return AuthOutcome.AUTHENTICATED; diff --git a/examples/basic-auth/README.md b/examples/basic-auth/README.md index be96c59824..8eb4fc506a 100644 --- a/examples/basic-auth/README.md +++ b/examples/basic-auth/README.md @@ -22,7 +22,7 @@ Step 2: Deploy and run the example curl http://admin:password@localhost:8080/basicauth/service/echo?value=hello -(If we navigate directly to http://localhost:8080/basicauth/service/echo?value=hello, we get "Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client."). +(If we navigate directly to http://localhost:8080/basicauth/service/echo?value=hello, we get an error in the browser because the request is not authenticated). This should result in the value 'hello' being returned as a response.