From 0a0b7da53e9c521c7008adb804c21eecac5cdfd2 Mon Sep 17 00:00:00 2001 From: Frode Ingebrigtsen Date: Fri, 11 Sep 2020 07:36:37 +0200 Subject: [PATCH] KEYCLOAK-15429 Add CORS origin on permission request with invalid access token --- .../oidc/endpoints/TokenEndpoint.java | 12 ++++++ .../testsuite/authz/UmaGrantTypeTest.java | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 367537eeca..9e4576c09c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -23,6 +23,7 @@ import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; +import org.keycloak.TokenVerifier; import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.authorization.AuthorizationTokenService; @@ -36,10 +37,13 @@ import org.keycloak.broker.provider.IdentityProviderMapper; import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate; import org.keycloak.common.ClientConnection; import org.keycloak.common.Profile; +import org.keycloak.common.VerificationException; import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.constants.AdapterConstants; +import org.keycloak.crypto.SignatureProvider; +import org.keycloak.crypto.SignatureVerifierContext; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; @@ -1209,6 +1213,14 @@ public class TokenEndpoint { AccessToken accessToken = Tokens.getAccessToken(session); if (accessToken == null) { + try { + // In case the access token is invalid because it's expired or the user is disabled, identify the client + // from the access token anyway in order to set correct CORS headers. + AccessToken invalidToken = new JWSInput(accessTokenString).readJsonContent(AccessToken.class); + ClientModel client = realm.getClientByClientId(invalidToken.getIssuedFor()); + cors.allowedOrigins(session, client); + } catch (JWSInputException ignore) { + } throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid bearer token", Status.UNAUTHORIZED); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java index 0fd0af4613..52bc35dd4e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java @@ -27,7 +27,10 @@ import static org.junit.Assert.fail; import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT; import java.net.URI; +import java.util.Arrays; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import javax.ws.rs.client.Client; @@ -39,6 +42,12 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; +import com.google.common.base.Charsets; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicNameValuePair; import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; @@ -51,6 +60,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.authorization.AuthorizationResponse; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; import org.keycloak.representations.idm.authorization.Permission; @@ -61,6 +71,7 @@ import org.keycloak.representations.idm.authorization.ScopePermissionRepresentat import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.JsonSerialization; @@ -361,6 +372,33 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest { assertTrue(permissions.isEmpty()); } + @Test + public void testCORSHeadersInFailedRptRequest() throws Exception { + AccessTokenResponse accessTokenResponse = getAuthzClient().obtainAccessToken("marta", "password"); + + UserRepresentation userRepresentation = getRealm().users().search("marta").get(0); + UserRepresentation updatedUser = UserBuilder.edit(userRepresentation).enabled(false).build(); + getRealm().users().get(userRepresentation.getId()).update(updatedUser); + + PermissionRequest permissions = new PermissionRequest("Resource A", "ScopeA", "ScopeB"); + String ticket = getAuthzClient().protection().permission().create(Arrays.asList(permissions)).getTicket(); + + String tokenEndpoint = getAuthzClient().getServerConfiguration().getTokenEndpoint(); + HttpPost post = new HttpPost(tokenEndpoint); + post.addHeader("Origin", "http://localhost"); + post.addHeader("Authorization", "Bearer " + accessTokenResponse.getToken()); + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.UMA_GRANT_TYPE)); + parameters.add(new BasicNameValuePair("ticket", ticket)); + + UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charsets.UTF_8); + post.setEntity(formEntity); + + CloseableHttpResponse response = oauth.getHttpClient().get().execute(post); + assertEquals(401, response.getStatusLine().getStatusCode()); + assertEquals("http://localhost", response.getFirstHeader("Access-Control-Allow-Origin").getValue()); + } + @Test public void testRefreshRpt() { AccessTokenResponse accessTokenResponse = getAuthzClient().obtainAccessToken("marta", "password");