diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java index b8d982d306..77b6ec17ef 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java @@ -79,19 +79,14 @@ public class ResourcePolicyProviderFactory implements PolicyProviderFactory config = new HashMap(policy.getConfig()); + Map config = new HashMap(policy.getConfig()); - config.compute("defaultResourceType", (key, value) -> { - String resourceType = resourcePermission.getResourceType(); - return resourceType != null ? resourcePermission.getResourceType() : null; - }); + config.compute("defaultResourceType", (key, value) -> { + String resourceType = representation.getResourceType(); + return resourceType != null ? representation.getResourceType() : null; + }); - policy.setConfig(config); - - } + policy.setConfig(config); } } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java index 52af952708..0be56fcfa5 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java @@ -25,6 +25,9 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; +import java.util.HashMap; +import java.util.Map; + /** * @author Pedro Igor */ @@ -59,7 +62,32 @@ public class ScopePolicyProviderFactory implements PolicyProviderFactory config = new HashMap(policy.getConfig()); + + config.compute("defaultResourceType", (key, value) -> { + String resourceType = representation.getResourceType(); + return resourceType != null ? representation.getResourceType() : null; + }); + + policy.setConfig(config); + } } @Override diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java index b6a02b414d..1f08553f32 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopePermissionRepresentation.java @@ -21,8 +21,18 @@ package org.keycloak.representations.idm.authorization; */ public class ScopePermissionRepresentation extends AbstractPolicyRepresentation { + private String resourceType; + @Override public String getType() { return "scope"; } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getResourceType() { + return resourceType; + } } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java index 8429f73ad3..acff301449 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java @@ -18,10 +18,7 @@ package org.keycloak.authorization.jpa.entities; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.persistence.Access; import javax.persistence.AccessType; @@ -59,7 +56,7 @@ import org.keycloak.representations.idm.authorization.Logic; @NamedQuery(name="findPolicyIdByResource", query="select p from PolicyEntity p inner join p.resources r inner join fetch p.associatedPolicies a where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"), @NamedQuery(name="findPolicyIdByScope", query="select pe from PolicyEntity pe inner join pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds) and p.id = pe.id))"), @NamedQuery(name="findPolicyIdByResourceScope", query="select pe from PolicyEntity pe inner join pe.resources r inner join pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds) and p.id = pe.id)) and exists (select p.id from ResourceEntity r inner join r.policies p where r.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.id = pe.id and p.type = 'scope' and r.id in (:resourceId)))"), - @NamedQuery(name="findPolicyIdByNullResourceScope", query="select pe from PolicyEntity pe inner join pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.id = pe.id and p.type = 'scope' and s.id in (:scopeIds))) and pe.resources is empty"), + @NamedQuery(name="findPolicyIdByNullResourceScope", query="select pe from PolicyEntity pe left join fetch pe.config c inner join pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.id = pe.id and p.type = 'scope' and s.id in (:scopeIds))) and pe.resources is empty and not exists (select pec from pe.config pec where KEY(pec) = 'defaultResourceType')"), @NamedQuery(name="findPolicyIdByType", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"), @NamedQuery(name="findPolicyIdByResourceType", query="select p from PolicyEntity p inner join p.config c inner join fetch p.associatedPolicies a where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"), @NamedQuery(name="findPolicyIdByDependentPolices", query="select p.id from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"), diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java index 12674ea0c8..ce8665c465 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java @@ -780,7 +780,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { } @Test - public void testObtainAllEntitlementsForResource() throws Exception { + public void testObtainAllEntitlementsForResourceWithResourcePermission() throws Exception { ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); AuthorizationResource authorization = client.authorization(); @@ -849,6 +849,11 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.addPermission(resource.getId(), "scope:view", "scope:update", "scope:delete"); + response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(1, permissions.size()); + for (Permission grantedPermission : permissions) { assertEquals(resource.getId(), grantedPermission.getResourceId()); assertEquals(2, grantedPermission.getScopes().size()); @@ -856,6 +861,87 @@ public class EntitlementAPITest extends AbstractAuthzTest { } } + @Test + public void testObtainAllEntitlementsForResourceWithScopePermission() throws Exception { + ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); + AuthorizationResource authorization = client.authorization(); + + JSPolicyRepresentation policy = new JSPolicyRepresentation(); + + policy.setName(KeycloakModelUtils.generateId()); + policy.setCode("$evaluation.grant();"); + + authorization.policies().js().create(policy).close(); + + ResourceRepresentation resourceWithoutType = new ResourceRepresentation(); + + resourceWithoutType.setName(KeycloakModelUtils.generateId()); + resourceWithoutType.addScope("scope:view", "scope:update", "scope:delete"); + + try (Response response = authorization.resources().create(resourceWithoutType)) { + resourceWithoutType = response.readEntity(ResourceRepresentation.class); + } + + ResourceRepresentation resourceWithType = new ResourceRepresentation(); + + resourceWithType.setName(KeycloakModelUtils.generateId()); + resourceWithType.setType("type-one"); + resourceWithType.addScope("scope:view", "scope:update", "scope:delete"); + + try (Response response = authorization.resources().create(resourceWithType)) { + resourceWithType = response.readEntity(ResourceRepresentation.class); + } + + ScopePermissionRepresentation permission = new ScopePermissionRepresentation(); + + permission.setName(KeycloakModelUtils.generateId()); + permission.addResource(resourceWithoutType.getId()); + permission.addScope("scope:view"); + permission.addPolicy(policy.getName()); + + authorization.permissions().scope().create(permission).close(); + + permission = new ScopePermissionRepresentation(); + + permission.setName(KeycloakModelUtils.generateId()); + permission.setResourceType("type-one"); + permission.addScope("scope:update"); + permission.addPolicy(policy.getName()); + + authorization.permissions().scope().create(permission).close(); + + String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); + AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG); + + AuthorizationRequest request = new AuthorizationRequest(); + request.addPermission(resourceWithoutType.getId(), "scope:view", "scope:update", "scope:delete"); + + AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + Collection permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(1, permissions.size()); + + for (Permission grantedPermission : permissions) { + assertEquals(resourceWithoutType.getId(), grantedPermission.getResourceId()); + assertEquals(1, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("scope:view"))); + } + + request = new AuthorizationRequest(); + request.addPermission(resourceWithType.getId(), "scope:view", "scope:update", "scope:delete"); + + response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(1, permissions.size()); + + for (Permission grantedPermission : permissions) { + assertEquals(resourceWithType.getId(), grantedPermission.getResourceId()); + assertEquals(1, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("scope:update"))); + } + } + @Test public void testServerDecisionStrategy() throws Exception { ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); @@ -1040,29 +1126,59 @@ public class EntitlementAPITest extends AbstractAuthzTest { authorization.resources().create(resource).close(); } - ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation(); + for (int i = 0; i < 10; i++) { + ResourceRepresentation resource = new ResourceRepresentation(); - permission.setName(KeycloakModelUtils.generateId()); - permission.setResourceType("type-one"); - permission.addPolicy(policy.getName()); + resource.setType("type-four"); + resource.setName(KeycloakModelUtils.generateId()); + resource.addScope("scope:view", "scope:update"); - authorization.permissions().resource().create(permission).close(); + authorization.resources().create(resource).close(); + } - permission = new ResourcePermissionRepresentation(); + for (int i = 0; i < 10; i++) { + ResourceRepresentation resource = new ResourceRepresentation(); - permission.setName(KeycloakModelUtils.generateId()); - permission.setResourceType("type-two"); - permission.addPolicy(policy.getName()); + resource.setType("type-five"); + resource.setName(KeycloakModelUtils.generateId()); + resource.addScope("scope:view"); - authorization.permissions().resource().create(permission).close(); + authorization.resources().create(resource).close(); + } - permission = new ResourcePermissionRepresentation(); - permission.setName(KeycloakModelUtils.generateId()); - permission.setResourceType("type-three"); - permission.addPolicy(policy.getName()); + ResourcePermissionRepresentation resourcePermission = new ResourcePermissionRepresentation(); - authorization.permissions().resource().create(permission).close(); + resourcePermission.setName(KeycloakModelUtils.generateId()); + resourcePermission.setResourceType("type-one"); + resourcePermission.addPolicy(policy.getName()); + + authorization.permissions().resource().create(resourcePermission).close(); + + resourcePermission = new ResourcePermissionRepresentation(); + + resourcePermission.setName(KeycloakModelUtils.generateId()); + resourcePermission.setResourceType("type-two"); + resourcePermission.addPolicy(policy.getName()); + + authorization.permissions().resource().create(resourcePermission).close(); + + resourcePermission = new ResourcePermissionRepresentation(); + + resourcePermission.setName(KeycloakModelUtils.generateId()); + resourcePermission.setResourceType("type-three"); + resourcePermission.addPolicy(policy.getName()); + + authorization.permissions().resource().create(resourcePermission).close(); + + ScopePermissionRepresentation scopePersmission = new ScopePermissionRepresentation(); + + scopePersmission.setName(KeycloakModelUtils.generateId()); + scopePersmission.setResourceType("type-four"); + scopePersmission.addScope("scope:view"); + scopePersmission.addPolicy(policy.getName()); + + authorization.permissions().scope().create(scopePersmission).close(); String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG); @@ -1081,6 +1197,26 @@ public class EntitlementAPITest extends AbstractAuthzTest { permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); assertEquals(10, permissions.size()); + request = new AuthorizationRequest(); + request.addPermission("resource-type:type-four", "scope:view"); + response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(10, permissions.size()); + for (Permission grantedPermission : permissions) { + assertEquals(1, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("scope:view"))); + } + + request = new AuthorizationRequest(); + request.addPermission("resource-type:type-five", "scope:view"); + try { + authzClient.authorization(accessToken).authorize(request); + fail("no type-five resources can be granted since scope permission for scope:view only applies to type-four"); + } catch (RuntimeException expected) { + assertEquals(403, HttpResponseException.class.cast(expected.getCause()).getStatusCode()); + assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("access_denied")); + } for (int i = 0; i < 5; i++) { ResourceRepresentation resource = new ResourceRepresentation();