diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java index 4635613fb2..56d7ec199d 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java @@ -21,12 +21,14 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Pedro Igor */ +@JsonIgnoreProperties(ignoreUnknown = true) public class Permission { @JsonProperty("rsid") diff --git a/services/src/main/java/org/keycloak/authorization/protection/introspect/RPTIntrospectionProvider.java b/services/src/main/java/org/keycloak/authorization/protection/introspect/RPTIntrospectionProvider.java index 989dbe1c99..d8100ecfa5 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/introspect/RPTIntrospectionProvider.java +++ b/services/src/main/java/org/keycloak/authorization/protection/introspect/RPTIntrospectionProvider.java @@ -17,17 +17,22 @@ */ package org.keycloak.authorization.protection.introspect; -import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.ObjectNode; import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.AccessTokenIntrospectionProvider; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken.Authorization; +import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.util.JsonSerialization; /** @@ -65,7 +70,19 @@ public class RPTIntrospectionProvider extends AccessTokenIntrospectionProvider { metadata.setResourceAccess(null); tokenMetadata = JsonSerialization.createObjectNode(metadata); - tokenMetadata.putPOJO("permissions", accessToken.getAuthorization().getPermissions()); + Authorization authorization = accessToken.getAuthorization(); + + if (authorization != null) { + Collection permissions; + + if (authorization.getPermissions() != null) { + permissions = authorization.getPermissions().stream().map(UmaPermissionRepresentation::new).collect(Collectors.toSet()); + } else { + permissions = Collections.emptyList(); + } + + tokenMetadata.putPOJO("permissions", permissions); + } } else { tokenMetadata = JsonSerialization.createObjectNode(); } @@ -82,4 +99,26 @@ public class RPTIntrospectionProvider extends AccessTokenIntrospectionProvider { public void close() { } + + //todo: we need to avoid creating this class when processing responses. The only reason for that is that + // UMA defines "resource_id" and "resource_scopes" claims but we use "rsid" and "scopes". + // To avoid breaking backward compatiblity we are just responding with all these claims. + public class UmaPermissionRepresentation extends Permission { + + public UmaPermissionRepresentation(Permission permission) { + setResourceId(permission.getResourceId()); + setResourceName(permission.getResourceName()); + setScopes(permission.getScopes()); + } + + @JsonProperty("resource_id") + public String getUmaResourceId() { + return getResourceId(); + } + + @JsonProperty("resource_scopes") + public Set getUmaResourceScopes() { + return getScopes(); + } + } } 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 0d6203b521..0aa9d6d8ea 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 @@ -16,15 +16,19 @@ */ package org.keycloak.testsuite.authz; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT; import java.net.URI; import java.util.Collection; -import java.util.List; +import java.util.Map; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; @@ -41,6 +45,8 @@ import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.authorization.client.AuthorizationDeniedException; +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.representation.TokenIntrospectionResponse; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessToken; @@ -54,6 +60,7 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.util.BasicAuthHelper; +import org.keycloak.util.JsonSerialization; /** * @author Pedro Igor @@ -456,6 +463,57 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest { assertTrue(permissions.isEmpty()); } + @Test + public void testTokenIntrospect() throws Exception { + AuthzClient authzClient = getAuthzClient(); + AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken("marta", "password"); + AuthorizationResponse response = authorize(null, null, null, null, accessTokenResponse.getToken(), null, null, new PermissionRequest("Resource A", "ScopeA", "ScopeB")); + String rpt = response.getToken(); + + assertNotNull(rpt); + assertFalse(response.isUpgraded()); + + AccessToken accessToken = toAccessToken(rpt); + AccessToken.Authorization authorization = accessToken.getAuthorization(); + + assertNotNull(authorization); + + Collection permissions = authorization.getPermissions(); + + assertNotNull(permissions); + assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); + assertTrue(permissions.isEmpty()); + + TokenIntrospectionResponse introspectionResponse = authzClient.protection().introspectRequestingPartyToken(rpt); + + assertNotNull(introspectionResponse); + assertNotNull(introspectionResponse.getPermissions()); + + oauth.realm("authz-test"); + String introspectHttpResponse = oauth.introspectTokenWithClientCredential("resource-server-test", "secret", "requesting_party_token", rpt); + + Map jsonNode = JsonSerialization.readValue(introspectHttpResponse, Map.class); + + assertEquals(true, jsonNode.get("active")); + + Collection permissionClaims = (Collection) jsonNode.get("permissions"); + + assertNotNull(permissionClaims); + assertEquals(1, permissionClaims.size()); + + Map claim = (Map) permissionClaims.iterator().next(); + + assertThat(claim.keySet(), containsInAnyOrder("resource_id", "rsname", "resource_scopes", "scopes", "rsid")); + assertThat(claim.get("rsname"), equalTo("Resource A")); + + ResourceRepresentation resourceRep = authzClient.protection().resource().findByName("Resource A"); + assertThat(claim.get("rsid"), equalTo(resourceRep.getId())); + assertThat(claim.get("resource_id"), equalTo(resourceRep.getId())); + + assertThat((Collection) claim.get("resource_scopes"), containsInAnyOrder("ScopeA", "ScopeB")); + assertThat((Collection) claim.get("scopes"), containsInAnyOrder("ScopeA", "ScopeB")); + } + private String getIdToken(String username, String password) { oauth.realm("authz-test"); oauth.clientId("test-app");