[KEYCLOAK-8915] - Support resource type in authorization requests
This commit is contained in:
parent
a58a0e7678
commit
61eb94c674
9 changed files with 346 additions and 8 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue