[KEYCLOAK-10714] - Add filtering support in My Resources endpoint by name
This commit is contained in:
parent
ab2bb31505
commit
17785dac08
6 changed files with 100 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
@ -57,14 +56,24 @@ 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
|
||||
* @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(@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());
|
||||
}
|
||||
|
||||
links.add(Link.fromUri(
|
||||
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
|
||||
.build(nextPage ? first : first - max, max))
|
||||
.rel("prev").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()]);
|
||||
}
|
||||
|
|
|
@ -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(String name) {
|
||||
return getMyResources(name, -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 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);
|
||||
}
|
||||
return doGet("", new TypeReference<List<Resource>>() {});
|
||||
|
||||
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);
|
||||
assertEquals(lastPage ? 1 : 2, links.size());
|
||||
|
||||
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) {
|
||||
|
|
Loading…
Reference in a new issue