From 7580be8708468a55a4891e1d182fd1c65985bf92 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 3 Mar 2020 09:50:08 +0100 Subject: [PATCH] KEYCLOAK-13121 added the basic functionality --- .../account/resources/ResourcesService.java | 32 +++- .../account/ResourcesRestServiceTest.java | 8 +- .../ui/account2/MyResourcesTest.java | 159 ++++++++++++++++++ .../ui/account2/page/MyResourcesPage.java | 21 +++ .../ui/account2/page/PhotozPage.java | 4 + .../theme/keycloak-preview/account/index.ftl | 2 +- .../app/account-service/account.service.ts | 1 - .../AbstractResourceTable.tsx | 49 ++++++ .../my-resources-page/EditTheResource.tsx | 147 ++++++++++++++++ .../my-resources-page/MyResourcesPage.tsx | 39 +++-- .../my-resources-page/PermissionRequest.tsx | 145 ++++++++++++++++ .../my-resources-page/ResourcesTable.tsx | 75 ++++----- .../SharedResourcesTable.tsx | 114 +++++++++++++ .../account/resources/content.js | 2 +- 14 files changed, 724 insertions(+), 74 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/MyResourcesTest.java create mode 100644 testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java create mode 100644 testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/PhotozPage.java create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/my-resources-page/AbstractResourceTable.tsx create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/my-resources-page/EditTheResource.tsx create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/my-resources-page/PermissionRequest.tsx create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/my-resources-page/SharedResourcesTable.tsx diff --git a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java index e0d8798dd2..e55412f55c 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java @@ -25,11 +25,7 @@ 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.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -111,6 +107,30 @@ public class ResourcesService extends AbstractResourceService { .stream(), first, max); } + /** + */ + @GET + @Path("pending-requests") + @Produces(MediaType.APPLICATION_JSON) + public Response getPendingRequests() { + Map filters = new HashMap<>(); + + filters.put(PermissionTicket.REQUESTER, user.getId()); + filters.put(PermissionTicket.GRANTED, Boolean.FALSE.toString()); + + final List permissionTickets = ticketStore.find(filters, null, -1, -1); + + final List resourceList = new ArrayList<>(permissionTickets.size()); + for (PermissionTicket ticket : permissionTickets) { + ResourcePermission resourcePermission = new ResourcePermission(ticket.getResource(), provider); + resourcePermission.addScope(new Scope(ticket.getScope())); + resourceList.add(resourcePermission); + } + + return queryResponse( + (f, m) -> resourceList.stream(), -1, resourceList.size()); + } + @Path("{id}") public Object getResource(@PathParam("id") String id) { org.keycloak.authorization.model.Resource resource = resourceStore.findById(id, null); @@ -203,7 +223,7 @@ public class ResourcesService extends AbstractResourceService { if (first > 0) { links.add(Link.fromUri( KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}") - .build(nextPage ? first : first - max, max)) + .build(Math.max(first - max, 0), max)) .rel("prev").build()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java index 964992ecf5..9c469164c9 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java @@ -178,7 +178,11 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest { assertEquals(10, resources.size()); resources = getMyResources(20, 10, response -> { - assertNextPageLink(response, "/realms/test/account/resources", 20, 10, true); + assertNextPageLink(response, "/realms/test/account/resources", 30, 10, true); + }); + + getMyResources(15, 5, response -> { + assertNextPageLink(response, "/realms/test/account/resources", 20, 5); }); assertEquals(10, resources.size()); @@ -768,7 +772,7 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest { if (link.contains("rel=\"next\"")) { assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + nextPage + "&max=" + max + ">; rel=\"next\"", link); } else { - assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + (nextPage - max) + "&max=" + max + ">; rel=\"prev\"", link); + assertEquals("<" + authzClient.getConfiguration().getAuthServerUrl() + uri + "?first=" + (nextPage - max * 2) + "&max=" + max + ">; rel=\"prev\"", link); } } } catch (IOException e) { diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/MyResourcesTest.java b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/MyResourcesTest.java new file mode 100644 index 0000000000..63d6941b48 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/MyResourcesTest.java @@ -0,0 +1,159 @@ +package org.keycloak.testsuite.ui.account2; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.AuthorizationResource; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.Configuration; +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.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage; +import org.keycloak.testsuite.ui.account2.page.MyResourcesPage; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.UserBuilder; +import org.keycloak.util.JsonSerialization; + +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +public class MyResourcesTest extends BaseAccountPageTest { + private static final String[] userNames = new String[]{"alice", "jdoe", "bob"}; + + @Page + private MyResourcesPage myResourcesPage; + + private AuthzClient authzClient; + private RealmRepresentation testRealm; + private CloseableHttpClient httpClient; + + @Override + public void addTestRealms(List testRealms) { + super.addTestRealms(testRealms); + testRealm = testRealms.get(0); + testRealm.setUserManagedAccessAllowed(true); + + testRealm.setUsers(Arrays.asList(createUser("alice"), + createUser("jdoe"), createUser("bob"))); + + ClientRepresentation client = ClientBuilder.create() + .clientId("my-resource-server") + .authorizationServicesEnabled(true) + .serviceAccountsEnabled(true) + .secret("secret") + .name("My Resource Server") + .baseUrl("http://resourceserver.com") + .directAccessGrants().build(); + + testRealm.setClients(singletonList(client)); + } + + private UserRepresentation createUser(String userName) { + return UserBuilder.create() + .username(userName) + .enabled(true) + .password("password") + .role("account", AccountRoles.MANAGE_ACCOUNT) + .build(); + } + + @Before + public void before() { + httpClient = HttpClientBuilder.create().build(); + } + + @After + public void after() { + try { + httpClient.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Before + public void setup() throws Exception { + ClientResource resourceServer = getResourceServer(); + authzClient = createAuthzClient(resourceServer.toRepresentation()); + AuthorizationResource authorization = resourceServer.authorization(); + + for (int i = 0; i < 30; i++) { + ResourceRepresentation resource = new ResourceRepresentation(); + + resource.setOwnerManagedAccess(true); + + final byte[] content = new JWSInput(authzClient.obtainAccessToken("jdoe", "password").getToken()).getContent(); + final AccessToken accessToken = JsonSerialization.readValue(content, AccessToken.class); + resource.setOwner(accessToken.getSubject()); + + resource.setName("Resource " + i); + resource.setDisplayName("Display Name " + i); + resource.setIconUri("Icon Uri " + i); + resource.addScope("Scope A", "Scope B", "Scope C", "Scope D"); + resource.setUri("http://resourceServer.com/resources/" + i); + + try (Response response1 = authorization.resources().create(resource)) { + resource.setId(response1.readEntity(ResourceRepresentation.class).getId()); + } + + for (String scope : Arrays.asList("Scope A", "Scope B")) { + PermissionTicketRepresentation ticket = new PermissionTicketRepresentation(); + + ticket.setGranted(true); + ticket.setOwner(resource.getOwner().getId()); + ticket.setRequesterName(userNames[i % userNames.length]); + ticket.setResource(resource.getId()); + ticket.setScopeName(scope); + + authzClient.protection("jdoe", "password").permission().create(ticket); + } + } + } + + @Test + public void shouldShowMyResourcesInTable() { + myResourcesPage.assertCurrent(); + + assertEquals(10, myResourcesPage.getResourcesListCount()); + } + + private AuthzClient createAuthzClient(ClientRepresentation client) { + Map credentials = new HashMap<>(); + + credentials.put("secret", "secret"); + + return AuthzClient + .create(new Configuration(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth", + testRealm.getRealm(), client.getClientId(), + credentials, httpClient)); + } + + private ClientResource getResourceServer() { + ClientsResource clients = adminClient.realm(TEST).clients(); + return clients.get(clients.findByClientId("my-resource-server").get(0).getId()); + } + + @Override + protected AbstractLoggedInPage getAccountPage() { + return myResourcesPage; + } +} diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java new file mode 100644 index 0000000000..33c58feb62 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java @@ -0,0 +1,21 @@ +package org.keycloak.testsuite.ui.account2.page; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +import java.util.List; + +public class MyResourcesPage extends AbstractLoggedInPage { + + @FindBy(id = "resourcesList") + private List resourcesList; + + @Override + public String getPageId() { + return "resources"; + } + + public int getResourcesListCount() { + return resourcesList.size(); + } +} diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/PhotozPage.java b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/PhotozPage.java new file mode 100644 index 0000000000..8bde2f83be --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/PhotozPage.java @@ -0,0 +1,4 @@ +package org.keycloak.testsuite.ui.account2.page; + +public class PhotozPage { +} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl index db33a6672d..db90b2480c 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl +++ b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl @@ -229,7 +229,7 @@ -