Skip to content

Commit 2265c44

Browse files
authored
JCL-382: Access Credential Query builder (#532)
1 parent c7c87f2 commit 2265c44

File tree

3 files changed

+357
-13
lines changed

3 files changed

+357
-13
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright 2023 Inrupt Inc.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to use,
7+
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
* Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
package com.inrupt.client.accessgrant;
22+
23+
import java.net.URI;
24+
import java.util.HashSet;
25+
import java.util.Objects;
26+
import java.util.Set;
27+
28+
/**
29+
* An object to represent an access credential query.
30+
*
31+
* @param <T> The access credential type
32+
*/
33+
public class AccessCredentialQuery<T extends AccessCredential> {
34+
35+
private static final URI SOLID_ACCESS_GRANT = URI.create("SolidAccessGrant");
36+
private static final URI SOLID_ACCESS_REQUEST = URI.create("SolidAccessRequest");
37+
private static final URI SOLID_ACCESS_DENIAL = URI.create("SolidAccessDenial");
38+
39+
private final URI type;
40+
private final Set<URI> purposes;
41+
private final Set<String> modes;
42+
private final URI resource;
43+
private final URI creator;
44+
private final URI recipient;
45+
private final Class<T> clazz;
46+
47+
/**
48+
* Create an access credential query.
49+
*
50+
* @param resource the resource, may be {@code null}
51+
* @param creator the creator, may be {@code null}
52+
* @param recipient the recipient, may be {@code null}
53+
* @param purposes the purposes, never {@code null}
54+
* @param modes the access modes, never {@code null}
55+
* @param clazz the credential type, never {@code null}
56+
*/
57+
AccessCredentialQuery(final URI resource, final URI creator, final URI recipient,
58+
final Set<URI> purposes, final Set<String> modes, final Class<T> clazz) {
59+
this.clazz = Objects.requireNonNull(clazz, "The clazz parameter must not be null!");
60+
61+
if (AccessGrant.class.isAssignableFrom(clazz)) {
62+
this.type = SOLID_ACCESS_GRANT;
63+
} else if (AccessRequest.class.isAssignableFrom(clazz)) {
64+
this.type = SOLID_ACCESS_REQUEST;
65+
} else if (AccessDenial.class.isAssignableFrom(clazz)) {
66+
this.type = SOLID_ACCESS_DENIAL;
67+
} else {
68+
throw new AccessGrantException("Unsupported type " + clazz + " in query request");
69+
}
70+
71+
this.resource = resource;
72+
this.creator = creator;
73+
this.recipient = recipient;
74+
this.purposes = purposes;
75+
this.modes = modes;
76+
}
77+
78+
/**
79+
* Get the access credential type value.
80+
*
81+
* @return the type, never {@code null}
82+
*/
83+
public URI getType() {
84+
return type;
85+
}
86+
87+
/**
88+
* Get the requested resource.
89+
*
90+
* @return the resource, may be {@code null}
91+
*/
92+
public URI getResource() {
93+
return resource;
94+
}
95+
96+
/**
97+
* Get the requested creator.
98+
*
99+
* @return the creator, may be {@code null}
100+
*/
101+
public URI getCreator() {
102+
return creator;
103+
}
104+
105+
/**
106+
* Get the requested recipient.
107+
*
108+
* @return the recipient, may be {@code null}
109+
*/
110+
public URI getRecipient() {
111+
return recipient;
112+
}
113+
114+
/**
115+
* Get the requested purposes.
116+
*
117+
* @return the purpose identifiers, never {@code null}
118+
*/
119+
public Set<URI> getPurposes() {
120+
return purposes;
121+
}
122+
123+
/**
124+
* Get the requested access modes.
125+
*
126+
* @return the access modes, never {@code null}
127+
*/
128+
public Set<String> getModes() {
129+
return modes;
130+
}
131+
132+
/* package private */
133+
Class<T> getAccessCredentialType() {
134+
return clazz;
135+
}
136+
137+
/**
138+
* Create a new access credential query builder.
139+
*
140+
* @return the builder
141+
*/
142+
public static Builder newBuilder() {
143+
return new Builder();
144+
}
145+
146+
/**
147+
* A builder class for access credential queries.
148+
*/
149+
public static class Builder {
150+
151+
private final Set<URI> purposes = new HashSet<>();
152+
private final Set<String> modes = new HashSet<>();
153+
private URI builderResource;
154+
private URI builderCreator;
155+
private URI builderRecipient;
156+
157+
/**
158+
* Set the resource identifier.
159+
*
160+
* @param resource the resource identifier, may be {@code null}
161+
* @return this builder
162+
*/
163+
public Builder resource(final URI resource) {
164+
builderResource = resource;
165+
return this;
166+
}
167+
168+
/**
169+
* Add a purpose identifier.
170+
*
171+
* @param purpose a purpose identifier; {@code null} values have no effect.
172+
* @return this builder
173+
*/
174+
public Builder purpose(final URI purpose) {
175+
if (purpose != null) {
176+
purposes.add(purpose);
177+
}
178+
return this;
179+
}
180+
181+
/**
182+
* Add an access mode value.
183+
*
184+
* @param mode a mode value; {@code null} values have no effect.
185+
* @return this builder
186+
*/
187+
public Builder mode(final String mode) {
188+
if (mode != null) {
189+
modes.add(mode);
190+
}
191+
return this;
192+
}
193+
194+
/**
195+
* Set the creator identifier.
196+
*
197+
* @param creator the creator identifier, may be {@code null}
198+
* @return this builder
199+
*/
200+
public Builder creator(final URI creator) {
201+
builderCreator = creator;
202+
return this;
203+
}
204+
205+
/**
206+
* Set the recipient identifier.
207+
*
208+
* @param recipient the recipient identifier, may be {@code null}
209+
* @return this builder
210+
*/
211+
public Builder recipient(final URI recipient) {
212+
builderRecipient = recipient;
213+
return this;
214+
}
215+
216+
/**
217+
* Build the access credential query.
218+
*
219+
* @param <T> the credential type
220+
* @param clazz the credential type
221+
* @return the query object
222+
*/
223+
public <T extends AccessCredential> AccessCredentialQuery build(final Class<T> clazz) {
224+
return new AccessCredentialQuery<T>(builderResource, builderCreator, builderRecipient, purposes, modes,
225+
clazz);
226+
}
227+
}
228+
}

access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,28 @@ public CompletionStage<AccessCredentialVerification> verify(final AccessCredenti
390390
*/
391391
public <T extends AccessCredential> CompletionStage<List<T>> query(final URI resource, final URI creator,
392392
final URI recipient, final URI purpose, final String mode, final Class<T> clazz) {
393+
394+
final Set<String> modes = mode != null ? Collections.singleton(mode) : Collections.emptySet();
395+
final Set<URI> purposes = purpose != null ? Collections.singleton(purpose) : Collections.emptySet();
396+
397+
return query(resource, creator, recipient, purposes, modes, clazz);
398+
}
399+
400+
/**
401+
* Perform an Access Grant query.
402+
*
403+
* @param <T> the AccessCredential type
404+
* @param query the access credential query, never {@code null}
405+
* @return the next stage of completion, including the matched Access Credentials
406+
*/
407+
public <T extends AccessCredential> CompletionStage<List<T>> query(final AccessCredentialQuery<T> query) {
408+
Objects.requireNonNull(query, "The query may not be null!");
409+
return query(query.getResource(), query.getCreator(), query.getRecipient(), query.getPurposes(),
410+
query.getModes(), query.getAccessCredentialType());
411+
}
412+
413+
private <T extends AccessCredential> CompletionStage<List<T>> query(final URI resource, final URI creator,
414+
final URI recipient, final Set<URI> purposes, final Set<String> modes, final Class<T> clazz) {
393415
Objects.requireNonNull(clazz, "The clazz parameter must not be null!");
394416

395417
final URI type;
@@ -409,7 +431,7 @@ public <T extends AccessCredential> CompletionStage<List<T>> query(final URI res
409431

410432
return v1Metadata().thenCompose(metadata -> {
411433
final List<CompletableFuture<List<T>>> futures = buildQuery(config.getIssuer(), type,
412-
resource, creator, recipient, purpose, mode).stream()
434+
resource, creator, recipient, purposes, modes).stream()
413435
.map(data -> Request.newBuilder(metadata.queryEndpoint)
414436
.header(CONTENT_TYPE, APPLICATION_JSON)
415437
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build())
@@ -455,7 +477,7 @@ public CompletionStage<List<AccessGrant>> query(final URI type, final URI agent,
455477
Objects.requireNonNull(type, "The type parameter must not be null!");
456478
return v1Metadata().thenCompose(metadata -> {
457479
final List<CompletableFuture<List<AccessGrant>>> futures = buildQuery(config.getIssuer(), type,
458-
resource, null, agent, null, mode).stream()
480+
resource, null, agent, Collections.emptySet(), Collections.singleton(mode)).stream()
459481
.map(data -> Request.newBuilder(metadata.queryEndpoint)
460482
.header(CONTENT_TYPE, APPLICATION_JSON)
461483
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build())
@@ -688,14 +710,15 @@ static Collection<Object> getCredentials(final Map<String, Object> data) {
688710
}
689711

690712
static List<Map<String, Object>> buildQuery(final URI issuer, final URI type, final URI resource, final URI creator,
691-
final URI recipient, final URI purpose, final String mode) {
713+
final URI recipient, final Set<URI> purposes, final Set<String> modes) {
692714
final List<Map<String, Object>> queries = new ArrayList<>();
693-
buildQuery(queries, issuer, type, resource, creator, recipient, purpose, mode);
715+
buildQuery(queries, issuer, type, resource, creator, recipient, purposes, modes);
694716
return queries;
695717
}
696718

697719
static void buildQuery(final List<Map<String, Object>> queries, final URI issuer, final URI type,
698-
final URI resource, final URI creator, final URI recipient, final URI purpose, final String mode) {
720+
final URI resource, final URI creator, final URI recipient, final Set<URI> purposes,
721+
final Set<String> modes) {
699722
final Map<String, Object> credential = new HashMap<>();
700723
credential.put(CONTEXT, Arrays.asList(VC_CONTEXT_URI, INRUPT_CONTEXT_URI));
701724
credential.put("issuer", issuer);
@@ -706,7 +729,7 @@ static void buildQuery(final List<Map<String, Object>> queries, final URI issuer
706729
subject.put("id", creator);
707730
}
708731

709-
final Map<String, Object> consent = buildConsent(type, resource, recipient, purpose, mode);
732+
final Map<String, Object> consent = buildConsent(type, resource, recipient, purposes, modes);
710733
if (!consent.isEmpty()) {
711734
if (isAccessGrant(type) || isAccessDenial(type)) {
712735
subject.put(PROVIDED_CONSENT, consent);
@@ -726,12 +749,12 @@ static void buildQuery(final List<Map<String, Object>> queries, final URI issuer
726749
// Recurse
727750
final URI parent = getParent(resource);
728751
if (parent != null) {
729-
buildQuery(queries, issuer, type, parent, creator, recipient, purpose, mode);
752+
buildQuery(queries, issuer, type, parent, creator, recipient, purposes, modes);
730753
}
731754
}
732755

733-
static Map<String, Object> buildConsent(final URI type, final URI resource, final URI recipient, final URI purpose,
734-
final String mode) {
756+
static Map<String, Object> buildConsent(final URI type, final URI resource, final URI recipient,
757+
final Set<URI> purposes, final Set<String> modes) {
735758
final Map<String, Object> consent = new HashMap<>();
736759
if (recipient != null) {
737760
if (isAccessGrant(type) || isAccessDenial(type)) {
@@ -743,11 +766,11 @@ static Map<String, Object> buildConsent(final URI type, final URI resource, fina
743766
if (resource != null) {
744767
consent.put(FOR_PERSONAL_DATA, resource);
745768
}
746-
if (purpose != null) {
747-
consent.put(FOR_PURPOSE, purpose);
769+
if (!purposes.isEmpty()) {
770+
consent.put(FOR_PURPOSE, purposes);
748771
}
749-
if (mode != null) {
750-
consent.put(MODE, mode);
772+
if (!modes.isEmpty()) {
773+
consent.put(MODE, modes);
751774
}
752775
return consent;
753776
}

0 commit comments

Comments
 (0)