From 1a42b6962b6240b4de1bbde7205f5cfe1695f9cf Mon Sep 17 00:00:00 2001 From: Aaron Coburn Date: Thu, 1 Jun 2023 12:18:26 -0400 Subject: [PATCH 1/2] JCL-367: Distinguish between creator and recipient in access credential queries --- .../client/accessgrant/AccessGrantClient.java | 36 +++++++++++-------- .../accessgrant/AccessGrantClientTest.java | 32 ++++++++--------- .../base/AccessGrantScenarios.java | 24 ++++++------- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java index 538bb128e44..7111fe3b826 100644 --- a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java +++ b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java @@ -377,15 +377,16 @@ public CompletionStage verify(final AccessCredenti * Perform an Access Grant query. * * @param the AccessCredential type - * @param agent the agent identifier, may be {@code null} * @param resource the resource identifier, may be {@code null} + * @param creator the identifier for the agent who created the credential, may be {@code null} + * @param recipient the identifier for the agent who is the recipient for the credential, may be {@code null} * @param purpose the access purpose, may be {@code null} * @param mode the access mode, may be {@code null} * @param clazz the AccessCredential type, either {@link AccessGrant} or {@link AccessRequest} - * @return the next stage of completion, including the matched Access Grants + * @return the next stage of completion, including the matched Access Credentials */ - public CompletionStage> query(final URI agent, final URI resource, - final URI purpose, final String mode, final Class clazz) { + public CompletionStage> query(final URI resource, final URI creator, + final URI recipient, final URI purpose, final String mode, final Class clazz) { Objects.requireNonNull(clazz, "The clazz parameter must not be null!"); final URI type; @@ -405,7 +406,7 @@ public CompletionStage> query(final URI age return v1Metadata().thenCompose(metadata -> { final List>> futures = buildQuery(config.getIssuer(), type, - agent, resource, purpose, mode).stream() + resource, creator, recipient, purpose, mode).stream() .map(data -> Request.newBuilder(metadata.queryEndpoint) .header(CONTENT_TYPE, APPLICATION_JSON) .POST(Request.BodyPublishers.ofByteArray(serialize(data))).build()) @@ -451,7 +452,7 @@ public CompletionStage> query(final URI type, final URI agent, Objects.requireNonNull(type, "The type parameter must not be null!"); return v1Metadata().thenCompose(metadata -> { final List>> futures = buildQuery(config.getIssuer(), type, - agent, resource, null, mode).stream() + resource, null, agent, null, mode).stream() .map(data -> Request.newBuilder(metadata.queryEndpoint) .header(CONTENT_TYPE, APPLICATION_JSON) .POST(Request.BodyPublishers.ofByteArray(serialize(data))).build()) @@ -683,26 +684,26 @@ static Collection getCredentials(final Map data) { return Collections.emptyList(); } - static List> buildQuery(final URI issuer, final URI type, final URI agent, final URI resource, - final URI purpose, final String mode) { + static List> buildQuery(final URI issuer, final URI type, final URI resource, final URI creator, + final URI recipient, final URI purpose, final String mode) { final List> queries = new ArrayList<>(); - buildQuery(queries, issuer, type, agent, resource, purpose, mode); + buildQuery(queries, issuer, type, resource, creator, recipient, purpose, mode); return queries; } - static void buildQuery(final List> queries, final URI issuer, final URI type, final URI agent, - final URI resource, final URI purpose, final String mode) { + static void buildQuery(final List> queries, final URI issuer, final URI type, + final URI resource, final URI creator, final URI recipient, final URI purpose, final String mode) { final Map credential = new HashMap<>(); credential.put(CONTEXT, Arrays.asList(VC_CONTEXT_URI, INRUPT_CONTEXT_URI)); credential.put("issuer", issuer); credential.put(TYPE, Arrays.asList(type)); final Map consent = new HashMap<>(); - if (agent != null) { + if (recipient != null) { if (isAccessGrant(type) || isAccessDenial(type)) { - consent.put(IS_PROVIDED_TO, agent); + consent.put(IS_PROVIDED_TO, recipient); } else if (isAccessRequest(type)) { - consent.put(IS_CONSENT_FOR_DATA_SUBJECT, agent); + consent.put(IS_CONSENT_FOR_DATA_SUBJECT, recipient); } } if (resource != null) { @@ -716,6 +717,9 @@ static void buildQuery(final List> queries, final URI issuer } final Map subject = new HashMap<>(); + if (creator != null) { + subject.put("id", creator); + } if (!consent.isEmpty()) { if (isAccessGrant(type) || isAccessDenial(type)) { subject.put(PROVIDED_CONSENT, consent); @@ -723,6 +727,8 @@ static void buildQuery(final List> queries, final URI issuer subject.put("hasConsent", consent); } credential.put(CREDENTIAL_SUBJECT, subject); + } else if (!subject.isEmpty()) { + credential.put(CREDENTIAL_SUBJECT, subject); } final Map data = new HashMap<>(); @@ -733,7 +739,7 @@ static void buildQuery(final List> queries, final URI issuer // Recurse final URI parent = getParent(resource); if (parent != null) { - buildQuery(queries, issuer, type, agent, parent, purpose, mode); + buildQuery(queries, issuer, type, parent, creator, recipient, purpose, mode); } } diff --git a/access-grant/src/test/java/com/inrupt/client/accessgrant/AccessGrantClientTest.java b/access-grant/src/test/java/com/inrupt/client/accessgrant/AccessGrantClientTest.java index 05c5664dd4a..8757a8be196 100644 --- a/access-grant/src/test/java/com/inrupt/client/accessgrant/AccessGrantClientTest.java +++ b/access-grant/src/test/java/com/inrupt/client/accessgrant/AccessGrantClientTest.java @@ -508,9 +508,8 @@ void testQueryGrant() { final String token = generateIdToken(claims); final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); - final List grants = client.query(null, - URI.create("https://storage.example/e973cc3d-5c28-4a10-98c5-e40079289358/a/b/c"), null, "Read", - AccessGrant.class) + final URI resource = URI.create("https://storage.example/e973cc3d-5c28-4a10-98c5-e40079289358/a/b/c"); + final List grants = client.query(resource, null, null, null, "Read", AccessGrant.class) .toCompletableFuture().join(); assertEquals(1, grants.size()); } @@ -525,9 +524,8 @@ void testQueryGrantAgent() { final String token = generateIdToken(claims); final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); - final List grants = client.query(URI.create("https://id.test/user"), - null, null, "Read", AccessGrant.class) - .toCompletableFuture().join(); + final List grants = client.query(null, null, URI.create("https://id.test/user"), + null, "Read", AccessGrant.class).toCompletableFuture().join(); assertEquals(1, grants.size()); } @@ -541,9 +539,8 @@ void testQueryRequestAgent() { final String token = generateIdToken(claims); final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); - final List requests = client.query(URI.create("https://id.test/user"), - null, null, "Read", AccessRequest.class) - .toCompletableFuture().join(); + final List requests = client.query(null, null, URI.create("https://id.test/user"), + null, "Read", AccessRequest.class).toCompletableFuture().join(); assertEquals(1, requests.size()); } @@ -557,10 +554,9 @@ void testQueryRequest() { final String token = generateIdToken(claims); final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); - final List requests = client.query(null, - URI.create("https://storage.example/f1759e6d-4dda-4401-be61-d90d070a5474/a/b/c"), null, "Read", - AccessRequest.class) - .toCompletableFuture().join(); + final URI resource = URI.create("https://storage.example/f1759e6d-4dda-4401-be61-d90d070a5474/a/b/c"); + final List requests = client.query(resource, null, null, null, "Read", AccessRequest.class) + .toCompletableFuture().join(); assertEquals(1, requests.size()); } @@ -574,10 +570,9 @@ void testQueryDenial() { final String token = generateIdToken(claims); final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); - final List grants = client.query(null, - URI.create("https://storage.example/ef9c4b90-0459-408d-bfa9-1c61d46e1eaf/e/f/g"), null, "Read", - AccessDenial.class) - .toCompletableFuture().join(); + final URI resource = URI.create("https://storage.example/ef9c4b90-0459-408d-bfa9-1c61d46e1eaf/e/f/g"); + final List grants = client.query(resource, null, null, null, "Read", AccessDenial.class) + .toCompletableFuture().join(); assertEquals(1, grants.size()); } @@ -592,7 +587,8 @@ void testQueryInvalidType() { final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token)); final URI uri = URI.create("https://storage.example/f1759e6d-4dda-4401-be61-d90d070a5474/a/b/c"); - assertThrows(AccessGrantException.class, () -> client.query(null, uri, null, "Read", AccessCredential.class)); + assertThrows(AccessGrantException.class, () -> + client.query(uri, null, null, null, "Read", AccessCredential.class)); } diff --git a/integration/base/src/main/java/com/inrupt/client/integration/base/AccessGrantScenarios.java b/integration/base/src/main/java/com/inrupt/client/integration/base/AccessGrantScenarios.java index 7c57a1136f0..cdb352f06f6 100644 --- a/integration/base/src/main/java/com/inrupt/client/integration/base/AccessGrantScenarios.java +++ b/integration/base/src/main/java/com/inrupt/client/integration/base/AccessGrantScenarios.java @@ -323,8 +323,8 @@ void accessGrantQueryByRequestorTest(final Session session) { final AccessGrantClient accessGrantClient = new AccessGrantClient(URI.create(VC_PROVIDER)).session(session); //query for all grants issued by the user - final List grants = accessGrantClient.query(URI.create(webidUrl), - sharedResource, PURPOSE1, GRANT_MODE_READ, AccessRequest.class) + final List grants = accessGrantClient.query(sharedResource, null, URI.create(webidUrl), + PURPOSE1, GRANT_MODE_READ, AccessRequest.class) .toCompletableFuture().join(); // result is 4 because we retrieve the grants for each path // sharedTextFileURI = @@ -332,8 +332,8 @@ void accessGrantQueryByRequestorTest(final Session session) { assertEquals(1, grants.size()); //query for all grants issued by a random user - final List randomGrants = accessGrantClient.query(URI.create("https://someuser.test"), - sharedResource, PURPOSE1, GRANT_MODE_READ, AccessRequest.class) + final List randomGrants = accessGrantClient.query(sharedResource, null, + URI.create("https://someuser.test"), PURPOSE1, GRANT_MODE_READ, AccessRequest.class) .toCompletableFuture().join(); assertEquals(0, randomGrants.size()); } @@ -347,14 +347,14 @@ void accessGrantQueryByResourceTest(final Session session) { final AccessGrantClient accessGrantClient = new AccessGrantClient(URI.create(VC_PROVIDER)).session(session); //query for all grants of a dedicated resource - final List requests = accessGrantClient.query(URI.create(webidUrl), - sharedResource, PURPOSE1, GRANT_MODE_READ, AccessRequest.class) + final List requests = accessGrantClient.query(sharedResource, null, URI.create(webidUrl), + PURPOSE1, GRANT_MODE_READ, AccessRequest.class) .toCompletableFuture().join(); assertEquals(1, requests.size()); //query for all grants of a random resource - final List randomGrants = accessGrantClient.query(URI.create(webidUrl), - URI.create("https://somerandom.test"), PURPOSE1, GRANT_MODE_READ, AccessRequest.class) + final List randomGrants = accessGrantClient.query(URI.create("https://somerandom.test"), + null, URI.create(webidUrl), PURPOSE1, GRANT_MODE_READ, AccessRequest.class) .toCompletableFuture().join(); assertEquals(0, randomGrants.size()); } @@ -368,14 +368,14 @@ void accessGrantQueryByPurposeTest(final Session session) { final AccessGrantClient accessGrantClient = new AccessGrantClient(URI.create(VC_PROVIDER)).session(session); //query for all grants with a dedicated purpose - final List requests = accessGrantClient.query(URI.create(webidUrl), - sharedResource, PURPOSE1, GRANT_MODE_READ, AccessRequest.class) + final List requests = accessGrantClient.query(sharedResource, null, URI.create(webidUrl), + PURPOSE1, GRANT_MODE_READ, AccessRequest.class) .toCompletableFuture().join(); assertEquals(1, requests.size()); //query for all grants of dedicated purpose combinations - final List randomGrants = accessGrantClient.query(URI.create(webidUrl), - sharedResource, PURPOSE1, GRANT_MODE_WRITE, AccessGrant.class) + final List randomGrants = accessGrantClient.query(sharedResource, null, URI.create(webidUrl), + PURPOSE1, GRANT_MODE_WRITE, AccessGrant.class) .toCompletableFuture().join(); assertEquals(0, randomGrants.size()); //our grant is actually a Read } From fd5f724f0a3114f55aa66167f205806273018e89 Mon Sep 17 00:00:00 2001 From: Aaron Coburn Date: Thu, 1 Jun 2023 13:00:02 -0400 Subject: [PATCH 2/2] Remove repeated strings --- .../client/accessgrant/AccessGrantClient.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java index 7111fe3b826..459dd4b8669 100644 --- a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java +++ b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java @@ -65,7 +65,6 @@ * {@link Session} object, typically an OpenID-based session: * *
{@code
-   URI SOLID_ACCESS_GRANT = URI.create("http://www.w3.org/ns/solid/vc#SolidAccessGrant");
    URI issuer = URI.create("https://issuer.example");
    Session openid = OpenIdSession.ofIdToken(idToken);
 
@@ -73,7 +72,7 @@
 
    URI resource = URI.create("https://storage.example/data/resource");
    URI purpose = URI.create("https://purpose.example/1");
-   client.query(null, resource, purpose, "Read", AccessGrant.class)
+   client.query(resource, null, openid.getPrincipal().orElse(null), purpose, "Read", AccessGrant.class)
        .thenApply(grants -> AccessGrantSession.ofAccessGrant(openid, grants.toArray(new AccessGrant[0])))
        .thenApply(session -> SolidClient.getClient().session(session))
        .thenAccept(cl -> {
@@ -89,6 +88,7 @@ public class AccessGrantClient {
     private static final String VC_CONTEXT_URI = "https://www.w3.org/2018/credentials/v1";
     private static final String INRUPT_CONTEXT_URI = "https://schema.inrupt.com/credentials/v1.jsonld";
     private static final String VERIFIABLE_CREDENTIAL = "verifiableCredential";
+    private static final String SOLID_VC_NAMESPACE = "http://www.w3.org/ns/solid/vc#";
     private static final String TYPE = "type";
     private static final String APPLICATION_JSON = "application/json";
     private static final String CONTENT_TYPE = "Content-Type";
@@ -102,9 +102,12 @@ public class AccessGrantClient {
     private static final String FOR_PURPOSE = "forPurpose";
     private static final String EXPIRATION_DATE = "expirationDate";
     private static final String CREDENTIAL = "credential";
-    private static final URI ACCESS_GRANT = URI.create("http://www.w3.org/ns/solid/vc#SolidAccessGrant");
-    private static final URI ACCESS_REQUEST = URI.create("http://www.w3.org/ns/solid/vc#SolidAccessRequest");
-    private static final URI ACCESS_DENIAL = URI.create("http://www.w3.org/ns/solid/vc#SolidAccessDenial");
+    private static final String SOLID_ACCESS_GRANT = "SolidAccessGrant";
+    private static final String SOLID_ACCESS_REQUEST = "SolidAccessRequest";
+    private static final String SOLID_ACCESS_DENIAL = "SolidAccessDenial";
+    private static final URI FQ_ACCESS_GRANT = URI.create(SOLID_VC_NAMESPACE + SOLID_ACCESS_GRANT);
+    private static final URI FQ_ACCESS_REQUEST = URI.create(SOLID_VC_NAMESPACE + SOLID_ACCESS_REQUEST);
+    private static final URI FQ_ACCESS_DENIAL = URI.create(SOLID_VC_NAMESPACE + SOLID_ACCESS_DENIAL);
     private static final Set ACCESS_GRANT_TYPES = getAccessGrantTypes();
     private static final Set ACCESS_REQUEST_TYPES = getAccessRequestTypes();
     private static final Set ACCESS_DENIAL_TYPES = getAccessDenialTypes();
@@ -301,9 +304,9 @@ public CompletionStage issue(final URI type, final URI agent, final
         }
         return v1Metadata().thenCompose(metadata -> {
             final Map data;
-            if (ACCESS_GRANT.equals(type)) {
+            if (FQ_ACCESS_GRANT.equals(type)) {
                 data = buildAccessGrantv1(agent, resources, modes, expiration, uriPurposes);
-            } else if (ACCESS_REQUEST.equals(type)) {
+            } else if (FQ_ACCESS_REQUEST.equals(type)) {
                 data = buildAccessRequestv1(agent, resources, modes, expiration, uriPurposes);
             } else {
                 throw new AccessGrantException("Unsupported grant type: " + type);
@@ -392,13 +395,13 @@ public  CompletionStage> query(final URI res
         final URI type;
         final Set supportedTypes;
         if (AccessGrant.class.isAssignableFrom(clazz)) {
-            type = URI.create("SolidAccessGrant");
+            type = URI.create(SOLID_ACCESS_GRANT);
             supportedTypes = ACCESS_GRANT_TYPES;
         } else if (AccessRequest.class.isAssignableFrom(clazz)) {
-            type = URI.create("SolidAccessRequest");
+            type = URI.create(SOLID_ACCESS_REQUEST);
             supportedTypes = ACCESS_REQUEST_TYPES;
         } else if (AccessDenial.class.isAssignableFrom(clazz)) {
-            type = URI.create("SolidAccessDenial");
+            type = URI.create(SOLID_ACCESS_DENIAL);
             supportedTypes = ACCESS_DENIAL_TYPES;
         } else {
             throw new AccessGrantException("Unsupported type " + clazz + " in query request");
@@ -855,35 +858,35 @@ static boolean isSuccess(final int statusCode) {
 
     static Set getAccessRequestTypes() {
         final Set types = new HashSet<>();
-        types.add("SolidAccessRequest");
-        types.add(ACCESS_REQUEST.toString());
+        types.add(SOLID_ACCESS_REQUEST);
+        types.add(FQ_ACCESS_REQUEST.toString());
         return Collections.unmodifiableSet(types);
     }
 
     static Set getAccessGrantTypes() {
         final Set types = new HashSet<>();
-        types.add("SolidAccessGrant");
-        types.add(ACCESS_GRANT.toString());
+        types.add(SOLID_ACCESS_GRANT);
+        types.add(FQ_ACCESS_GRANT.toString());
         return Collections.unmodifiableSet(types);
     }
 
     static Set getAccessDenialTypes() {
         final Set types = new HashSet<>();
-        types.add("SolidAccessDenial");
-        types.add(ACCESS_DENIAL.toString());
+        types.add(SOLID_ACCESS_DENIAL);
+        types.add(FQ_ACCESS_DENIAL.toString());
         return Collections.unmodifiableSet(types);
     }
 
     static boolean isAccessGrant(final URI type) {
-        return "SolidAccessGrant".equals(type.toString()) || ACCESS_GRANT.equals(type);
+        return SOLID_ACCESS_GRANT.equals(type.toString()) || FQ_ACCESS_GRANT.equals(type);
     }
 
     static boolean isAccessRequest(final URI type) {
-        return "SolidAccessRequest".equals(type.toString()) || ACCESS_REQUEST.equals(type);
+        return SOLID_ACCESS_REQUEST.equals(type.toString()) || FQ_ACCESS_REQUEST.equals(type);
     }
 
     static boolean isAccessDenial(final URI type) {
-        return "SolidAccessDenial".equals(type.toString()) || ACCESS_DENIAL.equals(type);
+        return SOLID_ACCESS_DENIAL.equals(type.toString()) || FQ_ACCESS_DENIAL.equals(type);
     }
 
     /**