[KEYCLOAK-10713] - Pagination to resources rest api

This commit is contained in:
Pedro Igor 2019-07-25 18:04:00 -03:00 committed by Bruno Oliveira da Silva
parent fe0d6f4583
commit 967d21dbb5
14 changed files with 499 additions and 92 deletions

View file

@ -148,6 +148,7 @@ public class StoreFactoryCacheManager extends CacheManager {
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, null));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResourceNameAndGranted(resourceName, requester, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResourceNameAndGranted(resourceName, requester, null));
if (scope != null) {
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));
}

View file

@ -676,6 +676,11 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
}
@Override
public List<Resource> findByOwner(String ownerId, String resourceServerId, int first, int max) {
return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId, first, max);
}
@Override
public List<Resource> findByUri(String uri, String resourceServerId) {
if (uri == null) return null;
@ -1130,6 +1135,16 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
(revision, permissions) -> new PermissionTicketResourceListQuery(revision, cacheKey, resourceName, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public List<Resource> findGrantedResources(String requester, int first, int max) {
return getPermissionTicketStoreDelegate().findGrantedResources(requester, first, max);
}
@Override
public List<Resource> findGrantedOwnerResources(String owner, int first, int max) {
return getPermissionTicketStoreDelegate().findGrantedOwnerResources(owner, first, max);
}
@Override
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
String cacheKey = getPermissionTicketByOwner(owner, resourceServerId);

View file

@ -41,7 +41,9 @@ import javax.persistence.UniqueConstraint;
{
@NamedQuery(name="findPermissionIdByResource", query="select p.id from PermissionTicketEntity p inner join p.resource r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"),
@NamedQuery(name="findPermissionIdByScope", query="select p.id from PermissionTicketEntity p inner join p.scope s where p.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id = :scopeId)"),
@NamedQuery(name="findPermissionTicketIdByServerId", query="select p.id from PermissionTicketEntity p where p.resourceServer.id = :serverId ")
@NamedQuery(name="findPermissionTicketIdByServerId", query="select p.id from PermissionTicketEntity p where p.resourceServer.id = :serverId "),
@NamedQuery(name="findGrantedResources", query="select distinct(r.id) from ResourceEntity r inner join PermissionTicketEntity p on r.id = p.resource.id where p.grantedTimestamp is not null and p.requester = :requester order by r.id"),
@NamedQuery(name="findGrantedOwnerResources", query="select distinct(r.id) from ResourceEntity r inner join PermissionTicketEntity p on r.id = p.resource.id where p.grantedTimestamp is not null and p.owner = :owner order by r.id")
}
)
public class PermissionTicketEntity {

View file

@ -58,7 +58,9 @@ import org.hibernate.annotations.FetchMode;
@NamedQueries(
{
@NamedQuery(name="findResourceIdByOwner", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :owner"),
@NamedQuery(name="findResourceIdByOwnerOrdered", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :owner order by r.id"),
@NamedQuery(name="findAnyResourceIdByOwner", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.owner = :owner"),
@NamedQuery(name="findAnyResourceIdByOwnerOrdered", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.owner = :owner order by r.id"),
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and :uri in elements(r.uris)"),
@NamedQuery(name="findResourceIdByName", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
@NamedQuery(name="findResourceIdByType", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"),

View file

@ -36,8 +36,10 @@ import javax.persistence.criteria.Root;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
@ -262,6 +264,60 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
return find(filters, resourceServerId, -1, -1);
}
@Override
public List<Resource> findGrantedResources(String requester, int first, int max) {
TypedQuery<String> query = entityManager.createNamedQuery("findGrantedResources", String.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("requester", requester);
if (first > -1 && max > -1) {
query.setFirstResult(first);
query.setMaxResults(max);
}
List<String> result = query.getResultList();
List<Resource> list = new LinkedList<>();
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
for (String id : result) {
Resource resource = resourceStore.findById(id, null);
if (Objects.nonNull(resource)) {
list.add(resource);
}
}
return list;
}
@Override
public List<Resource> findGrantedOwnerResources(String owner, int first, int max) {
TypedQuery<String> query = entityManager.createNamedQuery("findGrantedOwnerResources", String.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("owner", owner);
if (first > -1 && max > -1) {
query.setFirstResult(first);
query.setMaxResults(max);
}
List<String> result = query.getResultList();
List<Resource> list = new LinkedList<>();
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
for (String id : result) {
Resource resource = resourceStore.findById(id, null);
if (Objects.nonNull(resource)) {
list.add(resource);
}
}
return list;
}
@Override
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByType", String.class);

View file

@ -110,10 +110,24 @@ public class JPAResourceStore implements ResourceStore {
@Override
public void findByOwner(String ownerId, String resourceServerId, Consumer<Resource> consumer) {
String queryName = "findResourceIdByOwner";
findByOwnerFilter(ownerId, resourceServerId, consumer, -1, -1);
}
@Override
public List<Resource> findByOwner(String ownerId, String resourceServerId, int first, int max) {
List<Resource> list = new LinkedList<>();
findByOwnerFilter(ownerId, resourceServerId, list::add, first, max);
return list;
}
private void findByOwnerFilter(String ownerId, String resourceServerId, Consumer<Resource> consumer, int firstResult, int maxResult) {
boolean pagination = firstResult > -1 && maxResult > -1;
String queryName = pagination ? "findResourceIdByOwnerOrdered" : "findResourceIdByOwner";
if (resourceServerId == null) {
queryName = "findAnyResourceIdByOwner";
queryName = pagination ? "findAnyResourceIdByOwnerOrdered" : "findAnyResourceIdByOwner";
}
TypedQuery<ResourceEntity> query = entityManager.createNamedQuery(queryName, ResourceEntity.class);
@ -125,11 +139,21 @@ public class JPAResourceStore implements ResourceStore {
query.setParameter("serverId", resourceServerId);
}
StoreFactory storeFactory = provider.getStoreFactory();
if (pagination) {
query.setFirstResult(firstResult);
query.setMaxResults(maxResult);
}
query.getResultList().stream()
.map(id -> new ResourceAdapter(id, entityManager, storeFactory))
.forEach(consumer);
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
List<ResourceEntity> result = query.getResultList();
for (ResourceEntity entity : result) {
Resource cached = resourceStore.findById(entity.getId(), resourceServerId);
if (cached != null) {
consumer.accept(cached);
}
}
}
@Override

View file

@ -491,6 +491,11 @@ public final class AuthorizationProvider implements Provider {
delegate.findByOwner(ownerId, resourceServerId, consumer);
}
@Override
public List<Resource> findByOwner(String ownerId, String resourceServerId, int first, int max) {
return delegate.findByOwner(ownerId, resourceServerId, first, max);
}
@Override
public List<Resource> findByUri(String uri, String resourceServerId) {
return delegate.findByUri(uri, resourceServerId);

View file

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
/**
@ -109,4 +110,24 @@ public interface PermissionTicketStore {
* @return a list of permissions granted for a particular user
*/
List<PermissionTicket> findGranted(String resourceName, String userId, String resourceServerId);
/**
* Returns a list of {@link Resource} granted to the given {@code requester}
*
* @param requester the requester
* @param first first result
* @param max max result
* @return a list of {@link Resource} granted to the given {@code requester}
*/
List<Resource> findGrantedResources(String requester, int first, int max);
/**
* Returns a list of {@link Resource} granted by the owner to other users
*
* @param owner the owner
* @param first first result
* @param max max result
* @return a list of {@link Resource} granted by the owner
*/
List<Resource> findGrantedOwnerResources(String owner, int first, int max);
}

View file

@ -77,6 +77,8 @@ public interface ResourceStore {
void findByOwner(String ownerId, String resourceServerId, Consumer<Resource> consumer);
List<Resource> findByOwner(String ownerId, String resourceServerId, int first, int max);
/**
* Finds all {@link Resource} instances with the given uri.
*

View file

@ -46,9 +46,13 @@ import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import org.apache.http.client.methods.HttpDelete;
@ -316,7 +320,24 @@ public class SimpleHttp {
public String getFirstHeader(String name) throws IOException {
readResponse();
return response.getHeaders(name)[0].getValue();
Header[] headers = response.getHeaders(name);
if (headers != null && headers.length > 0) {
return headers[0].getValue();
}
return null;
}
public List<String> getHeader(String name) throws IOException {
readResponse();
Header[] headers = response.getHeaders(name);
if (headers != null && headers.length > 0) {
return Stream.of(headers).map(Header::getValue).collect(Collectors.toList());
}
return null;
}
public void close() throws IOException {

View file

@ -70,29 +70,6 @@ public abstract class AbstractResourceService {
return Cors.add(request, response).auth().allowedOrigins(auth.getToken()).build();
}
protected Collection<ResourcePermission> getPermissions(List<PermissionTicket> tickets, boolean withRequesters) {
Map<String, ResourcePermission> permissions = new HashMap<>();
for (PermissionTicket ticket : tickets) {
ResourcePermission resource = permissions
.computeIfAbsent(ticket.getResource().getId(), s -> new ResourcePermission(ticket, provider));
if (withRequesters) {
Permission user = resource.getPermission(ticket.getRequester());
if (user == null) {
resource.addPermission(ticket.getRequester(), user = new Permission(ticket.getRequester(), provider));
}
user.addScope(ticket.getScope().getName());
} else {
resource.addScope(new Scope(ticket.getScope()));
}
}
return permissions.values();
}
public static class Resource extends ResourceRepresentation {
private Client client;
@ -135,6 +112,11 @@ public abstract class AbstractResourceService {
setScopes(new HashSet<>());
}
ResourcePermission(org.keycloak.authorization.model.Resource resource, AuthorizationProvider provider) {
super(resource, provider);
setScopes(new HashSet<>());
}
public Collection<Permission> getPermissions() {
if (permissions == null) {
return null;

View file

@ -74,14 +74,14 @@ public class ResourceService extends AbstractResourceService {
@GET
@Path("permissions")
@Produces(MediaType.APPLICATION_JSON)
public Response getPermissions() {
public Response toPermissions() {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
filters.put(PermissionTicket.RESOURCE, resource.getId());
Collection<ResourcePermission> resources = getPermissions(ticketStore.find(filters, null, -1, -1), true);
Collection<ResourcePermission> resources = toPermissions(ticketStore.find(filters, null, -1, -1));
Collection<Permission> permissions = Collections.EMPTY_LIST;
if (!resources.isEmpty()) {
@ -210,4 +210,23 @@ public class ResourceService extends AbstractResourceService {
return user;
}
private Collection<ResourcePermission> toPermissions(List<PermissionTicket> tickets) {
Map<String, ResourcePermission> permissions = new HashMap<>();
for (PermissionTicket ticket : tickets) {
ResourcePermission resource = permissions
.computeIfAbsent(ticket.getResource().getId(), s -> new ResourcePermission(ticket, provider));
Permission user = resource.getPermission(ticket.getRequester());
if (user == null) {
resource.addPermission(ticket.getRequester(), user = new Permission(ticket.getRequester(), provider));
}
user.addScope(ticket.getScope().getName());
}
return permissions.values();
}
}

View file

@ -22,13 +22,23 @@ import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.Auth;
@ -46,45 +56,48 @@ public class ResourcesService extends AbstractResourceService {
/**
* Returns a list of {@link Resource} where the {@link #user} is the resource owner.
*
* @param first the first result
* @param max the max result
* @return a list of {@link Resource} where the {@link #user} is the resource owner
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getResources() {
return cors(Response.ok(resourceStore.findByOwner(user.getId(), null).stream()
.map(resource -> new Resource(resource, user, provider))
.collect(Collectors.toList())));
public Response getResources(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
return queryResponse((f, m) -> resourceStore.findByOwner(user.getId(), null, f, m)
.stream().map(resource -> new Resource(resource, user, provider)), first, max);
}
/**
* Returns a list of {@link Resource} shared with the {@link #user}
*
* @param first the first result
* @param max the max result
* @return a list of {@link Resource} shared with the {@link #user}
*/
@GET
@Path("shared-with-me")
@Produces(MediaType.APPLICATION_JSON)
public Response getSharedWithMe() {
return cors(Response.ok(getPermissions(ticketStore.findGranted(user.getId(), null), false)));
public Response getSharedWithMe(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getUser().getId(), f, m), false)
.stream(), first, max);
}
/**
* Returns a list of {@link Resource} where the {@link #user} is the resource owner and the resource is
* shared with other users.
*
* @param first the first result
* @param max the max result
* @return a list of {@link Resource} where the {@link #user} is the resource owner and the resource is
* * shared with other users
*/
@GET
@Path("shared-with-others")
@Produces(MediaType.APPLICATION_JSON)
public Response getSharedWithOthers() {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
return cors(Response.ok(getPermissions(ticketStore.find(filters, null, -1, -1), true)));
public Response getSharedWithOthers(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
return queryResponse(
(f, m) -> toPermissions(ticketStore.findGrantedOwnerResources(auth.getUser().getId(), f, m), true)
.stream(), first, max);
}
@Path("{id}")
@ -101,4 +114,86 @@ public class ResourcesService extends AbstractResourceService {
return new ResourceService(resource, provider.getKeycloakSession(), user, auth, request);
}
private Collection<ResourcePermission> toPermissions(List<org.keycloak.authorization.model.Resource> resources, boolean withRequesters) {
Collection<ResourcePermission> permissions = new ArrayList<>();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (org.keycloak.authorization.model.Resource resource : resources) {
ResourcePermission permission = new ResourcePermission(resource, provider);
List<PermissionTicket> tickets;
if (withRequesters) {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
filters.put(PermissionTicket.RESOURCE, resource.getId());
tickets = ticketStore.find(filters, null, -1, -1);
} else {
tickets = ticketStore.findGranted(resource.getName(), user.getId(), null);
}
for (PermissionTicket ticket : tickets) {
if (resource.equals(ticket.getResource())) {
if (withRequesters) {
Permission user = permission.getPermission(ticket.getRequester());
if (user == null) {
permission.addPermission(ticket.getRequester(),
user = new Permission(ticket.getRequester(), provider));
}
user.addScope(ticket.getScope().getName());
} else {
permission.addScope(new Scope(ticket.getScope()));
}
}
}
permissions.add(permission);
}
return permissions;
}
private Response queryResponse(BiFunction<Integer, Integer, Stream<?>> query, Integer first, Integer max) {
if (first != null && max != null) {
List result = query.apply(first, max + 1).collect(Collectors.toList());
int size = result.size();
if (size > max) {
result = result.subList(0, size - 1);
}
return cors(Response.ok().entity(result).links(createPageLinks(first, max, size)));
}
return cors(Response.ok().entity(query.apply(-1, -1).collect(Collectors.toList())));
}
private Link[] createPageLinks(Integer first, Integer max, int resultSize) {
if (resultSize == 0 || (first == 0 && resultSize <= max)) {
return new Link[] {};
}
List<Link> links = new ArrayList();
boolean nextPage = resultSize > max;
if (nextPage) {
links.add(Link.fromUri(
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
.build(first + max, max))
.rel("next").build());
}
links.add(Link.fromUri(
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
.build(nextPage ? first : first - max, max))
.rel("prev").build());
return links.toArray(new Link[links.size()]);
}
}

View file

@ -18,7 +18,9 @@ package org.keycloak.testsuite.account;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import javax.ws.rs.core.Response;
import java.io.IOException;
@ -26,9 +28,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.Test;
@ -42,7 +46,6 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -152,21 +155,45 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
List<Resource> resources = getMyResources();
assertEquals(30, resources.size());
for (int i = 0; i < 30; i++) {
String uri = "http://resourceServer.com/resources/" + i;
Resource resource = resources.stream()
.filter(rep -> rep.getUris().stream().anyMatch(resourceUri -> resourceUri.equals(uri))).findAny()
.get();
assertNotNull(resource.getId());
assertEquals("Resource " + i, resource.getName());
assertEquals("Display Name " + i, resource.getDisplayName());
assertEquals("Icon Uri " + i, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertMyResourcesResponse(resources);
}
@Test
public void testGetMyResourcesPagination() {
List<Resource> resources = getMyResources(0, 10, response -> assertNextPageLink(response, "/realms/test/account/resources", 10, 10));
assertEquals(10, resources.size());
assertMyResourcesResponse(resources);
resources = getMyResources(10, 10, response -> assertNextPageLink(response, "/realms/test/account/resources", 20, 10));
assertEquals(10, resources.size());
resources = getMyResources(20, 10, response -> {
assertNextPageLink(response, "/realms/test/account/resources", 20, 10, true);
});
assertEquals(10, resources.size());
resources = getMyResources(30, 10);
assertEquals(0, resources.size());
getMyResources(30, 30, response -> {
assertNextPageLink(response, "/realms/test/account/resources", 0, 0, true, true);
});
getMyResources(30, 31, response -> {
assertNextPageLink(response, "/realms/test/account/resources", 0, 0, true, true);
});
getMyResources(0, 30, response -> {
assertNextPageLink(response, "/realms/test/account/resources", 0, 0, true, true);
});
getMyResources(0, 31, response -> {
assertNextPageLink(response, "/realms/test/account/resources", 0, 0, true, true);
});
}
@Test
@ -175,20 +202,25 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
List<AbstractResourceService.ResourcePermission> resources = getSharedWithMe(userName);
assertEquals(10, resources.size());
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(2, resource.getScopes().size());
assertSharedWithMeResponse(resources);
}
}
@Test
public void testGetSharedWithMePagination() {
for (String userName : userNames) {
List<AbstractResourceService.ResourcePermission> resources = getSharedWithMe(userName, 0, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 3, 3));
assertSharedWithMeResponse(resources);
getSharedWithMe(userName, 3, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 6, 3));
getSharedWithMe(userName, 6, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 9, 3));
getSharedWithMe(userName, 9, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 9, 3, true));
}
}
@Test
@ -198,24 +230,27 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
});
assertEquals(30, resources.size());
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(1, resource.getPermissions().size());
Permission user = resource.getPermissions().iterator().next();
assertTrue(userNames.contains(user.getUsername()));
assertEquals(2, user.getScopes().size());
assertSharedWithOthersResponse(resources);
}
@Test
public void testGetSharedWithOthersPagination() {
List<AbstractResourceService.ResourcePermission> resources = doGet("/shared-with-others?first=0&max=5",
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {
}, response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-others", 5, 5));
assertEquals(5, resources.size());
assertSharedWithOthersResponse(resources);
doGet("/shared-with-others?first=5&max=5",
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {
}, response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-others", 10, 5));
doGet("/shared-with-others?first=20&max=5",
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {
}, response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-others", 25, 5));
doGet("/shared-with-others?first=25&max=5",
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {
}, response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-others", 25, 5, true));
}
@Test
@ -287,7 +322,7 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
@Test
public void testShareResource() throws Exception {
List<String> users = Arrays.asList("jdoe", "alice");
List<String> users = new LinkedList<>(Arrays.asList("jdoe", "alice"));
List<Permission> permissions = new ArrayList<>();
AbstractResourceService.ResourcePermission sharedResource = null;
@ -514,14 +549,26 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName) {
return getSharedWithMe(userName, -1, -1, null);
}
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName, int first, int max, Consumer<SimpleHttp.Response> responseHandler) {
if (first > -1 && max > -1) {
return doGet("/shared-with-me?first=" + first + "&max=" + max, authzClient.obtainAccessToken(userName, "password").getToken(),
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {}, responseHandler);
}
return doGet("/shared-with-me", authzClient.obtainAccessToken(userName, "password").getToken(),
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {});
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {}, responseHandler);
}
private <R> R doGet(String resource, TypeReference<R> typeReference) {
return doGet(resource, tokenUtil.getToken(), typeReference);
}
private <R> R doGet(String resource, TypeReference<R> typeReference, Consumer<SimpleHttp.Response> response) {
return doGet(resource, tokenUtil.getToken(), typeReference, response);
}
private <R> R doGet(String resource, Class<R> type) {
return doGet(resource, tokenUtil.getToken(), type);
}
@ -534,6 +581,25 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
}
private <R> R doGet(String resource, String token, TypeReference<R> typeReference, Consumer<SimpleHttp.Response> responseHandler) {
try {
SimpleHttp http = get(resource, token);
http.header("Accept", "application/json");
SimpleHttp.Response response = http.asResponse();
if (responseHandler != null) {
responseHandler.accept(response);
}
R result = JsonSerialization.readValue(response.asString(), typeReference);
return result;
} catch (IOException cause) {
throw new RuntimeException("Failed to fetch resource", cause);
}
}
private <R> R doGet(String resource, String token, Class<R> type) {
try {
return get(resource, token).asJson(type);
@ -567,6 +633,102 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
private List<Resource> getMyResources() {
return getMyResources(-1, -1);
}
private List<Resource> getMyResources(int first, int max) {
if (first > -1 && max > -1) {
return doGet("?first=" + first + "&max=" + max, new TypeReference<List<Resource>>() {});
}
return doGet("", new TypeReference<List<Resource>>() {});
}
private List<Resource> getMyResources(int first, int max, Consumer<SimpleHttp.Response> response) {
if (first > -1 && max > -1) {
return doGet("?first=" + first + "&max=" + max, new TypeReference<List<Resource>>() {}, response);
}
return doGet("", new TypeReference<List<Resource>>() {}, response);
}
private void assertSharedWithOthersResponse(List<AbstractResourceService.ResourcePermission> resources) {
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(1, resource.getPermissions().size());
Permission user = resource.getPermissions().iterator().next();
assertTrue(userNames.contains(user.getUsername()));
assertEquals(2, user.getScopes().size());
}
}
private void assertMyResourcesResponse(List<Resource> resources) {
for (Resource resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
}
}
private void assertSharedWithMeResponse(List<AbstractResourceService.ResourcePermission> resources) {
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(2, resource.getScopes().size());
}
}
private void assertNextPageLink(SimpleHttp.Response response, String uri, int first, int max) {
assertNextPageLink(response, uri, first, max, false);
}
private void assertNextPageLink(SimpleHttp.Response response, String uri, int first, int max, boolean lastPage) {
assertNextPageLink(response, uri, first, max, lastPage, false);
}
private void assertNextPageLink(SimpleHttp.Response response, String uri, int first, int max, boolean lastPage, boolean singlePage) {
try {
List<String> links = response.getHeader("Link");
if (singlePage) {
assertNull(links);
return;
}
assertNotNull(links);
assertEquals(lastPage ? 1 : 2, links.size());
for (String link : links) {
if (link.contains("rel=\"next\"")) {
assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + first + "&max=" + max + ">; rel=\"next\"", link);
} else {
assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + (first - max) + "&max=" + max + ">; rel=\"prev\"", link);
}
}
} catch (IOException e) {
fail("Fail to get link header");
}
}
}