[KEYCLOAK-8915] - Support resource type in authorization requests

This commit is contained in:
Pedro Igor 2019-05-27 17:04:46 -03:00 committed by Bruno Oliveira da Silva
parent a58a0e7678
commit 61eb94c674
9 changed files with 346 additions and 8 deletions

View file

@ -87,6 +87,9 @@ public class StoreFactoryCacheManager extends CacheManager {
if (type != null) {
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, owner, serverId));
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, null, serverId));
invalidations.add(StoreFactoryCacheSession.getResourceByTypeInstanceCacheKey(type, serverId));
addInvalidations(InResourcePredicate.create().resource(type), invalidations);
}

View file

@ -360,6 +360,14 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return "resource.type." + type + "." + serverId;
}
public static String getResourceByTypeCacheKey(String type, String owner, String serverId) {
return "resource.type." + type + ".owner." + owner + "." + serverId;
}
public static String getResourceByTypeInstanceCacheKey(String type, String serverId) {
return "resource.type.instance." + type + "." + serverId;
}
public static String getResourceByUriCacheKey(String uri, String serverId) {
return "resource.uri." + uri + "." + serverId;
}
@ -725,6 +733,42 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
}
@Override
public List<Resource> findByType(String type, String owner, String resourceServerId) {
if (resourceServerId.equals(owner)) {
return findByType(type, resourceServerId);
} else {
if (type == null) return Collections.emptyList();
String cacheKey = getResourceByTypeCacheKey(type, owner, resourceServerId);
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, owner, resourceServerId),
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
}
@Override
public void findByType(String type, String owner, String resourceServerId, Consumer<Resource> consumer) {
if (type == null) return;
String cacheKey = getResourceByTypeCacheKey(type, owner, resourceServerId);
cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, owner, resourceServerId),
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
}
@Override
public List<Resource> findByTypeInstance(String type, String resourceServerId) {
if (type == null) return Collections.emptyList();
String cacheKey = getResourceByTypeInstanceCacheKey(type, resourceServerId);
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByTypeInstance(type, resourceServerId),
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public void findByTypeInstance(String type, String resourceServerId, Consumer<Resource> consumer) {
if (type == null) return;
String cacheKey = getResourceByTypeInstanceCacheKey(type, resourceServerId);
cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByTypeInstance(type, resourceServerId),
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
}
private <R extends Resource, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
return cacheQuery(cacheKey, queryType, resultSupplier, querySupplier, resourceServerId, null);
}

View file

