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
@@ -0,0 +1,228 @@
/*
* Copyright 2023 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.inrupt.client.accessgrant;

import java.net.URI;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
* An object to represent an access credential query.
*
* @param <T> The access credential type
*/
public class AccessCredentialQuery<T extends AccessCredential> {

private static final URI SOLID_ACCESS_GRANT = URI.create("SolidAccessGrant");
private static final URI SOLID_ACCESS_REQUEST = URI.create("SolidAccessRequest");
private static final URI SOLID_ACCESS_DENIAL = URI.create("SolidAccessDenial");

private final URI type;
private final Set<URI> purposes;
private final Set<String> modes;
private final URI resource;
private final URI creator;
private final URI recipient;
private final Class<T> clazz;

/**
* Create an access credential query.
*
* @param resource the resource, may be {@code null}
* @param creator the creator, may be {@code null}
* @param recipient the recipient, may be {@code null}
* @param purposes the purposes, never {@code null}
* @param modes the access modes, never {@code null}
* @param clazz the credential type, never {@code null}
*/
AccessCredentialQuery(final URI resource, final URI creator, final URI recipient,
final Set<URI> purposes, final Set<String> modes, final Class<T> clazz) {
this.clazz = Objects.requireNonNull(clazz, "The clazz parameter must not be null!");

if (AccessGrant.class.isAssignableFrom(clazz)) {
this.type = SOLID_ACCESS_GRANT;
} else if (AccessRequest.class.isAssignableFrom(clazz)) {
this.type = SOLID_ACCESS_REQUEST;
} else if (AccessDenial.class.isAssignableFrom(clazz)) {
this.type = SOLID_ACCESS_DENIAL;
} else {
throw new AccessGrantException("Unsupported type " + clazz + " in query request");
}

this.resource = resource;
this.creator = creator;
this.recipient = recipient;
this.purposes = purposes;
this.modes = modes;
}

/**
* Get the access credential type value.
*
* @return the type, never {@code null}
*/
public URI getType() {
return type;
}

/**
* Get the requested resource.
*
* @return the resource, may be {@code null}
*/
public URI getResource() {
return resource;
Copy link
Contributor

Choose a reason for hiding this comment

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

Even if it may not be supported by all Access Management Applications, an Access Grant may cover multiple resources. Is it something that should be reflected in the querying capabilities? Or do we want to push this constraint down to the client libraries, in which case we should consider the same for the JC client.

}

/**
* Get the requested creator.
*
* @return the creator, may be {@code null}
*/
public URI getCreator() {
return creator;
}

/**
* Get the requested recipient.
*
* @return the recipient, may be {@code null}
*/
public URI getRecipient() {
return recipient;
}

/**
* Get the requested purposes.
*
* @return the purpose identifiers, never {@code null}
*/
public Set<URI> getPurposes() {
return purposes;
}

/**
* Get the requested access modes.
*
* @return the access modes, never {@code null}
*/
public Set<String> getModes() {
return modes;
}

/* package private */
Class<T> getAccessCredentialType() {
return clazz;
}

/**
* Create a new access credential query builder.
*
* @return the builder
*/
public static Builder newBuilder() {
return new Builder();
}

/**
* A builder class for access credential queries.
*/
public static class Builder {

private final Set<URI> purposes = new HashSet<>();
private final Set<String> modes = new HashSet<>();
private URI builderResource;
private URI builderCreator;
private URI builderRecipient;

/**
* Set the resource identifier.
*
* @param resource the resource identifier, may be {@code null}
* @return this builder
*/
public Builder resource(final URI resource) {
builderResource = resource;
return this;
}

/**
* Add a purpose identifier.
*
* @param purpose a purpose identifier; {@code null} values have no effect.
* @return this builder
*/
public Builder purpose(final URI purpose) {
if (purpose != null) {
purposes.add(purpose);
}
return this;
}

/**
* Add an access mode value.
*
* @param mode a mode value; {@code null} values have no effect.
* @return this builder
*/
public Builder mode(final String mode) {
if (mode != null) {
modes.add(mode);
}
return this;
}

/**
* Set the creator identifier.
*
* @param creator the creator identifier, may be {@code null}
* @return this builder
*/
public Builder creator(final URI creator) {
builderCreator = creator;
return this;
}

/**
* Set the recipient identifier.
*
* @param recipient the recipient identifier, may be {@code null}
* @return this builder
*/
public Builder recipient(final URI recipient) {
builderRecipient = recipient;
return this;
}

/**
* Build the access credential query.
*
* @param <T> the credential type
* @param clazz the credential type
* @return the query object
*/
public <T extends AccessCredential> AccessCredentialQuery build(final Class<T> clazz) {
return new AccessCredentialQuery<T>(builderResource, builderCreator, builderRecipient, purposes, modes,
clazz);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,28 @@ public CompletionStage<AccessCredentialVerification> verify(final AccessCredenti
*/
public <T extends AccessCredential> CompletionStage<List<T>> query(final URI resource, final URI creator,
final URI recipient, final URI purpose, final String mode, final Class<T> clazz) {

final Set<String> modes = mode != null ? Collections.singleton(mode) : Collections.emptySet();
final Set<URI> purposes = purpose != null ? Collections.singleton(purpose) : Collections.emptySet();

return query(resource, creator, recipient, purposes, modes, clazz);
}

/**
* Perform an Access Grant query.
*
* @param <T> the AccessCredential type
* @param query the access credential query, never {@code null}
* @return the next stage of completion, including the matched Access Credentials
*/
public <T extends AccessCredential> CompletionStage<List<T>> query(final AccessCredentialQuery<T> query) {
Objects.requireNonNull(query, "The query may not be null!");
return query(query.getResource(), query.getCreator(), query.getRecipient(), query.getPurposes(),
query.getModes(), query.getAccessCredentialType());
}

private <T extends AccessCredential> CompletionStage<List<T>> query(final URI resource, final URI creator,
final URI recipient, final Set<URI> purposes, final Set<String> modes, final Class<T> clazz) {
Objects.requireNonNull(clazz, "The clazz parameter must not be null!");

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

return v1Metadata().thenCompose(metadata -> {
final List<CompletableFuture<List<T>>> futures = buildQuery(config.getIssuer(), type,
resource, creator, recipient, purpose, mode).stream()
resource, creator, recipient, purposes, modes).stream()
.map(data -> Request.newBuilder(metadata.queryEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build())
Expand Down Expand Up @@ -455,7 +477,7 @@ public CompletionStage<List<AccessGrant>> query(final URI type, final URI agent,
Objects.requireNonNull(type, "The type parameter must not be null!");
return v1Metadata().thenCompose(metadata -> {
final List<CompletableFuture<List<AccessGrant>>> futures = buildQuery(config.getIssuer(), type,
resource, null, agent, null, mode).stream()
resource, null, agent, Collections.emptySet(), Collections.singleton(mode)).stream()
.map(data -> Request.newBuilder(metadata.queryEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
.POST(Request.BodyPublishers.ofByteArray(serialize(data))).build())
Expand Down Expand Up @@ -688,14 +710,15 @@ static Collection<Object> getCredentials(final Map<String, Object> data) {
}

static List<Map<String, Object>> buildQuery(final URI issuer, final URI type, final URI resource, final URI creator,
final URI recipient, final URI purpose, final String mode) {
final URI recipient, final Set<URI> purposes, final Set<String> modes) {
final List<Map<String, Object>> queries = new ArrayList<>();
buildQuery(queries, issuer, type, resource, creator, recipient, purpose, mode);
buildQuery(queries, issuer, type, resource, creator, recipient, purposes, modes);
return queries;
}

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

final Map<String, Object> consent = buildConsent(type, resource, recipient, purpose, mode);
final Map<String, Object> consent = buildConsent(type, resource, recipient, purposes, modes);
if (!consent.isEmpty()) {
if (isAccessGrant(type) || isAccessDenial(type)) {
subject.put(PROVIDED_CONSENT, consent);
Expand All @@ -726,12 +749,12 @@ static void buildQuery(final List<Map<String, Object>> queries, final URI issuer
// Recurse
final URI parent = getParent(resource);
if (parent != null) {
buildQuery(queries, issuer, type, parent, creator, recipient, purpose, mode);
buildQuery(queries, issuer, type, parent, creator, recipient, purposes, modes);
}
}

static Map<String, Object> buildConsent(final URI type, final URI resource, final URI recipient, final URI purpose,
final String mode) {
static Map<String, Object> buildConsent(final URI type, final URI resource, final URI recipient,
final Set<URI> purposes, final Set<String> modes) {
final Map<String, Object> consent = new HashMap<>();
if (recipient != null) {
if (isAccessGrant(type) || isAccessDenial(type)) {
Expand All @@ -743,11 +766,11 @@ static Map<String, Object> buildConsent(final URI type, final URI resource, fina
if (resource != null) {
consent.put(FOR_PERSONAL_DATA, resource);
}
if (purpose != null) {
consent.put(FOR_PURPOSE, purpose);
if (!purposes.isEmpty()) {
consent.put(FOR_PURPOSE, purposes);
}
if (mode != null) {
consent.put(MODE, mode);
if (!modes.isEmpty()) {
consent.put(MODE, modes);
}
return consent;
}
Expand Down
Loading