keycloak-memoizeit
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java 3(+2 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java 54(+54 -0)
adapters/oidc/spring-boot/pom.xml 18(+18 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java 51(+50 -1)
examples/basic-auth/README.md 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 103(+82 -21)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LogoutTest.java 100(+100 -0)
Details
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 420ae93..a58a05a 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 0000000..5ee6662
--- /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 <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
+ * @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 2cd1261..c04f21c 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,18 +74,36 @@ 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) {
+ if (verifySSL()) return AuthOutcome.FAILED;
+ 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();
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;
@@ -150,6 +168,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<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
@@ -158,10 +180,12 @@ public abstract class RequestAuthenticator {
}
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
+
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> 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
*/
adapters/oidc/spring-boot/pom.xml 18(+18 -0)
diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml
index a60441d..1d24a54 100755
--- a/adapters/oidc/spring-boot/pom.xml
+++ b/adapters/oidc/spring-boot/pom.xml
@@ -89,6 +89,24 @@
</dependency>
<dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
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 68c750c..5c1b546 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<io.undertow.servlet.api.SecurityConstraint> getSecurityConstraints() {
+
+ List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
+ 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 {
examples/basic-auth/README.md 2(+1 -1)
diff --git a/examples/basic-auth/README.md b/examples/basic-auth/README.md
index be96c59..8eb4fc5 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.
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 3f8d32a..85fea77 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/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 54bf9ae..4fedd83 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/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 15b48fa..a602ffd 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 c5790dc..507c0fe 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
@@ -385,6 +380,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");
@@ -522,6 +536,53 @@ public class AdapterTestStrategy extends ExternalResource {
}
/**
+ * 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
*
* @throws Throwable
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index 70dc85a..aaac871 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -157,6 +157,12 @@
]
},
{
+ "name": "customer-portal-public",
+ "enabled": true,
+ "publicClient": true,
+ "directAccessGrantsEnabled": true
+ },
+ {
"name": "product-portal",
"enabled": true,
"adminUrl": "http://localhost:8081/product-portal",
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 0000000..d3a5cc4
--- /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 0000000..4a44294
--- /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 bf6b3e0..00c9fd3 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
@@ -293,6 +293,10 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
@@ -587,7 +591,42 @@
</pluginManagement>
</build>
</profile>
-
+ <profile>
+ <id>auth-server-apply-patch</id>
+ <activation>
+ <property>
+ <name>auth.server.patch.zip</name>
+ </property>
+ </activation>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>install-patch</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <executable>${common.resources}/install-patch.${script.suffix}</executable>
+ <workingDirectory>${auth.server.home}/bin</workingDirectory>
+ <environmentVariables>
+ <JAVA_HOME>${auth.server.java.home}</JAVA_HOME>
+ <JBOSS_HOME>${auth.server.home}</JBOSS_HOME>
+ <PATCH_ZIP>${auth.server.patch.zip}</PATCH_ZIP>
+ </environmentVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
<profile>
<id>auth-server-cluster</id>
<properties>
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 23c365f..cef65dc 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
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 0000000..38dde74
--- /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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+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<RealmRepresentation> 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 c9746d2..3653065 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 5427075..11e25d3 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"
},
{