@ -62,6 +62,8 @@ import org.hibernate.annotations.FetchMode;
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and :uri in elements(r.uris)"),
@NamedQuery(name="findResourceIdByName", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
@NamedQuery(name="findResourceIdByType", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"),
@NamedQuery(name="findResourceIdByTypeNoOwner", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.type = :type"),
@NamedQuery(name="findResourceIdByTypeInstance", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.type = :type and r.owner <> :serverId"),
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
@NamedQuery(name="findResourceIdByScope", query="select r from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
@NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId")

View file

@ -39,7 +39,6 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
/**
@ -289,13 +288,61 @@ public class JPAResourceStore implements ResourceStore {
return list;
}
@Override
public List<Resource> findByType(String type, String owner, String resourceServerId) {
List<Resource> list = new LinkedList<>();
findByType(type, owner, resourceServerId, list::add);
return list;
}
@Override
public void findByType(String type, String resourceServerId, Consumer<Resource> consumer) {
TypedQuery<ResourceEntity> query = entityManager.createNamedQuery("findResourceIdByType", ResourceEntity.class);
findByType(type, resourceServerId, resourceServerId, consumer);
}
@Override
public void findByType(String type, String owner, String resourceServerId, Consumer<Resource> consumer) {
TypedQuery<ResourceEntity> query;
if (owner != null) {
query = entityManager.createNamedQuery("findResourceIdByType", ResourceEntity.class);
} else {
query = entityManager.createNamedQuery("findResourceIdByTypeNoOwner", ResourceEntity.class);
}
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", type);
if (owner != null) {
query.setParameter("ownerId", owner);
}
query.setParameter("serverId", resourceServerId);
StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
.map(entity -> new ResourceAdapter(entity, entityManager, storeFactory))
.forEach(consumer);
}
@Override
public List<Resource> findByTypeInstance(String type, String resourceServerId) {
List<Resource> list = new LinkedList<>();
findByTypeInstance(type, resourceServerId, list::add);
return list;
}
@Override
public void findByTypeInstance(String type, String resourceServerId, Consumer<Resource> consumer) {
TypedQuery<ResourceEntity> query = entityManager.createNamedQuery("findResourceIdByTypeInstance", ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", type);
query.setParameter("ownerId", resourceServerId);
query.setParameter("serverId", resourceServerId);
StoreFactory storeFactory = provider.getStoreFactory();

View file

@ -535,6 +535,26 @@ public final class AuthorizationProvider implements Provider {
public void findByType(String type, String resourceServerId, Consumer<Resource> consumer) {
delegate.findByType(type, resourceServerId, consumer);
}
@Override
public void findByType(String type, String owner, String resourceServerId, Consumer<Resource> consumer) {
delegate.findByType(type, owner, resourceServerId, consumer);
}
@Override
public List<Resource> findByType(String type, String owner, String resourceServerId) {
return delegate.findByType(type, resourceServerId);
}
@Override
public List<Resource> findByTypeInstance(String type, String resourceServerId) {
return delegate.findByTypeInstance(type, resourceServerId);
}
@Override
public void findByTypeInstance(String type, String resourceServerId, Consumer<Resource> consumer) {
delegate.findByTypeInstance(type, resourceServerId, consumer);
}
};
}
}

View file

@ -139,6 +139,15 @@ public interface ResourceStore {
*/
List<Resource> findByType(String type, String resourceServerId);
/**
* Finds all {@link Resource} with the given type.
*
* @param type the type of the resource
* @param owner the resource owner or null for any resource with a given type
* @return a list of resources with the given type
*/
List<Resource> findByType(String type, String owner, String resourceServerId);
/**
* Finds all {@link Resource} with the given type.
*
@ -148,4 +157,19 @@ public interface ResourceStore {
* @return a list of resources with the given type
*/
void findByType(String type, String resourceServerId, Consumer<Resource> consumer);
/**
* Finds all {@link Resource} with the given type.
*
* @param type the type of the resource
* @param owner the resource owner or null for any resource with a given type
* @param resourceServerId the resource server id
* @param consumer the result consumer
* @return a list of resources with the given type
*/
void findByType(String type, String owner, String resourceServerId, Consumer<Resource> consumer);
List<Resource> findByTypeInstance(String type, String resourceServerId);
void findByTypeInstance(String type, String resourceServerId, Consumer<Resource> consumer);
}

View file

@ -410,6 +410,22 @@ public class AuthorizationTokenService {
if (resource != null) {
requestedResources.add(resource);
} else if (resourceId.startsWith("resource-type:")) {
// only resource types, no resource instances. resource types are owned by the resource server
String resourceType = resourceId.substring("resource-type:".length());
resourceStore.findByType(resourceType, resourceServer.getId(), resourceServer.getId(), requestedResources::add);
} else if (resourceId.startsWith("resource-type-any:")) {
// any resource with a given type
String resourceType = resourceId.substring("resource-type-any:".length());
resourceStore.findByType(resourceType, null, resourceServer.getId(), requestedResources::add);
} else if (resourceId.startsWith("resource-type-instance:")) {
// only resource instances with a given type
String resourceType = resourceId.substring("resource-type-instance:".length());
resourceStore.findByTypeInstance(resourceType, resourceServer.getId(), requestedResources::add);
} else if (resourceId.startsWith("resource-type-owner:")) {
// only resources where the current identity is the owner
String resourceType = resourceId.substring("resource-type-owner:".length());
resourceStore.findByType(resourceType, identity.getId(), resourceServer.getId(), requestedResources::add);
} else {
String resourceName = resourceId;
Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId());

View file

@ -155,11 +155,9 @@ public final class Permissions {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
resourceStore.findByType(type, resourceServer.getId(), resource1 -> {
if (resource1.getOwner().equals(resourceServer.getId())) {
for (Scope typeScope : resource1.getScopes()) {
if (!scopes.contains(typeScope)) {
scopes.add(typeScope);
}
for (Scope typeScope : resource1.getScopes()) {
if (!scopes.contains(typeScope)) {
scopes.add(typeScope);
}
}
});

View file

@ -51,6 +51,7 @@ import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.ResourceResource;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
@ -841,6 +842,189 @@ public class EntitlementAPITest extends AbstractAuthzTest {
}
}
@Test
public void testObtainAllEntitlementsForResourceType() 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();
for (int i = 0; i < 10; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setType("type-one");
resource.setName(KeycloakModelUtils.generateId());
authorization.resources().create(resource).close();
}
for (int i = 0; i < 10; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setType("type-two");
resource.setName(KeycloakModelUtils.generateId());
authorization.resources().create(resource).close();
}
for (int i = 0; i < 10; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setType("type-three");
resource.setName(KeycloakModelUtils.generateId());
authorization.resources().create(resource).close();
}
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName(KeycloakModelUtils.generateId());
permission.setResourceType("type-one");
permission.addPolicy(policy.getName());
authorization.permissions().resource().create(permission).close();
permission = new ResourcePermissionRepresentation();
permission.setName(KeycloakModelUtils.generateId());
permission.setResourceType("type-two");
permission.addPolicy(policy.getName());
authorization.permissions().resource().create(permission).close();
permission = new ResourcePermissionRepresentation();
permission.setName(KeycloakModelUtils.generateId());
permission.setResourceType("type-three");
permission.addPolicy(policy.getName());
authorization.permissions().resource().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("resource-type:type-one");
AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
Collection<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(10, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type:type-three");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(10, permissions.size());
for (int i = 0; i < 5; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setOwner("kolo");
resource.setType("type-two");
resource.setName(KeycloakModelUtils.generateId());
authorization.resources().create(resource).close();
}
request = new AuthorizationRequest();
request.addPermission("resource-type-any:type-two");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(15, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-owner:type-two");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(5, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-instance:type-two");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(5, permissions.size());
Permission next = permissions.iterator().next();
ResourceResource resourceMgmt = client.authorization().resources().resource(next.getResourceId());
ResourceRepresentation representation = resourceMgmt.toRepresentation();
representation.setType("type-three");
resourceMgmt.update(representation);
request = new AuthorizationRequest();
request.addPermission("resource-type-instance:type-two");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(4, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-instance:type-three");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(1, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-any:type-three");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(11, permissions.size());
for (int i = 0; i < 2; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setOwner("marta");
resource.setType("type-one");
resource.setName(KeycloakModelUtils.generateId());
authorization.resources().create(resource).close();
}
request = new AuthorizationRequest();
request.addPermission("resource-type:type-one");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(10, permissions.size());
accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
request = new AuthorizationRequest();
request.addPermission("resource-type-owner:type-one");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(2, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-instance:type-one");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(2, permissions.size());
request = new AuthorizationRequest();
request.addPermission("resource-type-any:type-one");
response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken());
permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(12, permissions.size());
}
@Test
public void testOverridePermission() throws Exception {
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);