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 @@ -37,6 +37,7 @@
/** A base class for access credentials. **/
public class AccessCredential {

static final int SINGLETON = 1;
private static final Logger LOGGER = LoggerFactory.getLogger(AccessCredential.class);

protected static final String TYPE = "type";
Expand All @@ -49,6 +50,7 @@ public class AccessCredential {
private final Set<String> modes;
private final Set<URI> purposes;
private final Set<URI> resources;
private final Set<String> templates;
private final URI recipient;
private final URI creator;
private final Instant expiration;
Expand All @@ -71,6 +73,7 @@ protected AccessCredential(final URI identifier, final String credential,

this.purposes = data.getPurposes();
this.resources = data.getResources();
this.templates = data.getTemplates();
this.modes = data.getModes();
this.recipient = data.getRecipient();

Expand Down Expand Up @@ -165,6 +168,15 @@ public Set<URI> getResources() {
return resources;
}

/**
* Get the templates associated with the access credential.
*
* @return the associated templates
*/
public Set<String> getTemplates() {
return templates;
}

/**
* Get the creator of this access credential.
*
Expand Down Expand Up @@ -281,6 +293,7 @@ public static class CredentialData {
private final Set<String> modes;
private final Set<URI> purposes;
private final Set<URI> resources;
private final Set<String> templates;
private final URI recipient;
private final URI accessRequest;

Expand All @@ -294,7 +307,7 @@ public static class CredentialData {
*/
public CredentialData(final Set<URI> resources, final Set<String> modes,
final Set<URI> purposes, final URI recipient) {
this(resources, modes, purposes, recipient, null);
this(resources, Collections.emptySet(), modes, purposes, recipient, null);
}

/**
Expand All @@ -308,6 +321,22 @@ public CredentialData(final Set<URI> resources, final Set<String> modes,
*/
public CredentialData(final Set<URI> resources, final Set<String> modes,
final Set<URI> purposes, final URI recipient, final URI accessRequest) {
this(resources, Collections.emptySet(), modes, purposes, recipient, accessRequest);
}

/**
* Create a collection of user-managed credential data.
*
* @param resources the resources referenced by the credential
* @param templates the resource templates referenced by the credential
* @param modes the access modes defined by this credential
* @param purposes the purposes associated with this credential
* @param recipient the recipient for this credential, may be {@code null}
* @param accessRequest the access request identifier, may be {@code null}
*/
public CredentialData(final Set<URI> resources, final Set<String> templates, final Set<String> modes,
final Set<URI> purposes, final URI recipient, final URI accessRequest) {
this.templates = Objects.requireNonNull(templates, "templates may not be null!");
this.modes = Objects.requireNonNull(modes, "modes may not be null!");
this.purposes = Objects.requireNonNull(purposes, "purposes may not be null!");
this.resources = Objects.requireNonNull(resources, "resources may not be null!");
Expand Down Expand Up @@ -342,6 +371,15 @@ public Set<URI> getResources() {
return resources;
}

/**
* Get the URL templates associated with this credential.
*
* @return the URL templates
*/
public Set<String> getTemplates() {
return templates;
}

/**
* Get the recipient associated with this credential.
*
Expand Down Expand Up @@ -393,7 +431,7 @@ static Stream<URI> filterUris(final String uri) {
try {
return Stream.of(URI.create(uri));
} catch (final IllegalArgumentException ex) {
LOGGER.debug("Ignoring non-URI purpose: {}", ex.getMessage());
LOGGER.atDebug().setMessage("Ignoring non-URI purpose: {}").addArgument(ex::getMessage).log();
}
return Stream.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ static Set<String> getSupportedTypes() {
}

static AccessDenial parse(final String serialization) throws IOException {
try (final InputStream in = new ByteArrayInputStream(serialization.getBytes())) {
try (final InputStream in = new ByteArrayInputStream(serialization.getBytes(UTF_8))) {
// TODO process as JSON-LD
final Map<String, Object> data = jsonService.fromJson(in,
new HashMap<String, Object>(){}.getClass().getGenericSuperclass());

final List<Map<String, Object>> vcs = getCredentialsFromPresentation(data, supportedTypes);
if (vcs.size() != 1) {
if (vcs.size() != SINGLETON) {
throw new IllegalArgumentException(
"Invalid Access Denial: ambiguous number of verifiable credentials");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ static Set<String> getSupportedTypes() {
}

static AccessGrant parse(final String serialization) throws IOException {
try (final InputStream in = new ByteArrayInputStream(serialization.getBytes())) {
try (final InputStream in = new ByteArrayInputStream(serialization.getBytes(UTF_8))) {
// TODO process as JSON-LD
final Map<String, Object> data = jsonService.fromJson(in,
new HashMap<String, Object>(){}.getClass().getGenericSuperclass());

final List<Map<String, Object>> vcs = getCredentialsFromPresentation(data, supportedTypes);
if (vcs.size() != 1) {
if (vcs.size() != SINGLETON) {
throw new IllegalArgumentException(
"Invalid Access Grant: ambiguous number of verifiable credentials");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -105,6 +106,7 @@ public class AccessGrantClient {
private static final String IS_PROVIDED_TO = "isProvidedTo";
private static final String IS_CONSENT_FOR_DATA_SUBJECT = "isConsentForDataSubject";
private static final String FOR_PERSONAL_DATA = "forPersonalData";
private static final String TEMPLATE = "template";
private static final String HAS_STATUS = "hasStatus";
private static final String REQUEST = "request";
private static final String VERIFIED_REQUEST = "verifiedRequest";
Expand Down Expand Up @@ -183,7 +185,10 @@ private AccessGrantClient(final Client client, final ClientCache<URI, Metadata>
this.config = Objects.requireNonNull(config, "config may not be null!");
this.metadataCache = Objects.requireNonNull(metadataCache, "metadataCache may not be null!");
this.jsonService = ServiceProvider.getJsonService();
LOGGER.debug("Initializing Access Grant client with issuer: {}", config.getIssuer());
LOGGER.atDebug()
.setMessage("Initializing Access Grant client with issuer: {}")
.addArgument(config::getIssuer)
.log();
}

/**
Expand All @@ -204,7 +209,7 @@ public AccessGrantClient session(final Session session) {
* @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(),
return requestAccess(request.getRecipient(), request.getResources(), request.getTemplates(),
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt());
}

Expand All @@ -220,16 +225,23 @@ public CompletionStage<AccessRequest> requestAccess(final AccessRequest.RequestP
*/
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);
return requestAccess(recipient, resources, Collections.emptySet(), 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) {
final Set<String> templates, final Set<String> modes, final Set<URI> purposes, final Instant expiration,
final Instant issuance) {
Objects.requireNonNull(resources, "Resources may not be null!");
Objects.requireNonNull(templates, "Templates may not be null!");
Objects.requireNonNull(modes, "Access modes may not be null!");
if (templates.isEmpty() && resources.isEmpty()) {
LOGGER.warn("Both resources and templates are empty in access request");
} else if (!templates.isEmpty() && !resources.isEmpty()) {
LOGGER.warn("Both resources and templates are non-empty in access request");
}
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, modes, purposes, expiration,
issuance);
final Map<String, Object> data = buildAccessRequestv1(recipient, resources, templates, modes, purposes,
expiration, issuance);

final Request req = Request.newBuilder(metadata.issueEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON)
Expand All @@ -253,26 +265,79 @@ private CompletionStage<AccessRequest> requestAccess(final URI recipient, final
}

/**
* Issue an access grant based on an access request. The access request is not verified.
* Issue an access grant based on an access request.
*
* <p>
* The access request is not verified.
* Any templated URLs are ignored.
*
* @param request the access request
* @return the next stage of completion containing the issued access grant
*/
public CompletionStage<AccessGrant> grantAccess(final AccessRequest request) {
return grantAccess(request, false);
return grantAccess(request, templates -> Collections.emptySet(), false);
}

/**
* Issue an access grant based on an access request.
*
* <p>
* The access request is verified.
* Any templated URLs are processed according to the provided mapping function.
*
* @param request the access request
* @param mapping a mapping function for template URLs
* @return the next stage of completion containing the issued access grant
*/
public CompletionStage<AccessGrant> grantAccess(final AccessRequest request,
final Function<Set<String>, Set<URI>> mapping) {
return grantAccess(request, mapping, true);
}

/**
* Issue an access grant based on an access request.
*
* <p>
* Any templated URLs are ignored.
*
* @param request the access request
* @param verifyRequest whether the request should be verified before issuing the access grant
* @return the next stage of completion containing the issued access grant
*/
public CompletionStage<AccessGrant> grantAccess(final AccessRequest request, final boolean verifyRequest) {
return grantAccess(request, templates -> Collections.emptySet(), verifyRequest);
}

/**
* Issue an access grant based on an access request.
*
* @param request the access request
* @param mapping a mapping function for template URLs
* @param verifyRequest whether the request should be verified before issuing the access grant
* @return the next stage of completion containing the issued access grant
*/
public CompletionStage<AccessGrant> grantAccess(final AccessRequest request,
final Function<Set<String>, Set<URI>> mapping, final boolean verifyRequest) {
Objects.requireNonNull(request, "Request may not be null!");
final var templated = mapping.apply(request.getTemplates());
if (templated.size() != request.getTemplates().size()) {
LOGGER.atDebug()
.setMessage("Unexpected number of mapped template values, found ({}) expected ({})")
.addArgument(templated::size)
.addArgument(() -> request.getTemplates().size())
.log();
}
final var resources = new HashSet<URI>(request.getResources());
resources.addAll(templated);
if (resources.isEmpty()) {
LOGGER.atWarn()
.setMessage("No data URLs supplied: {} resource URLs and {} mapped templates")
.addArgument(() -> request.getResources().size())
.addArgument(templated::size)
.log();
}
return v1Metadata().thenCompose(metadata -> {
final Map<String, Object> data = buildAccessGrantv1(request.getCreator(), request.getResources(),
final Map<String, Object> data = buildAccessGrantv1(request.getCreator(), resources,
request.getModes(), request.getPurposes(), request.getExpiration(), request.getIssuedAt(),
request.getIdentifier(), verifyRequest);
final Request req = Request.newBuilder(metadata.issueEndpoint)
Expand Down Expand Up @@ -815,12 +880,17 @@ static Map<String, Object> buildAccessGrantv1(
return data;
}

static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI> resources, final Set<String> modes,
final Set<URI> purposes, final Instant expiration, final Instant issuance) {
static Map<String, Object> buildAccessRequestv1(final URI agent, final Set<URI> resources,
final Set<String> templates, final Set<String> modes, 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);
consent.put(FOR_PERSONAL_DATA, resources);
if (!resources.isEmpty()) {
consent.put(FOR_PERSONAL_DATA, resources);
} else {
consent.put(TEMPLATE, templates);
}
if (agent != null) {
consent.put(IS_CONSENT_FOR_DATA_SUBJECT, agent);
}
Expand Down
Loading
Loading