Merge pull request #3152 from pedroigor/KEYCLOAK-3377

[KEYCLOAK-3377] - Add pagination to authorization UI
This commit is contained in:
Pedro Igor 2016-08-18 19:43:34 -03:00 committed by GitHub
commit fa1fb3a3a9
21 changed files with 606 additions and 53 deletions

View file

@ -103,6 +103,11 @@ public class CachedPolicyStore implements PolicyStore {
return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
} }
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override @Override
public List<Policy> findByResource(String resourceId) { public List<Policy> findByResource(String resourceId) {
List<Policy> cache = new ArrayList<>(); List<Policy> cache = new ArrayList<>();

View file

@ -32,6 +32,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedResource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; 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()); return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
} }
@Override
public List<Resource> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override @Override
public List<Resource> findByScope(String... id) { public List<Resource> findByScope(String... id) {
return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());

View file

@ -30,6 +30,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedScope;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
/** /**
@ -114,6 +115,11 @@ public class CachedScopeStore implements ScopeStore {
return getDelegate().findByResourceServer(id); return getDelegate().findByResourceServer(id);
} }
@Override
public List<Scope> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
private String getCacheKeyForScope(String id) { private String getCacheKeyForScope(String id) {
return SCOPE_ID_CACHE_PREFIX + id; return SCOPE_ID_CACHE_PREFIX + id;
} }

View file

@ -27,11 +27,15 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.Query; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -100,6 +104,43 @@ public class JPAPolicyStore implements PolicyStore {
return query.getResultList(); return query.getResultList();
} }
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<PolicyEntity> querybuilder = builder.createQuery(PolicyEntity.class);
Root<PolicyEntity> root = querybuilder.from(PolicyEntity.class);
List<Predicate> 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 @Override
public List<Policy> findByResource(final String resourceId) { public List<Policy> findByResource(final String resourceId) {
Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId"); Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId");

View file

@ -26,8 +26,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Query; 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.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -96,6 +102,37 @@ public class JPAResourceStore implements ResourceStore {
return query.getResultList(); return query.getResultList();
} }
@Override
public List findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ResourceEntity> querybuilder = builder.createQuery(ResourceEntity.class);
Root<ResourceEntity> root = querybuilder.from(ResourceEntity.class);
List<Predicate> 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 @Override
public List<Resource> findByScope(String... id) { public List<Resource> findByScope(String... id) {
Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)"); Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");

View file

@ -27,7 +27,13 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.Query; 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.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -85,4 +91,31 @@ public class JPAScopeStore implements ScopeStore {
return query.getResultList(); return query.getResultList();
} }
@Override
public List<Scope> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ScopeEntity> querybuilder = builder.createQuery(ScopeEntity.class);
Root<ScopeEntity> root = querybuilder.from(ScopeEntity.class);
List<Predicate> 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();
}
} }

View file

@ -32,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -101,6 +103,31 @@ public class MongoPolicyStore implements PolicyStore {
.collect(toList()); .collect(toList());
} }
@Override
public List<Policy> findByResourceServer(Map<String, String[]> 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 @Override
public List<Policy> findByResource(String resourceId) { public List<Policy> findByResource(String resourceId) {
DBObject query = new QueryBuilder() DBObject query = new QueryBuilder()

View file

@ -18,6 +18,7 @@
package org.keycloak.authorization.mongo.store; package org.keycloak.authorization.mongo.store;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.QueryBuilder; import com.mongodb.QueryBuilder;
import org.keycloak.authorization.AuthorizationProvider; 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.adapter.ResourceAdapter;
import org.keycloak.authorization.mongo.entities.ResourceEntity; import org.keycloak.authorization.mongo.entities.ResourceEntity;
import org.keycloak.authorization.store.ResourceStore; 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.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -98,10 +100,29 @@ public class MongoResourceStore implements ResourceStore {
.map(scope -> findById(scope.getId())).collect(toList()); .map(scope -> findById(scope.getId())).collect(toList());
} }
@Override
public List<Resource> findByResourceServer(Map<String, String[]> 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 @Override
public List<Resource> findByScope(String... id) { public List<Resource> findByScope(String... id) {
DBObject query = new QueryBuilder() DBObject query = new QueryBuilder()
.and("scopes.id").in(id) .and("scopes").in(id)
.get(); .get();
return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream()

View file

@ -18,6 +18,7 @@
package org.keycloak.authorization.mongo.store; package org.keycloak.authorization.mongo.store;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.QueryBuilder; import com.mongodb.QueryBuilder;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
@ -31,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -98,6 +101,21 @@ public class MongoScopeStore implements ScopeStore {
.collect(toList()); .collect(toList());
} }
@Override
public List<Scope> findByResourceServer(Map<String, String[]> 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() { private MongoStoreInvocationContext getInvocationContext() {
return this.invocationContext; return this.invocationContext;
} }

View file

@ -19,11 +19,10 @@ package org.keycloak.authorization.store;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances. * A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances.
@ -75,6 +74,15 @@ public interface PolicyStore {
*/ */
List<Policy> findByResourceServer(String resourceServerId); List<Policy> findByResourceServer(String resourceServerId);
/**
* Returns a list of {@link Policy} associated with a {@link ResourceServer} with the given <code>resourceServerId</code>.
*
* @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<Policy> findByResourceServer(Map<String, String[]> 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 <code>resourceId</code>. * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given <code>resourceId</code>.
* *

View file

@ -19,10 +19,9 @@ package org.keycloak.authorization.store;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import java.util.List; 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. * A {@link ResourceStore} is responsible to manage the persistence of {@link Resource} instances.
@ -72,6 +71,15 @@ public interface ResourceStore {
*/ */
List<Resource> findByResourceServer(String resourceServerId); List<Resource> 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<Resource> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult);
/** /**
* Finds all {@link Resource} associated with a given scope. * Finds all {@link Resource} associated with a given scope.
* *

View file

@ -22,6 +22,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* A {@link ScopeStore} is responsible to manage the persistence of {@link Scope} instances. * 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 * @return a list of scopes that belong to the given resource server
*/ */
List<Scope> findByResourceServer(String id); List<Scope> findByResourceServer(String id);
/**
* Returns a list of {@link Scope} associated with a {@link ResourceServer} with the given <code>resourceServerId</code>.
*
* @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<Scope> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult);
} }

View file

@ -26,8 +26,10 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory; 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.PolicyProviderRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.services.resources.admin.RealmAuth; import org.keycloak.services.resources.admin.RealmAuth;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -41,6 +43,12 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; 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 java.util.stream.Collectors;
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
@ -188,11 +196,54 @@ public class PolicyService {
@GET @GET
@Produces("application/json") @Produces("application/json")
@NoCache @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(); this.auth.requireView();
Map<String, String[]> 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(); StoreFactory storeFactory = authorization.getStoreFactory();
if (resource != null && !"".equals(resource.trim())) {
List<Policy> policies = new ArrayList<>();
HashMap<String, String[]> 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( 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)) .map(policy -> toRepresentation(policy, authorization))
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();
@ -244,4 +295,11 @@ public class PolicyService {
return null; return null;
} }
private void findAssociatedPolicies(Policy policy, List<Policy> policies) {
policy.getAssociatedPolicies().forEach(associated -> {
policies.add(associated);
findAssociatedPolicies(associated, policies);
});
}
} }

View file

@ -22,9 +22,13 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory; 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.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.RealmAuth; 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.QueryParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
@ -170,12 +177,63 @@ public class ResourceSetService {
@GET @GET
@NoCache @NoCache
@Produces("application/json") @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(); requireView();
StoreFactory storeFactory = authorization.getStoreFactory(); StoreFactory storeFactory = authorization.getStoreFactory();
Map<String, String[]> 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<String, String[]> scopeFilter = new HashMap<>();
scopeFilter.put("name", new String[] {scope});
List<Scope> 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( 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)) .map(resource -> toRepresentation(resource, this.resourceServer, authorization))
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();

View file

@ -41,7 +41,9 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
@ -158,10 +160,19 @@ public class ScopeService {
@GET @GET
@Produces("application/json") @Produces("application/json")
public Response findAll() { public Response findAll(@QueryParam("name") String name,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
this.auth.requireView(); this.auth.requireView();
Map<String, String[]> search = new HashMap<>();
if (name != null && !"".equals(name.trim())) {
search.put("name", new String[] {name});
}
return Response.ok( 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)) .map(scope -> toRepresentation(scope, this.authorization))
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();

View file

@ -109,7 +109,7 @@ public class ResourceService {
} }
private Set<String> findAll() { private Set<String> findAll() {
Response response = this.resourceManager.findAll(); Response response = this.resourceManager.findAll(null, null, null, null, null, -1, -1);
List<ResourceRepresentation> resources = (List<ResourceRepresentation>) response.getEntity(); List<ResourceRepresentation> resources = (List<ResourceRepresentation>) response.getEntity();
return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()); return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet());
} }

View file

@ -83,6 +83,13 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route,
$scope.realm = realm; $scope.realm = realm;
$scope.client = client; $scope.client = client;
$scope.query = {
realm: realm.realm,
client : client.id,
max : 20,
first : 0
};
ResourceServer.get({ ResourceServer.get({
realm : $route.current.params.realm, realm : $route.current.params.realm,
client : client.id 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}); $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.searchQuery();
$scope.resources = data;
}); });
$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) { 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.realm = realm;
$scope.client = client; $scope.client = client;
$scope.query = {
realm: realm.realm,
client : client.id,
max : 20,
first : 0
};
ResourceServer.get({ ResourceServer.get({
realm : $route.current.params.realm, realm : $route.current.params.realm,
client : client.id client : client.id
}, function(data) { }, function(data) {
$scope.server = data; $scope.server = data;
ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
$scope.scopes = data;
});
$scope.createPolicy = function(scope) { $scope.createPolicy = function(scope) {
$location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id}); $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) { 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.client = client;
$scope.policyProviders = []; $scope.policyProviders = [];
$scope.query = {
realm: realm.realm,
client : client.id,
permission: false,
max : 20,
first : 0
};
PolicyProvider.query({ PolicyProvider.query({
realm : $route.current.params.realm, realm : $route.current.params.realm,
client : client.id client : client.id
@ -380,8 +452,35 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l
client : client.id client : client.id
}, function(data) { }, function(data) {
$scope.server = 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 = []; $scope.policies = [];
for (i = 0; i < data.length; i++) { 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.policies.push(data[i]);
} }
} }
});
});
$scope.addPolicy = function(policyType) { $scope.searchLoaded = true;
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); $scope.lastSearch = $scope.query.search;
} });
};
}); });
module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) { 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.client = client;
$scope.policyProviders = []; $scope.policyProviders = [];
$scope.query = {
realm: realm.realm,
client : client.id,
permission: true,
max : 20,
first : 0
};
PolicyProvider.query({ PolicyProvider.query({
realm : $route.current.params.realm, realm : $route.current.params.realm,
client : client.id client : client.id
@ -418,8 +524,35 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route
client : client.id client : client.id
}, function(data) { }, function(data) {
$scope.server = 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 = []; $scope.policies = [];
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {
@ -427,12 +560,11 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route
$scope.policies.push(data[i]); $scope.policies.push(data[i]);
} }
} }
});
});
$scope.addPolicy = function(policyType) { $scope.searchLoaded = true;
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); $scope.lastSearch = $scope.query.search;
} });
};
}); });
module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) { module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) {

View file

@ -10,12 +10,21 @@
<div class="form-group"> <div class="form-group">
{{:: 'filter' | translate}}:&nbsp;&nbsp; {{:: 'filter' | translate}}:&nbsp;&nbsp;
<div class="input-group"> <div class="input-group">
<input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}"> <input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="query.name" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('policySearch').click()">
<div class="input-group-addon">
<i class="fa fa-search" id="policySearch" type="submit" data-ng-click="firstPage()"></i>
</div>
</div> </div>
<div class="input-group"> <div class="input-group">
<select class="form-control search" data-ng-model="search.type" <input type="text" placeholder="{{:: 'authz-resource' | translate}}" data-ng-model="query.resource" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('policySearch').click()">
ng-options="p.type as p.name group by p.group for p in policyProviders track by p.type"> <div class="input-group-addon">
<option value="" selected ng-click="search.type = ''">{{:: 'authz-all-types' | translate}}</option> <i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</div>
</div>
<div class="input-group">
<select class="form-control search" data-ng-model="query.type"
ng-options="p.type as p.name group by p.group for p in policyProviders track by p.type" data-ng-change="firstPage()">
<option value="" selected ng-click="query.type = ''">{{:: 'authz-all-types' | translate}}</option>
</select> </select>
</div> </div>
</div> </div>
@ -36,6 +45,17 @@
<th>{{:: 'authz-associated-policies' | translate}}</th> <th>{{:: 'authz-associated-policies' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tfoot data-ng-show="policies && (policies.length >= query.max || query.first > 0)">
<tr>
<td colspan="7">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
<button data-ng-click="nextPage()" class="next" ng-disabled="policies.length < query.max">{{:: 'next-page' | translate}}</button>
</div>
</td>
</tr>
</tfoot>
<tbody> <tbody>
<tr ng-repeat="policy in policies | filter: {name: search.name, type: search.type} | orderBy:'name'"> <tr ng-repeat="policy in policies | filter: {name: search.name, type: search.type} | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a></td>

View file

@ -10,12 +10,21 @@
<div class="form-group"> <div class="form-group">
{{:: 'filter' | translate}}:&nbsp;&nbsp; {{:: 'filter' | translate}}:&nbsp;&nbsp;
<div class="input-group"> <div class="input-group">
<input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}"> <input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="query.name" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('policySearch').click()">
<div class="input-group-addon">
<i class="fa fa-search" id="policySearch" type="submit" data-ng-click="firstPage()"></i>
</div>
</div> </div>
<div class="input-group"> <div class="input-group">
<select class="form-control search" data-ng-model="search.type" <input type="text" placeholder="{{:: 'authz-resource' | translate}}" data-ng-model="query.resource" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('policySearch').click()">
ng-options="p.type as p.name group by p.group for p in policyProviders track by p.type"> <div class="input-group-addon">
<option value="" selected ng-click="search.type = ''">{{:: 'authz-all-types' | translate}}</option> <i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</div>
</div>
<div class="input-group">
<select class="form-control search" data-ng-model="query.type"
ng-options="p.type as p.name group by p.group for p in policyProviders track by p.type" data-ng-change="firstPage()">
<option value="" selected ng-click="query.type = ''">{{:: 'authz-all-types' | translate}}</option>
</select> </select>
</div> </div>
</div> </div>
@ -35,6 +44,17 @@
<th>{{:: 'type' | translate}}</th> <th>{{:: 'type' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tfoot data-ng-show="policies && (policies.length >= query.max || query.first > 0)">
<tr>
<td colspan="7">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
<button data-ng-click="nextPage()" class="next" ng-disabled="policies.length < query.max">{{:: 'next-page' | translate}}</button>
</div>
</td>
</tr>
</tfoot>
<tbody> <tbody>
<tr ng-repeat="policy in policies | filter: {name: search.name, type: search.type} | orderBy:'name'"> <tr ng-repeat="policy in policies | filter: {name: search.name, type: search.type} | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{policy.type}}/{{policy.id}}">{{policy.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{policy.type}}/{{policy.id}}">{{policy.name}}</a></td>

View file

@ -10,22 +10,34 @@
{{:: 'filter' | translate}}:&nbsp;&nbsp; {{:: 'filter' | translate}}:&nbsp;&nbsp;
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}"> <input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="query.name" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('resourceSearch').click()">
<div class="input-group-addon"> <div class="input-group-addon">
<i class="fa fa-search" type="submit"></i> <i class="fa fa-search" id="resourceSearch" type="submit" data-ng-click="firstPage()"></i>
</div> </div>
</div> </div>
<div class="input-group"> <div class="input-group">
<input type="text" placeholder="{{:: 'authz-owner' | translate}}" data-ng-model="search.owner.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}"> <input type="text" placeholder="{{:: 'type' | translate}}" data-ng-model="query.type" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('resourceSearch').click()">
<div class="input-group-addon"> <div class="input-group-addon">
<i class="fa fa-search" type="submit"></i> <i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</div> </div>
</div> </div>
<div class="input-group"> <div class="input-group">
<select class="form-control search" data-ng-model="search.type" <input type="text" placeholder="{{:: 'authz-uri' | translate}}" data-ng-model="query.uri" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('resourceSearch').click()">
ng-options="r.type as r.type for r in resources | unique : 'type'"> <div class="input-group-addon">
<option value="" selected ng-click="search.type = ''">{{:: 'type' | translate}}</option> <i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</select> </div>
</div>
<div class="input-group">
<input type="text" placeholder="{{:: 'authz-owner' | translate}}" data-ng-model="query.owner" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('resourceSearch').click()">
<div class="input-group-addon">
<i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</div>
</div>
<div class="input-group">
<input type="text" placeholder="{{:: 'authz-scope' | translate}}" data-ng-model="query.scope" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('resourceSearch').click()">
<div class="input-group-addon">
<i class="fa fa-search" type="submit" data-ng-click="firstPage()"></i>
</div>
</div> </div>
</div> </div>
@ -45,6 +57,17 @@
<th>{{:: 'actions' | translate}}</th> <th>{{:: 'actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tfoot data-ng-show="resources && (resources.length >= query.max || query.first > 0)">
<tr>
<td colspan="7">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
<button data-ng-click="nextPage()" class="next" ng-disabled="resources.length < query.max">{{:: 'next-page' | translate}}</button>
</div>
</td>
</tr>
</tfoot>
<tbody> <tbody>
<tr ng-repeat="resource in resources | filter:search | orderBy:'name'"> <tr ng-repeat="resource in resources | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a></td>

View file

@ -9,13 +9,12 @@
<div class="form-inline"> <div class="form-inline">
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}"> <input type="text" placeholder="{{:: 'name' | translate}}" data-ng-model="query.name" class="form-control search" onkeydown="if (event.keyCode == 13) document.getElementById('scopeSearch').click()">
<div class="input-group-addon"> <div class="input-group-addon">
<i class="fa fa-search" type="submit"></i> <i class="fa fa-search" id="scopeSearch" type="submit" data-ng-click="firstPage()"></i>
</div> </div>
</div> </div>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<a id="createScope" class="btn btn-default" href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/create">{{:: 'create' | translate}}</a> <a id="createScope" class="btn btn-default" href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/create">{{:: 'create' | translate}}</a>
</div> </div>
@ -29,6 +28,17 @@
<th>{{:: 'actions' | translate}}</th> <th>{{:: 'actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tfoot data-ng-show="scopes && (scopes.length >= query.max || query.first > 0)">
<tr>
<td colspan="7">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
<button data-ng-click="nextPage()" class="next" ng-disabled="scopes.length < query.max">{{:: 'next-page' | translate}}</button>
</div>
</td>
</tr>
</tfoot>
<tbody> <tbody>
<tr ng-repeat="scope in scopes | filter:search | orderBy:'name'"> <tr ng-repeat="scope in scopes | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>