From 4cd0a8e894896c0099157833dda49e512760a4fe Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 18 Aug 2016 13:29:54 -0300 Subject: [PATCH] [KEYCLOAK-3377] - Add pagination to authorization UI --- .../infinispan/CachedPolicyStore.java | 5 + .../infinispan/CachedResourceStore.java | 6 + .../infinispan/CachedScopeStore.java | 6 + .../jpa/store/JPAPolicyStore.java | 43 ++++- .../jpa/store/JPAResourceStore.java | 37 ++++ .../jpa/store/JPAScopeStore.java | 33 ++++ .../mongo/store/MongoPolicyStore.java | 27 +++ .../mongo/store/MongoResourceStore.java | 25 ++- .../mongo/store/MongoScopeStore.java | 18 ++ .../authorization/store/PolicyStore.java | 12 +- .../authorization/store/ResourceStore.java | 12 +- .../authorization/store/ScopeStore.java | 13 +- .../authorization/admin/PolicyService.java | 62 ++++++- .../admin/ResourceSetService.java | 62 ++++++- .../authorization/admin/ScopeService.java | 15 +- .../protection/resource/ResourceService.java | 2 +- .../resources/js/authz/authz-controller.js | 170 ++++++++++++++++-- .../resource-server-permission-list.html | 28 ++- .../policy/resource-server-policy-list.html | 28 ++- .../authz/resource-server-resource-list.html | 39 +++- .../authz/resource-server-scope-list.html | 16 +- 21 files changed, 606 insertions(+), 53 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java index 4657633d67..b503bcef77 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java @@ -103,6 +103,11 @@ public class CachedPolicyStore implements PolicyStore { return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + @Override public List findByResource(String resourceId) { List cache = new ArrayList<>(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index 4d9a946983..f86de2f067 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -32,6 +32,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedResource; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; @@ -126,6 +127,11 @@ public class CachedResourceStore implements ResourceStore { return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + @Override public List findByScope(String... id) { return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java index 72f3f25ec1..f86a7d1bb8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java @@ -30,6 +30,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedScope; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; /** @@ -114,6 +115,11 @@ public class CachedScopeStore implements ScopeStore { return getDelegate().findByResourceServer(id); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + private String getCacheKeyForScope(String id) { return SCOPE_ID_CACHE_PREFIX + id; } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java index 8b88ad16b7..b57cd1efd5 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java @@ -27,11 +27,15 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -100,6 +104,43 @@ public class JPAPolicyStore implements PolicyStore { return query.getResultList(); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(PolicyEntity.class); + Root root = querybuilder.from(PolicyEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + if ("permission".equals(name)) { + if (Boolean.valueOf(value[0])) { + predicates.add(root.get("type").in("resource", "scope")); + } else { + predicates.add(builder.not(root.get("type").in("resource", "scope"))); + } + } else if ("id".equals(name)) { + predicates.add(root.get(name).in(value)); + } else { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + } + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } + @Override public List findByResource(final String resourceId) { Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId"); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java index 986d007808..6d00bb66d4 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java @@ -26,8 +26,14 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -96,6 +102,37 @@ public class JPAResourceStore implements ResourceStore { return query.getResultList(); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(ResourceEntity.class); + Root root = querybuilder.from(ResourceEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + if ("scope".equals(name)) { + predicates.add(root.join("scopes").get("id").in(value)); + } else { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + } + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } + @Override public List findByScope(String... id) { Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)"); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java index cc9a956498..d468314374 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java @@ -27,7 +27,13 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -85,4 +91,31 @@ public class JPAScopeStore implements ScopeStore { return query.getResultList(); } + + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(ScopeEntity.class); + Root root = querybuilder.from(ScopeEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java index 6f0ba5dcd7..04a3d9ac4b 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java @@ -32,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -101,6 +103,31 @@ public class MongoPolicyStore implements PolicyStore { .collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + if ("permission".equals(name)) { + if (Boolean.valueOf(value[0])) { + queryBuilder.and("type").in(new String[] {"resource", "scope"}); + } else { + queryBuilder.and("type").notIn(new String[] {"resource", "scope"}); + } + } else if ("id".equals(name)) { + queryBuilder.and("_id").in(value); + } else { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + } + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(PolicyEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(policy -> findById(policy.getId())).collect(toList()); + } + @Override public List findByResource(String resourceId) { DBObject query = new QueryBuilder() diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java index 11b735ba6c..a85de72970 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java @@ -18,6 +18,7 @@ package org.keycloak.authorization.mongo.store; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.authorization.AuthorizationProvider; @@ -26,12 +27,13 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.mongo.adapter.ResourceAdapter; import org.keycloak.authorization.mongo.entities.ResourceEntity; import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -98,10 +100,29 @@ public class MongoResourceStore implements ResourceStore { .map(scope -> findById(scope.getId())).collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + if ("scope".equals(name)) { + queryBuilder.and("scopes").in(value); + } else { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + } + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(ResourceEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(scope -> findById(scope.getId())).collect(toList()); + } + @Override public List findByScope(String... id) { DBObject query = new QueryBuilder() - .and("scopes.id").in(id) + .and("scopes").in(id) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java index e57b69bf6e..4b7edd6a2f 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java @@ -18,6 +18,7 @@ package org.keycloak.authorization.mongo.store; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.authorization.AuthorizationProvider; @@ -31,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -98,6 +101,21 @@ public class MongoScopeStore implements ScopeStore { .collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(ScopeEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(scope -> findById(scope.getId())).collect(toList()); + } + private MongoStoreInvocationContext getInvocationContext() { return this.invocationContext; } diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java index f55db9971e..51c7dd71ec 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java @@ -19,11 +19,10 @@ package org.keycloak.authorization.store; import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; import java.util.List; +import java.util.Map; /** * A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances. @@ -75,6 +74,15 @@ public interface PolicyStore { */ List findByResourceServer(String resourceServerId); + /** + * Returns a list of {@link Policy} associated with a {@link ResourceServer} with the given resourceServerId. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of a resource server + * @return a list of policies that belong to the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); + /** * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given resourceId. * diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java index 5b92808b1e..f06be7853f 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java @@ -19,10 +19,9 @@ package org.keycloak.authorization.store; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; import java.util.List; -import java.util.Set; +import java.util.Map; /** * A {@link ResourceStore} is responsible to manage the persistence of {@link Resource} instances. @@ -72,6 +71,15 @@ public interface ResourceStore { */ List findByResourceServer(String resourceServerId); + /** + * Finds all {@link Resource} instances associated with a given resource server. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of the resource server + * @return a list with all resources associated with the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); + /** * Finds all {@link Resource} associated with a given scope. * diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java index 501217f5e7..81a7064fc7 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java @@ -22,6 +22,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import java.util.List; +import java.util.Map; /** * A {@link ScopeStore} is responsible to manage the persistence of {@link Scope} instances. @@ -75,4 +76,14 @@ public interface ScopeStore { * @return a list of scopes that belong to the given resource server */ List findByResourceServer(String id); -} + + /** + * Returns a list of {@link Scope} associated with a {@link ResourceServer} with the given resourceServerId. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of a resource server + * + * @return a list of scopes that belong to the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index d7e6dee2a4..3b9c1942c5 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -26,8 +26,10 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Consumes; @@ -41,6 +43,12 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -188,11 +196,54 @@ public class PolicyService { @GET @Produces("application/json") @NoCache - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("type") String type, + @QueryParam("resource") String resource, + @QueryParam("permission") Boolean permission, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { this.auth.requireView(); + + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + + if (type != null && !"".equals(type.trim())) { + search.put("type", new String[] {type}); + } + StoreFactory storeFactory = authorization.getStoreFactory(); + + if (resource != null && !"".equals(resource.trim())) { + List policies = new ArrayList<>(); + HashMap resourceSearch = new HashMap<>(); + + resourceSearch.put("name", new String[] {resource}); + + storeFactory.getResourceStore().findByResourceServer(resourceSearch, resourceServer.getId(), -1, -1).forEach(resource1 -> { + ResourceRepresentation resourceRepresentation = ModelToRepresentation.toRepresentation(resource1, resourceServer, authorization); + resourceRepresentation.getPolicies().forEach(policyRepresentation -> { + Policy associated = storeFactory.getPolicyStore().findById(policyRepresentation.getId()); + policies.add(associated); + findAssociatedPolicies(associated, policies); + }); + }); + + if (policies.isEmpty()) { + return Response.ok(Collections.emptyList()).build(); + } + + search.put("id", policies.stream().map(Policy::getId).toArray(String[]::new)); + } + + if (permission != null) { + search.put("permission", new String[] {permission.toString()}); + } + return Response.ok( - storeFactory.getPolicyStore().findByResourceServer(resourceServer.getId()).stream() + storeFactory.getPolicyStore().findByResourceServer(search, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(policy -> toRepresentation(policy, authorization)) .collect(Collectors.toList())) .build(); @@ -244,4 +295,11 @@ public class PolicyService { return null; } + + private void findAssociatedPolicies(Policy policy, List policies) { + policy.getAssociatedPolicies().forEach(associated -> { + policies.add(associated); + findAssociatedPolicies(associated, policies); + }); + } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index c628a82f3e..d31146f7d4 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -22,9 +22,13 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.RealmAuth; @@ -40,7 +44,10 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -170,12 +177,63 @@ public class ResourceSetService { @GET @NoCache @Produces("application/json") - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("uri") String uri, + @QueryParam("owner") String owner, + @QueryParam("type") String type, + @QueryParam("scope") String scope, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { requireView(); StoreFactory storeFactory = authorization.getStoreFactory(); + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + + if (uri != null && !"".equals(uri.trim())) { + search.put("uri", new String[] {uri}); + } + + if (owner != null && !"".equals(owner.trim())) { + RealmModel realm = authorization.getKeycloakSession().getContext().getRealm(); + ClientModel clientModel = realm.getClientByClientId(owner); + + if (clientModel != null) { + owner = clientModel.getId(); + } else { + UserModel user = authorization.getKeycloakSession().users().getUserByUsername(owner, realm); + + if (user != null) { + owner = user.getId(); + } + } + + search.put("owner", new String[] {owner}); + } + + if (type != null && !"".equals(type.trim())) { + search.put("type", new String[] {type}); + } + + if (scope != null && !"".equals(scope.trim())) { + HashMap scopeFilter = new HashMap<>(); + + scopeFilter.put("name", new String[] {scope}); + + List scopes = authorization.getStoreFactory().getScopeStore().findByResourceServer(scopeFilter, resourceServer.getId(), -1, -1); + + if (scopes.isEmpty()) { + return Response.ok(Collections.emptyList()).build(); + } + + search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new)); + } + return Response.ok( - storeFactory.getResourceStore().findByResourceServer(this.resourceServer.getId()).stream() + storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(resource -> toRepresentation(resource, this.resourceServer, authorization)) .collect(Collectors.toList())) .build(); diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index df0e6daee4..44464b4fe9 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -41,7 +41,9 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -158,10 +160,19 @@ public class ScopeService { @GET @Produces("application/json") - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { this.auth.requireView(); + + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + return Response.ok( - this.authorization.getStoreFactory().getScopeStore().findByResourceServer(this.resourceServer.getId()).stream() + this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(scope -> toRepresentation(scope, this.authorization)) .collect(Collectors.toList())) .build(); diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index 168d62ba6e..b02a935238 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -109,7 +109,7 @@ public class ResourceService { } private Set findAll() { - Response response = this.resourceManager.findAll(); + Response response = this.resourceManager.findAll(null, null, null, null, null, -1, -1); List resources = (List) response.getEntity(); return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()); } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index ec37854c9c..9c67740e61 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -83,6 +83,13 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.realm = realm; $scope.client = client; + $scope.query = { + realm: realm.realm, + client : client.id, + max : 20, + first : 0 + }; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -93,10 +100,35 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/resource/create').search({rsrid: resource._id}); } - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); + $scope.searchQuery(); }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.resources = ResourceServerResource.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) { @@ -234,20 +266,52 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.realm = realm; $scope.client = client; + $scope.query = { + realm: realm.realm, + client : client.id, + max : 20, + first : 0 + }; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id }, function(data) { $scope.server = data; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - $scope.createPolicy = function(scope) { $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id}); } + + $scope.searchQuery(); }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.scopes = ResourceServerScope.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) { @@ -364,6 +428,14 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.client = client; $scope.policyProviders = []; + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + max : 20, + first : 0 + }; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -380,8 +452,35 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l client : client.id }, function(data) { $scope.server = data; + $scope.searchQuery(); + }); - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { + $scope.addPolicy = function(policyType) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPolicy.query($scope.query, function(data) { $scope.policies = []; for (i = 0; i < data.length; i++) { @@ -389,12 +488,11 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.policies.push(data[i]); } } - }); - }); - $scope.addPolicy = function(policyType) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); - } + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) { @@ -402,6 +500,14 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.client = client; $scope.policyProviders = []; + $scope.query = { + realm: realm.realm, + client : client.id, + permission: true, + max : 20, + first : 0 + }; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -418,8 +524,35 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route client : client.id }, function(data) { $scope.server = data; + $scope.searchQuery(); + }); - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { + $scope.addPolicy = function(policyType) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPolicy.query($scope.query, function(data) { $scope.policies = []; for (i = 0; i < data.length; i++) { @@ -427,12 +560,11 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.policies.push(data[i]); } } - }); - }); - $scope.addPolicy = function(policyType) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); - } + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html index a41fe181ca..bfd1b22464 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html @@ -10,12 +10,21 @@
{{:: 'filter' | translate}}:  
- + +
+ +
- +
+ +
+
+
+
@@ -36,6 +45,17 @@ {{:: 'authz-associated-policies' | translate}} + + + +
+ + + +
+ + + {{policy.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html index 34240b6f9c..84600c097c 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html @@ -10,12 +10,21 @@
{{:: 'filter' | translate}}:  
- + +
+ +
- +
+ +
+
+
+
@@ -35,6 +44,17 @@ {{:: 'type' | translate}} + + + +
+ + + +
+ + + {{policy.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html index 40b2cf9a4f..b1c3978988 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html @@ -10,22 +10,34 @@ {{:: 'filter' | translate}}:  
- +
- +
- +
- +
- + +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
@@ -45,6 +57,17 @@ {{:: 'actions' | translate}} + + + +
+ + + +
+ + + {{resource.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html index 39cfc776f3..78861293c9 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html @@ -9,13 +9,12 @@
- +
- +
- @@ -29,6 +28,17 @@ {{:: 'actions' | translate}} + + + +
+ + + +
+ + + {{scope.name}}