Skip to content

Commit cbb3e22

Browse files
authored
JCL-394: AccessRequest builder (#577)
1 parent 3040699 commit cbb3e22

File tree

4 files changed

+377
-8
lines changed

4 files changed

+377
-8
lines changed

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

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public class AccessGrantClient {
107107
private static final String PROVIDED_CONSENT = "providedConsent";
108108
private static final String FOR_PURPOSE = "forPurpose";
109109
private static final String EXPIRATION_DATE = "expirationDate";
110+
private static final String ISSUANCE_DATE = "issuanceDate";
110111
private static final String CREDENTIAL = "credential";
111112
private static final String SOLID_ACCESS_GRANT = "SolidAccessGrant";
112113
private static final String SOLID_ACCESS_REQUEST = "SolidAccessRequest";
@@ -180,6 +181,17 @@ public AccessGrantClient session(final Session session) {
180181
return new AccessGrantClient(client.session(session), metadataCache, config);
181182
}
182183

184+
/**
185+
* Issue an access request.
186+
*
187+
* @param request the parameters for the access request
188+
* @return the next stage of completion containing the resulting access request
189+
*/
190+
public CompletionStage<AccessRequest> requestAccess(final AccessRequest.RequestParameters request) {
191+
return requestAccess(request.getRecipient(), request.getResources(),
192+
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
193+
}
194+
183195
/**
184196
* Issue an access request.
185197
*
@@ -192,10 +204,16 @@ public AccessGrantClient session(final Session session) {
192204
*/
193205
public CompletionStage<AccessRequest> requestAccess(final URI recipient, final Set<URI> resources,
194206
final Set<String> modes, final Set<URI> purposes, final Instant expiration) {
207+
return requestAccess(recipient, resources, modes, purposes, expiration, null);
208+
}
209+
210+
private CompletionStage<AccessRequest> requestAccess(final URI recipient, final Set<URI> resources,
211+
final Set<String> modes, final Set<URI> purposes, final Instant expiration, final Instant issuance) {
195212
Objects.requireNonNull(resources, "Resources may not be null!");
196213
Objects.requireNonNull(modes, "Access modes may not be null!");
197214
return v1Metadata().thenCompose(metadata -> {
198-
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, modes, expiration, purposes);
215+
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, modes, purposes, expiration,
216+
issuance);
199217

200218
final Request req = Request.newBuilder(metadata.issueEndpoint)
201219
.header(CONTENT_TYPE, APPLICATION_JSON)
@@ -228,7 +246,7 @@ public CompletionStage<AccessGrant> grantAccess(final AccessRequest request) {
228246
Objects.requireNonNull(request, "Request may not be null!");
229247
return v1Metadata().thenCompose(metadata -> {
230248
final Map<String, Object> data = buildAccessGrantv1(request.getCreator(), request.getResources(),
231-
request.getModes(), request.getExpiration(), request.getPurposes());
249+
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
232250
final Request req = Request.newBuilder(metadata.issueEndpoint)
233251
.header(CONTENT_TYPE, APPLICATION_JSON)
234252
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build();
@@ -260,7 +278,7 @@ public CompletionStage<AccessDenial> denyAccess(final AccessRequest request) {
260278
Objects.requireNonNull(request, "Request may not be null!");
261279
return v1Metadata().thenCompose(metadata -> {
262280
final Map<String, Object> data = buildAccessDenialv1(request.getCreator(), request.getResources(),
263-
request.getModes(), request.getExpiration(), request.getPurposes());
281+
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
264282
final Request req = Request.newBuilder(metadata.issueEndpoint)
265283
.header(CONTENT_TYPE, APPLICATION_JSON)
266284
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build();
@@ -311,9 +329,9 @@ public CompletionStage<AccessGrant> issue(final URI type, final URI recipient, f
311329
return v1Metadata().thenCompose(metadata -> {
312330
final Map<String, Object> data;
313331
if (FQ_ACCESS_GRANT.equals(type)) {
314-
data = buildAccessGrantv1(recipient, resources, modes, expiration, uriPurposes);
332+
data = buildAccessGrantv1(recipient, resources, modes, uriPurposes, expiration, null);
315333
} else if (FQ_ACCESS_REQUEST.equals(type)) {
316-
data = buildAccessRequestv1(recipient, resources, modes, expiration, uriPurposes);
334+
data = buildAccessRequestv1(recipient, resources, modes, uriPurposes, expiration, null);
317335
} else {
318336
throw new AccessGrantException("Unsupported grant type: " + type);
319337
}
@@ -809,7 +827,7 @@ static URI asUri(final Object value) {
809827
}
810828

811829
static Map<String, Object> buildAccessDenialv1(final URI agent, final Set<URI> resources, final Set<String> modes,
812-
final Instant expiration, final Set<URI> purposes) {
830+
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
813831
Objects.requireNonNull(agent, "Access denial agent may not be null!");
814832
final Map<String, Object> consent = new HashMap<>();
815833
consent.put(MODE, modes);
@@ -828,6 +846,9 @@ static Map<String, Object> buildAccessDenialv1(final URI agent, final Set<URI> r
828846
if (expiration != null) {
829847
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
830848
}
849+
if (issuance != null) {
850+
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
851+
}
831852
credential.put(CREDENTIAL_SUBJECT, subject);
832853

833854
final Map<String, Object> data = new HashMap<>();
@@ -836,7 +857,7 @@ static Map<String, Object> buildAccessDenialv1(final URI agent, final Set<URI> r
836857
}
837858

838859
static Map<String, Object> buildAccessGrantv1(final URI agent, final Set<URI> resources, final Set<String> modes,
839-
final Instant expiration, final Set<URI> purposes) {
860+
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
840861
Objects.requireNonNull(agent, "Access grant agent may not be null!");
841862
final Map<String, Object> consent = new HashMap<>();
842863
consent.put(MODE, modes);
@@ -855,6 +876,9 @@ static Map<String, Object> buildAccessGrantv1(final URI agent, final Set<URI> re
855876
if (expiration != null) {
856877
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
857878
}
879+
if (issuance != null) {
880+
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
881+
}
858882
credential.put(CREDENTIAL_SUBJECT, subject);
859883

860884
final Map<String, Object> data = new HashMap<>();
@@ -863,7 +887,7 @@ static Map<String, Object> buildAccessGrantv1(final URI agent, final Set<URI> re
863887
}
864888

865889
static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI> resources, final Set<String> modes,
866-
final Instant expiration, final Set<URI> purposes) {
890+
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
867891
final Map<String, Object> consent = new HashMap<>();
868892
consent.put(HAS_STATUS, "https://w3id.org/GConsent#ConsentStatusRequested");
869893
consent.put(MODE, modes);
@@ -883,6 +907,10 @@ static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI>
883907
if (expiration != null) {
884908
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
885909
}
910+
if (issuance != null) {
911+
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
912+
}
913+
886914
credential.put(CREDENTIAL_SUBJECT, subject);
887915

888916
final Map<String, Object> data = new HashMap<>();

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

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.io.IOException;
3131
import java.io.InputStream;
3232
import java.net.URI;
33+
import java.time.Instant;
34+
import java.util.Collection;
3335
import java.util.Collections;
3436
import java.util.HashMap;
3537
import java.util.HashSet;
@@ -133,4 +135,250 @@ static AccessRequest parse(final String serialization) throws IOException {
133135
}
134136
}
135137
}
138+
139+
/**
140+
* A collection of parameters used for creating access requests.
141+
*
142+
* <p>See, in particular, the {@link AccessGrantClient#requestAccess(RequestParameters)} method.
143+
*/
144+
public static class RequestParameters {
145+
146+
private final URI recipient;
147+
private final Set<URI> resources;
148+
private final Set<String> modes;
149+
private final Set<URI> purposes;
150+
private final Instant expiration;
151+
private final Instant issuedAt;
152+
153+
/* package private */
154+
RequestParameters(final URI recipient, final Set<URI> resources,
155+
final Set<String> modes, final Set<URI> purposes, final Instant expiration, final Instant issuedAt) {
156+
this.recipient = recipient;
157+
this.resources = resources;
158+
this.modes = modes;
159+
this.purposes = purposes;
160+
this.expiration = expiration;
161+
this.issuedAt = issuedAt;
162+
}
163+
164+
/**
165+
* Get the recipient used with an access request operation.
166+
*
167+
* <p>Note: the recipient will typically be the resource owner
168+
*
169+
* @return the recipient's identifier
170+
*/
171+
public URI getRecipient() {
172+
return recipient;
173+
}
174+
175+
/**
176+
* Get the resources used with an access request operation.
177+
*
178+
* @return the resource idnetifiers
179+
*/
180+
public Set<URI> getResources() {
181+
return resources;
182+
}
183+
184+
/**
185+
* Get the access modes used with an access request operation.
186+
*
187+
* @return the access modes
188+
*/
189+
public Set<String> getModes() {
190+
return modes;
191+
}
192+
193+
/**
194+
* Get the purpose identifiers used with an access request operation.
195+
*
196+
* @return the purpose identifiers
197+
*/
198+
public Set<URI> getPurposes() {
199+
return purposes;
200+
}
201+
202+
/**
203+
* Get the requested expiration date used with an access request operation.
204+
*
205+
* <p>Note: an access grant server may select a different expiration date
206+
*
207+
* @return the requested expiration date
208+
*/
209+
public Instant getExpiration() {
210+
return expiration;
211+
}
212+
213+
/**
214+
* Get the requested issuance date used with an access request operation.
215+
*
216+
* <p>Note: an access grant server may select a different issuance date
217+
*
218+
* @return the requested issuance date
219+
*/
220+
public Instant getIssuedAt() {
221+
return issuedAt;
222+
}
223+
224+
/**
225+
* Create a new {@link RequestParameters} builder.
226+
*
227+
* @return the new builder
228+
*/
229+
public static Builder newBuilder() {
230+
return new Builder();
231+
}
232+
233+
/**
234+
* A class for building access request parameters.
235+
*/
236+
public static class Builder {
237+
238+
private final Set<URI> builderResources = new HashSet<>();
239+
private final Set<String> builderModes = new HashSet<>();
240+
private final Set<URI> builderPurposes = new HashSet<>();
241+
private URI builderRecipient;
242+
private Instant builderExpiration;
243+
private Instant builderIssuedAt;
244+
245+
/* package-private */
246+
Builder() {
247+
// Prevent external instantiation
248+
}
249+
250+
/**
251+
* Set a recipient for the access request operation.
252+
*
253+
* <p>Note: this will typically be the identifier of resource owner
254+
*
255+
* @param recipient the recipient identifier, may be {@code null}
256+
* @return this builder
257+
*/
258+
public Builder recipient(final URI recipient) {
259+
builderRecipient = recipient;
260+
return this;
261+
}
262+
263+
/**
264+
* Set a single resource for the access request operation.
265+
*
266+
* @param resource the resource identifier, not {@code null}
267+
* @return this builder
268+
*/
269+
public Builder resource(final URI resource) {
270+
builderResources.add(resource);
271+
return this;
272+
}
273+
274+
/**
275+
* Set multiple resources for the access request operation.
276+
*
277+
* <p>Note: A null value will clear all existing resource values
278+
*
279+
* @param resources the resource identifiers, may be {@code null}
280+
* @return this builder
281+
*/
282+
public Builder resources(final Collection<URI> resources) {
283+
if (resources != null) {
284+
builderResources.addAll(resources);
285+
} else {
286+
builderResources.clear();
287+
}
288+
return this;
289+
}
290+
291+
/**
292+
* Set a single access mode for the access request operation.
293+
*
294+
* @param mode the access mode, not {@code null}
295+
* @return this builder
296+
*/
297+
public Builder mode(final String mode) {
298+
builderModes.add(mode);
299+
return this;
300+
}
301+
302+
/**
303+
* Set multiple access modes for the access request operation.
304+
*
305+
* <p>Note: A null value will clear all existing mode values
306+
*
307+
* @param modes the access modes, may be {@code null}
308+
* @return this builder
309+
*/
310+
public Builder modes(final Collection<String> modes) {
311+
if (modes != null) {
312+
builderModes.addAll(modes);
313+
} else {
314+
builderModes.clear();
315+
}
316+
return this;
317+
}
318+
319+
/**
320+
* Set a single purpose for the access request operation.
321+
*
322+
* @param purpose the purpose identifier, not {@code null}
323+
* @return this builder
324+
*/
325+
public Builder purpose(final URI purpose) {
326+
builderPurposes.add(purpose);
327+
return this;
328+
}
329+
330+
/**
331+
* Set multiple purposes for the access request operation.
332+
*
333+
* <p>Note: A null value will clear all existing purpose values
334+
*
335+
* @param purposes the purpose identifiers, may be {@code null}
336+
* @return this builder
337+
*/
338+
public Builder purposes(final Collection<URI> purposes) {
339+
if (purposes != null) {
340+
builderPurposes.addAll(purposes);
341+
} else {
342+
builderPurposes.clear();
343+
}
344+
return this;
345+
}
346+
347+
/**
348+
* Set a preferred expiration time for the access request operation.
349+
*
350+
* <p>Note: an access grant server may select a different expiration value
351+
*
352+
* @param expiration the expiration time, may be {@code null}.
353+
* @return this builder
354+
*/
355+
public Builder expiration(final Instant expiration) {
356+
builderExpiration = expiration;
357+
return this;
358+
}
359+
360+
/**
361+
* Set a preferred issuance time for the access request operation, likely at a time in the future.
362+
*
363+
* <p>Note: an access grant server may select a different issuance value
364+
*
365+
* @param issuedAt the issuance time, may be {@code null}.
366+
* @return this builder
367+
*/
368+
public Builder issuedAt(final Instant issuedAt) {
369+
builderIssuedAt = issuedAt;
370+
return this;
371+
}
372+
373+
/**
374+
* Build the {@link RequestParameters} object.
375+
*
376+
* @return the access request parameters
377+
*/
378+
public RequestParameters build() {
379+
return new RequestParameters(builderRecipient, builderResources, builderModes, builderPurposes,
380+
builderExpiration, builderIssuedAt);
381+
}
382+
}
383+
}
136384
}

0 commit comments

Comments
 (0)