[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
|
@Override
|
||||||
public List<Resource> findGrantedResources(String requester, int first, int max) {
|
public List<Resource> findGrantedResources(String requester, String name, int first, int max) {
|
||||||
return getPermissionTicketStoreDelegate().findGrantedResources(requester, first, max);
|
return getPermissionTicketStoreDelegate().findGrantedResources(requester, name, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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="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="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")
|
@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
|
@Override
|
||||||
public List<Resource> findGrantedResources(String requester, int first, int max) {
|
public List<Resource> findGrantedResources(String requester, String name, int first, int max) {
|
||||||
TypedQuery<String> query = entityManager.createNamedQuery("findGrantedResources", String.class);
|
TypedQuery<String> query = name == null ?
|
||||||
|
entityManager.createNamedQuery("findGrantedResources", String.class) :
|
||||||
|
entityManager.createNamedQuery("findGrantedResourcesByName", String.class);
|
||||||
|
|
||||||
query.setFlushMode(FlushModeType.COMMIT);
|
query.setFlushMode(FlushModeType.COMMIT);
|
||||||
query.setParameter("requester", requester);
|
query.setParameter("requester", requester);
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
query.setParameter("resourceName", "%" + name.toLowerCase() + "%");
|
||||||
|
}
|
||||||
|
|
||||||
if (first > -1 && max > -1) {
|
if (first > -1 && max > -1) {
|
||||||
query.setFirstResult(first);
|
query.setFirstResult(first);
|
||||||
query.setMaxResults(max);
|
query.setMaxResults(max);
|
||||||
|
|
|
@ -115,11 +115,12 @@ public interface PermissionTicketStore {
|
||||||
* Returns a list of {@link Resource} granted to the given {@code requester}
|
* Returns a list of {@link Resource} granted to the given {@code requester}
|
||||||
*
|
*
|
||||||
* @param requester the requester
|
* @param requester the requester
|
||||||
|
* @param name the keyword to query resources by name or null if any resource
|
||||||
* @param first first result
|
* @param first first result
|
||||||
* @param max max result
|
* @param max max result
|
||||||
* @return a list of {@link Resource} granted to the given {@code requester}
|
* @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
|
* 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 javax.ws.rs.core.Response;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -62,9 +61,19 @@ public class ResourcesService extends AbstractResourceService {
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response getResources(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
|
public Response getResources(@QueryParam("name") String name,
|
||||||
return queryResponse((f, m) -> resourceStore.findByOwner(user.getId(), null, f, m)
|
@QueryParam("first") Integer first,
|
||||||
.stream().map(resource -> new Resource(resource, user, provider)), first, max);
|
@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
|
@GET
|
||||||
@Path("shared-with-me")
|
@Path("shared-with-me")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response getSharedWithMe(@QueryParam("first") Integer first, @QueryParam("max") Integer max) {
|
public Response getSharedWithMe(@QueryParam("name") String name,
|
||||||
return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getUser().getId(), f, m), false)
|
@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);
|
.stream(), first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,10 +200,12 @@ public class ResourcesService extends AbstractResourceService {
|
||||||
.rel("next").build());
|
.rel("next").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (first > 0) {
|
||||||
links.add(Link.fromUri(
|
links.add(Link.fromUri(
|
||||||
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
|
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
|
||||||
.build(nextPage ? first : first - max, max))
|
.build(nextPage ? first : first - max, max))
|
||||||
.rel("prev").build());
|
.rel("prev").build());
|
||||||
|
}
|
||||||
|
|
||||||
return links.toArray(new Link[links.size()]);
|
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.AuthzClient;
|
||||||
import org.keycloak.authorization.client.Configuration;
|
import org.keycloak.authorization.client.Configuration;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -158,6 +159,13 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
|
||||||
assertMyResourcesResponse(resources);
|
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
|
@Test
|
||||||
public void testGetMyResourcesPagination() {
|
public void testGetMyResourcesPagination() {
|
||||||
List<Resource> resources = getMyResources(0, 10, response -> assertNextPageLink(response, "/realms/test/account/resources", 10, 10));
|
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
|
@Test
|
||||||
public void testGetSharedWithMePagination() {
|
public void testGetSharedWithMePagination() {
|
||||||
for (String userName : userNames) {
|
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));
|
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 3, 3));
|
||||||
|
|
||||||
assertSharedWithMeResponse(resources);
|
assertSharedWithMeResponse(resources);
|
||||||
|
|
||||||
getSharedWithMe(userName, 3, 3,
|
getSharedWithMe(userName, null, 3, 3,
|
||||||
response -> assertNextPageLink(response, "/realms/test/account/resources/shared-with-me", 6, 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));
|
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));
|
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) {
|
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) {
|
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName, String name, int first, int max, Consumer<SimpleHttp.Response> responseHandler) {
|
||||||
if (first > -1 && max > -1) {
|
KeycloakUriBuilder uri = KeycloakUriBuilder.fromUri("/shared-with-me");
|
||||||
return doGet("/shared-with-me?first=" + first + "&max=" + max, authzClient.obtainAccessToken(userName, "password").getToken(),
|
|
||||||
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {}, responseHandler);
|
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);
|
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {}, responseHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,18 +658,35 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
|
||||||
return getMyResources(-1, -1);
|
return getMyResources(-1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> getMyResources(int first, int max) {
|
private List<Resource> getMyResources(String name) {
|
||||||
if (first > -1 && max > -1) {
|
return getMyResources(name, -1, -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) {
|
||||||
|
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) {
|
private List<Resource> getMyResources(int first, int max, Consumer<SimpleHttp.Response> response) {
|
||||||
|
String query = "";
|
||||||
if (first > -1 && max > -1) {
|
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) {
|
private void assertSharedWithOthersResponse(List<AbstractResourceService.ResourcePermission> resources) {
|
||||||
|
@ -708,7 +747,7 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
|
||||||
assertNextPageLink(response, uri, first, max, lastPage, false);
|
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 {
|
try {
|
||||||
List<String> links = response.getHeader("Link");
|
List<String> links = response.getHeader("Link");
|
||||||
|
|
||||||
|
@ -718,13 +757,18 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNotNull(links);
|
assertNotNull(links);
|
||||||
|
|
||||||
|
if (max - nextPage == 0) {
|
||||||
|
assertEquals(1, links.size());
|
||||||
|
} else {
|
||||||
assertEquals(lastPage ? 1 : 2, links.size());
|
assertEquals(lastPage ? 1 : 2, links.size());
|
||||||
|
}
|
||||||
|
|
||||||
for (String link : links) {
|
for (String link : links) {
|
||||||
if (link.contains("rel=\"next\"")) {
|
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 {
|
} 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) {
|
} catch (IOException e) {
|
||||||
|
|
Loading…
Reference in a new issue