[KEYCLOAK-10714] - Add filtering support in My Resources endpoint by name

This commit is contained in:
Pedro Igor 2019-09-26 19:43:24 -03:00 committed by Stian Thorgersen
parent ab2bb31505
commit 17785dac08
6 changed files with 100 additions and 35 deletions

View file

@ -1136,8 +1136,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
}
@Override
public List<Resource> findGrantedResources(String requester, int first, int max) {
return getPermissionTicketStoreDelegate().findGrantedResources(requester, first, max);
public List<Resource> findGrantedResources(String requester, String name, int first, int max) {
return getPermissionTicketStoreDelegate().findGrantedResources(requester, name, first, max);
}
@Override

View file

@ -43,6 +43,7 @@ import javax.persistence.UniqueConstraint;
@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="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="findGrantedResourcesByName", 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 and lower(r.name) like :resourceName 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")
}
)

View file

@ -266,12 +266,18 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
}
@Override
public List<Resource> findGrantedResources(String requester, int first, int max) {
TypedQuery<String> query = entityManager.createNamedQuery("findGrantedResources", String.class);
public List<Resource> findGrantedResources(String requester, String name, int first, int max) {
TypedQuery<String> query = name == null ?
entityManager.createNamedQuery("findGrantedResources", String.class) :
entityManager.createNamedQuery("findGrantedResourcesByName", String.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("requester", requester);
if (name != null) {
query.setParameter("resourceName", "%" + name.toLowerCase() + "%");
}
if (first > -1 && max > -1) {
query.setFirstResult(first);
query.setMaxResults(max);

View file

@ -115,11 +115,12 @@ public interface PermissionTicketStore {
* Returns a list of {@link Resource} granted to the given {@code requester}
*
* @param requester the requester
* @param name the keyword to query resources by name or null if any resource
* @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);
List<Resource> findGrantedResources(String requester, String name, int first, int max);
/**
* Returns a list of {@link Resource} granted by the owner to other users

View file

@ -27,7 +27,6 @@ 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;
@ -62,9 +61,19 @@ public class ResourcesService extends AbstractResourceService {
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
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);
public Response getResources(@QueryParam("name") String name,
@QueryParam("first") Integer first,
@QueryParam("max") Integer max) {
Map<String, String[]> filters = new HashMap<>();
filters.put("owner", new String[] { user.getId() });
if (name != null) {
filters.put("name", new String[] { name });
}
return queryResponse((f, m) -> resourceStore.findByResourceServer(filters, null, f, m).stream()
.map(resource -> new Resource(resource, user, provider)), first, max);
}
/**
@ -77,8 +86,10 @@ public class ResourcesService extends AbstractResourceService {
@GET
@Path("shared-with-me")
@Produces(MediaType.APPLICATION_JSON)
public Response getSharedWithMe(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getUser().getId(), f, m), false)
public Response getSharedWithMe(@QueryParam("name") String name,
@QueryParam("first") Integer first,
@QueryParam("max") Integer max) {
return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getUser().getId(), name, f, m), false)
.stream(), first, max);
}
@ -189,10 +200,12 @@ public class ResourcesService extends AbstractResourceService {
.rel("next").build());
}
if (first > 0) {
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

@ -42,6 +42,7 @@ import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles;
import org.keycloak.representations.AccessToken;
@ -158,6 +159,13 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
assertMyResourcesResponse(resources);
}
@Test
public void testGetMyResourcesByName() {
assertEquals(11, getMyResources("Resource 1").size());
assertEquals(0, getMyResources("non-existent\n").size());
assertEquals(1, getMyResources("Resource 23").size());
}
@Test
public void testGetMyResourcesPagination() {
List<Resource> resources = getMyResources(0, 10, response -> assertNextPageLink(response, "/realms/test/account/resources", 10, 10));
@ -206,19 +214,26 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
}
@Test
public void testGetSharedWithMeByName() {
assertEquals(5, getSharedWithMe("jdoe", "Resource 1", -1, -1, null).size());
assertEquals(0, getSharedWithMe("jdoe", "non-existent", -1, -1, null).size());
assertEquals(10, getSharedWithMe("jdoe", "resource", -1, -1, null).size());
}
@Test
public void testGetSharedWithMePagination() {
for (String userName : userNames) {
List<AbstractResourceService.ResourcePermission> resources = getSharedWithMe(userName, 0, 3,
List<AbstractResourceService.ResourcePermission> resources = getSharedWithMe(userName, null, 0, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 3, 3));
assertSharedWithMeResponse(resources);
getSharedWithMe(userName, 3, 3,
getSharedWithMe(userName, null, 3, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 6, 3));
getSharedWithMe(userName, 6, 3,
getSharedWithMe(userName, null, 6, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 9, 3));
getSharedWithMe(userName, 9, 3,
getSharedWithMe(userName, null, 9, 3,
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 9, 3, true));
}
}
@ -549,15 +564,22 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName) {
return getSharedWithMe(userName, -1, -1, null);
return getSharedWithMe(userName, null, -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);
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName, String name, int first, int max, Consumer<SimpleHttp.Response> responseHandler) {
KeycloakUriBuilder uri = KeycloakUriBuilder.fromUri("/shared-with-me");
if (name != null) {
uri.queryParam("name", name);
}
return doGet("/shared-with-me", authzClient.obtainAccessToken(userName, "password").getToken(),
if (first > -1 && max > -1) {
uri.queryParam("first", first);
uri.queryParam("max", max);
}
return doGet(uri.build().toString(), authzClient.obtainAccessToken(userName, "password").getToken(),
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {}, responseHandler);
}
@ -636,18 +658,35 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
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>>() {});
private List<Resource> getMyResources(String name) {
return getMyResources(name, -1, -1);
}
return doGet("", new TypeReference<List<Resource>>() {});
private List<Resource> getMyResources(int first, int max) {
return getMyResources(null, first, max);
}
private List<Resource> getMyResources(String name, int first, int max) {
KeycloakUriBuilder uri = KeycloakUriBuilder.fromUri("");
if (name != null) {
uri.queryParam("name", name);
}
if (first > -1 && max > -1) {
uri.queryParam("first", first);
uri.queryParam("max", max);
}
return doGet(uri.build().toString(), new TypeReference<List<Resource>>() {});
}
private List<Resource> getMyResources(int first, int max, Consumer<SimpleHttp.Response> response) {
String query = "";
if (first > -1 && max > -1) {
return doGet("?first=" + first + "&max=" + max, new TypeReference<List<Resource>>() {}, response);
query = "?first=" + first + "&max=" + max;
}
return doGet("", new TypeReference<List<Resource>>() {}, response);
return doGet(query, new TypeReference<List<Resource>>() {}, response);
}
private void assertSharedWithOthersResponse(List<AbstractResourceService.ResourcePermission> resources) {
@ -708,7 +747,7 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
assertNextPageLink(response, uri, first, max, lastPage, false);
}
private void assertNextPageLink(SimpleHttp.Response response, String uri, int first, int max, boolean lastPage, boolean singlePage) {
private void assertNextPageLink(SimpleHttp.Response response, String uri, int nextPage, int max, boolean lastPage, boolean singlePage) {
try {
List<String> links = response.getHeader("Link");
@ -718,13 +757,18 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
}
assertNotNull(links);
if (max - nextPage == 0) {
assertEquals(1, links.size());
} else {
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);
assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + nextPage + "&max=" + max + ">; rel=\"next\"", link);
} else {
assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + (first - max) + "&max=" + max + ">; rel=\"prev\"", link);
assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + (nextPage - max) + "&max=" + max + ">; rel=\"prev\"", link);
}
}
} catch (IOException e) {