Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public class AccessGrantClient {
private static final String PROVIDED_CONSENT = "providedConsent";
private static final String FOR_PURPOSE = "forPurpose";
private static final String EXPIRATION_DATE = "expirationDate";
private static final String ISSUANCE_DATE = "issuanceDate";
private static final String CREDENTIAL = "credential";
private static final String SOLID_ACCESS_GRANT = "SolidAccessGrant";
private static final String SOLID_ACCESS_REQUEST = "SolidAccessRequest";
Expand Down Expand Up @@ -180,6 +181,17 @@ public AccessGrantClient session(final Session session) {
return new AccessGrantClient(client.session(session), metadataCache, config);
}

/**
* Issue an access request.
*
* @param request the parameters for the access request
* @return the next stage of completion containing the resulting access request
*/
public CompletionStage<AccessRequest> requestAccess(final AccessRequest.RequestParameters request) {
return requestAccess(request.getRecipient(), request.getResources(),
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
}

/**
* Issue an access request.
*
Expand All @@ -192,10 +204,16 @@ public AccessGrantClient session(final Session session) {
*/
public CompletionStage<AccessRequest> requestAccess(final URI recipient, final Set<URI> resources,
final Set<String> modes, final Set<URI> purposes, final Instant expiration) {
return requestAccess(recipient, resources, modes, purposes, expiration, null);
}

private CompletionStage<AccessRequest> requestAccess(final URI recipient, final Set<URI> resources,
final Set<String> modes, final Set<URI> purposes, final Instant expiration, final Instant issuance) {
Objects.requireNonNull(resources, "Resources may not be null!");
Objects.requireNonNull(modes, "Access modes may not be null!");
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, modes, expiration, purposes);
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, modes, purposes, expiration,
issuance);

final Request req = Request.newBuilder(metadata.issueEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
Expand Down Expand Up @@ -228,7 +246,7 @@ public CompletionStage<AccessGrant> grantAccess(final AccessRequest request) {
Objects.requireNonNull(request, "Request may not be null!");
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data = buildAccessGrantv1(request.getCreator(), request.getResources(),
request.getModes(), request.getExpiration(), request.getPurposes());
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
final Request req = Request.newBuilder(metadata.issueEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build();
Expand Down Expand Up @@ -260,7 +278,7 @@ public CompletionStage<AccessDenial> denyAccess(final AccessRequest request) {
Objects.requireNonNull(request, "Request may not be null!");
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data = buildAccessDenialv1(request.getCreator(), request.getResources(),
request.getModes(), request.getExpiration(), request.getPurposes());
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
final Request req = Request.newBuilder(metadata.issueEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build();
Expand Down Expand Up @@ -311,9 +329,9 @@ public CompletionStage<AccessGrant> issue(final URI type, final URI recipient, f
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data;
if (FQ_ACCESS_GRANT.equals(type)) {
data = buildAccessGrantv1(recipient, resources, modes, expiration, uriPurposes);
data = buildAccessGrantv1(recipient, resources, modes, uriPurposes, expiration, null);
} else if (FQ_ACCESS_REQUEST.equals(type)) {
data = buildAccessRequestv1(recipient, resources, modes, expiration, uriPurposes);
data = buildAccessRequestv1(recipient, resources, modes, uriPurposes, expiration, null);
} else {
throw new AccessGrantException("Unsupported grant type: " + type);
}
Expand Down Expand Up @@ -809,7 +827,7 @@ static URI asUri(final Object value) {
}

static Map<String, Object> buildAccessDenialv1(final URI agent, final Set<URI> resources, final Set<String> modes,
final Instant expiration, final Set<URI> purposes) {
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
Objects.requireNonNull(agent, "Access denial agent may not be null!");
final Map<String, Object> consent = new HashMap<>();
consent.put(MODE, modes);
Expand All @@ -828,6 +846,9 @@ static Map<String, Object> buildAccessDenialv1(final URI agent, final Set<URI> r
if (expiration != null) {
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
}
if (issuance != null) {
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
}
credential.put(CREDENTIAL_SUBJECT, subject);

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

static Map<String, Object> buildAccessGrantv1(final URI agent, final Set<URI> resources, final Set<String> modes,
final Instant expiration, final Set<URI> purposes) {
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
Objects.requireNonNull(agent, "Access grant agent may not be null!");
final Map<String, Object> consent = new HashMap<>();
consent.put(MODE, modes);
Expand All @@ -855,6 +876,9 @@ static Map<String, Object> buildAccessGrantv1(final URI agent, final Set<URI> re
if (expiration != null) {
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
}
if (issuance != null) {
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
}
credential.put(CREDENTIAL_SUBJECT, subject);

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

static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI> resources, final Set<String> modes,
final Instant expiration, final Set<URI> purposes) {
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
final Map<String, Object> consent = new HashMap<>();
consent.put(HAS_STATUS, "https://w3id.org/GConsent#ConsentStatusRequested");
consent.put(MODE, modes);
Expand All @@ -883,6 +907,10 @@ static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI>
if (expiration != null) {
credential.put(EXPIRATION_DATE, expiration.truncatedTo(ChronoUnit.SECONDS).toString());
}
if (issuance != null) {
credential.put(ISSUANCE_DATE, issuance.truncatedTo(ChronoUnit.SECONDS).toString());
}

credential.put(CREDENTIAL_SUBJECT, subject);

final Map<String, Object> data = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -133,4 +135,250 @@ static AccessRequest parse(final String serialization) throws IOException {
}
}
}

/**
* A collection of parameters used for creating access requests.
*
* <p>See, in particular, the {@link AccessGrantClient#requestAccess(RequestParameters)} method.
*/
public static class RequestParameters {

private final URI recipient;
private final Set<URI> resources;
private final Set<String> modes;
private final Set<URI> purposes;
private final Instant expiration;
private final Instant issuedAt;

/* package private */
RequestParameters(final URI recipient, final Set<URI> resources,
final Set<String> modes, final Set<URI> purposes, final Instant expiration, final Instant issuedAt) {
this.recipient = recipient;
this.resources = resources;
this.modes = modes;
this.purposes = purposes;
this.expiration = expiration;
this.issuedAt = issuedAt;
}

/**
* Get the recipient used with an access request operation.
*
* <p>Note: the recipient will typically be the resource owner
*
* @return the recipient's identifier
*/
public URI getRecipient() {
return recipient;
}

/**
* Get the resources used with an access request operation.
*
* @return the resource idnetifiers
*/
public Set<URI> getResources() {
return resources;
}

/**
* Get the access modes used with an access request operation.
*
* @return the access modes
*/
public Set<String> getModes() {
return modes;
}

/**
* Get the purpose identifiers used with an access request operation.
*
* @return the purpose identifiers
*/
public Set<URI> getPurposes() {
return purposes;
}

/**
* Get the requested expiration date used with an access request operation.
*
* <p>Note: an access grant server may select a different expiration date
*
* @return the requested expiration date
*/
public Instant getExpiration() {
return expiration;
}

/**
* Get the requested issuance date used with an access request operation.
*
* <p>Note: an access grant server may select a different issuance date
*
* @return the requested issuance date
*/
public Instant getIssuedAt() {
return issuedAt;
}

/**
* Create a new {@link RequestParameters} builder.
*
* @return the new builder
*/
public static Builder newBuilder() {
return new Builder();
}

/**
* A class for building access request parameters.
*/
public static class Builder {

private final Set<URI> builderResources = new HashSet<>();
private final Set<String> builderModes = new HashSet<>();
private final Set<URI> builderPurposes = new HashSet<>();
private URI builderRecipient;
private Instant builderExpiration;
private Instant builderIssuedAt;

/* package-private */
Builder() {
// Prevent external instantiation
}

/**
* Set a recipient for the access request operation.
*
* <p>Note: this will typically be the identifier of resource owner
*
* @param recipient the recipient identifier, may be {@code null}
* @return this builder
*/
public Builder recipient(final URI recipient) {
builderRecipient = recipient;
return this;
}

/**
* Set a single resource for the access request operation.
*
* @param resource the resource identifier, not {@code null}
* @return this builder
*/
public Builder resource(final URI resource) {
builderResources.add(resource);
return this;
}

/**
* Set multiple resources for the access request operation.
*
* <p>Note: A null value will clear all existing resource values
*
* @param resources the resource identifiers, may be {@code null}
* @return this builder
*/
public Builder resources(final Collection<URI> resources) {
if (resources != null) {
builderResources.addAll(resources);
} else {
builderResources.clear();
}
return this;
}

/**
* Set a single access mode for the access request operation.
*
* @param mode the access mode, not {@code null}
* @return this builder
*/
public Builder mode(final String mode) {
builderModes.add(mode);
return this;
}

/**
* Set multiple access modes for the access request operation.
*
* <p>Note: A null value will clear all existing mode values
*
* @param modes the access modes, may be {@code null}
* @return this builder
*/
public Builder modes(final Collection<String> modes) {
if (modes != null) {
builderModes.addAll(modes);
} else {
builderModes.clear();
}
return this;
}

/**
* Set a single purpose for the access request operation.
*
* @param purpose the purpose identifier, not {@code null}
* @return this builder
*/
public Builder purpose(final URI purpose) {
builderPurposes.add(purpose);
return this;
}

/**
* Set multiple purposes for the access request operation.
*
* <p>Note: A null value will clear all existing purpose values
*
* @param purposes the purpose identifiers, may be {@code null}
* @return this builder
*/
public Builder purposes(final Collection<URI> purposes) {
if (purposes != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a test case for a collection cleared this way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

builderPurposes.addAll(purposes);
} else {
builderPurposes.clear();
}
return this;
}

/**
* Set a preferred expiration time for the access request operation.
*
* <p>Note: an access grant server may select a different expiration value
*
* @param expiration the expiration time, may be {@code null}.
* @return this builder
*/
public Builder expiration(final Instant expiration) {
builderExpiration = expiration;
return this;
}

/**
* Set a preferred issuance time for the access request operation, likely at a time in the future.
*
* <p>Note: an access grant server may select a different issuance value
*
* @param issuedAt the issuance time, may be {@code null}.
* @return this builder
*/
public Builder issuedAt(final Instant issuedAt) {
builderIssuedAt = issuedAt;
return this;
}

/**
* Build the {@link RequestParameters} object.
*
* @return the access request parameters
*/
public RequestParameters build() {
return new RequestParameters(builderRecipient, builderResources, builderModes, builderPurposes,
builderExpiration, builderIssuedAt);
}
}
}
}
Loading