Skip to content

Commit

Permalink
feat: Credential create/update API
Browse files Browse the repository at this point in the history
  • Loading branch information
bscholtes1A committed Aug 26, 2024
1 parent df5e38b commit 1408a52
Show file tree
Hide file tree
Showing 20 changed files with 1,134 additions and 170 deletions.
14 changes: 7 additions & 7 deletions DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ maven/mavencentral/com.google.code.gson/gson/2.10.1, Apache-2.0, approved, #6159
maven/mavencentral/com.google.crypto.tink/tink/1.14.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.errorprone/error_prone_annotations/2.11.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.errorprone/error_prone_annotations/2.22.0, Apache-2.0, approved, #10661
maven/mavencentral/com.google.errorprone/error_prone_annotations/2.26.1, Apache-2.0, approved, #13657
maven/mavencentral/com.google.errorprone/error_prone_annotations/2.28.0, Apache-2.0, approved, #15107
maven/mavencentral/com.google.guava/failureaccess/1.0.1, Apache-2.0, approved, CQ22654
maven/mavencentral/com.google.guava/failureaccess/1.0.2, Apache-2.0, approved, CQ22654
maven/mavencentral/com.google.guava/guava/28.1-android, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.guava/guava/28.2-android, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ22437
maven/mavencentral/com.google.guava/guava/31.0.1-android, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.guava/guava/31.1-jre, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.google.guava/guava/33.2.0-jre, Apache-2.0 AND CC0-1.0 AND (Apache-2.0 AND CC-PDDC), approved, #14607
maven/mavencentral/com.google.guava/guava/33.3.0-jre, Apache-2.0 AND CC0-1.0 AND (Apache-2.0 AND CC-PDDC) AND (Apache-2.0 AND CC0-1.0), approved, #15952
maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava, Apache-2.0, approved, CQ22657
maven/mavencentral/com.google.j2objc/j2objc-annotations/1.3, Apache-2.0, approved, CQ21195
maven/mavencentral/com.google.protobuf/protobuf-java/3.25.3, BSD-3-Clause, approved, clearlydefined
Expand All @@ -72,7 +72,7 @@ maven/mavencentral/com.lmax/disruptor/3.4.4, Apache-2.0, approved, clearlydefine
maven/mavencentral/com.networknt/json-schema-validator/1.0.76, Apache-2.0, approved, CQ22638
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.28, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.40, Apache-2.0, approved, #15156
maven/mavencentral/com.puppycrawl.tools/checkstyle/10.17.0, LGPL-2.1-or-later AND (Apache-2.0 AND LGPL-2.1-or-later) AND Apache-2.0, approved, #15077
maven/mavencentral/com.puppycrawl.tools/checkstyle/10.18.0, LGPL-2.1-or-later, restricted, clearlydefined
maven/mavencentral/com.samskivert/jmustache/1.15, BSD-2-Clause AND BSD-3-Clause, approved, clearlydefined
maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, Apache-2.0, approved, #11159
maven/mavencentral/com.squareup.okhttp3/okhttp/4.12.0, Apache-2.0, approved, #15227
Expand Down Expand Up @@ -180,8 +180,8 @@ maven/mavencentral/net.javacrumbs.json-unit/json-unit-core/2.36.0, Apache-2.0, a
maven/mavencentral/net.minidev/accessors-smart/2.4.7, Apache-2.0, approved, #7515
maven/mavencentral/net.minidev/json-smart/2.4.7, Apache-2.0, approved, #3288
maven/mavencentral/net.sf.jopt-simple/jopt-simple/5.0.4, MIT, approved, CQ13174
maven/mavencentral/net.sf.saxon/Saxon-HE/12.4, MPL-2.0 AND (MPL-2.0 AND Apache-2.0) AND (MPL-2.0 AND LicenseRef-X11-style) AND MPL-1.0 AND W3C, approved, #12716
maven/mavencentral/org.antlr/antlr4-runtime/4.13.1, BSD-3-Clause, approved, #10767
maven/mavencentral/net.sf.saxon/Saxon-HE/12.5, NOASSERTION, restricted, clearlydefined
maven/mavencentral/org.antlr/antlr4-runtime/4.13.2, BSD-3-Clause, approved, #10767
maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368
maven/mavencentral/org.apache.commons/commons-digester3/3.2, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apache.commons/commons-lang3/3.10, Apache-2.0, approved, clearlydefined
Expand Down Expand Up @@ -224,7 +224,7 @@ maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.78.1, MIT, approved, #14435
maven/mavencentral/org.ccil.cowan.tagsoup/tagsoup/1.2.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.12.0, MIT, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.43.0, MIT, approved, clearlydefined
maven/mavencentral/org.checkerframework/checker-qual/3.46.0, MIT, approved, clearlydefined
maven/mavencentral/org.codehaus.plexus/plexus-classworlds/2.6.0, Apache-2.0 AND Plexus, approved, CQ22821
maven/mavencentral/org.codehaus.plexus/plexus-component-annotations/2.1.0, Apache-2.0, approved, #809
maven/mavencentral/org.codehaus.plexus/plexus-container-default/2.1.0, Apache-2.0, approved, clearlydefined
Expand Down Expand Up @@ -370,7 +370,7 @@ maven/mavencentral/org.ow2.asm/asm-commons/9.7, BSD-3-Clause, approved, #14075
maven/mavencentral/org.ow2.asm/asm-tree/9.7, BSD-3-Clause, approved, #14073
maven/mavencentral/org.ow2.asm/asm/9.1, BSD-3-Clause, approved, CQ23029
maven/mavencentral/org.ow2.asm/asm/9.7, BSD-3-Clause, approved, #14076
maven/mavencentral/org.postgresql/postgresql/42.7.3, BSD-2-Clause AND Apache-2.0, approved, #11681
maven/mavencentral/org.postgresql/postgresql/42.7.4, BSD-2-Clause AND Apache-2.0, approved, #11681
maven/mavencentral/org.reflections/reflections/0.10.2, Apache-2.0 AND WTFPL, approved, clearlydefined
maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined
maven/mavencentral/org.slf4j/slf4j-api/1.7.22, MIT, approved, CQ11943
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Identity Hub - Identity Write Credentials API

## Decision

Write endpoints (create/update) will be added to the Identity Hub identity API.

## Rationale

As of now the issuance of VCs into the Identity Hub is not defined as part of the DCP specification. Thus, it is
impossible
for now to add VCs within the Identity Hub at run time.

To circumvent this issue, we will add write endpoints (create/update) to the Identity Hub identity API.

> **_NOTE:_** It is strongly discouraged to expose these write endpoints public over the internet as they can be used
> by malicious actors to compromise your VCs. The same warning applies for all identity APIs.
1 change: 1 addition & 0 deletions docs/developer/decision-records/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
- [2022-07-29 Self-description](2022-07-29-self-description/)
- [2022-08-12 Code Quality Tooling](2022-08-12-code-quality-tooling/)
- [2023-01-20 Credentials Verifier Output Format](2023-01-20-credentials-verifier-output-format/)
- [2024-08-23 Identity Hub Write Credentials API](2024-08-23-identity_hub_identity_write_credentials_api/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright (c) 2024 Amadeus IT Group.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Amadeus IT Group - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.tests;

import io.restassured.http.Header;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialManifest;
import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubEndToEndExtension;
import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubEndToEndTestContext;
import org.eclipse.edc.junit.annotations.EndToEndTest;
import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.query.QuerySpec;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;

import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.notNullValue;

public class VerifiableCredentialApiEndToEndTest {

abstract static class Tests {

@AfterEach
void tearDown(ParticipantContextService store) {
// purge all users
store.query(QuerySpec.max()).getContent()
.forEach(pc -> store.deleteParticipantContext(pc.getParticipantId()).getContent());
}

@Test
void findById(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

var credential = context.createCredential();
var resourceId = context.storeCredential(credential, user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.get("/v1alpha/participants/%s/credentials/%s".formatted(toBase64(user), resourceId))
.then()
.log().ifValidationFails()
.statusCode(200)
.body(notNullValue()));
}

@Test
void create(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> {
var vc = context.createCredential();
var resourceId = UUID.randomUUID().toString();
var manifest = createManifest(user, vc).id(resourceId).build();
context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.body(manifest)
.post("/v1alpha/participants/%s/credentials".formatted(toBase64(user)))
.then()
.log().ifValidationFails()
.statusCode(204)
.body(notNullValue());

var resource = context.getCredential(resourceId).orElseThrow(() -> new EdcException("Failed to credential with id %s".formatted(resourceId)));
assertThat(resource.getVerifiableCredential().credential()).usingRecursiveComparison().isEqualTo(vc);
});
}

@Test
void update(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> {
var credential1 = context.createCredential();
var credential2 = context.createCredential();
var resourceId1 = context.storeCredential(credential1, user);
var manifest = createManifest(user, credential2).id(resourceId1).build();
context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.body(manifest)
.put("/v1alpha/participants/%s/credentials".formatted(toBase64(user)))
.then()
.log().ifValidationFails()
.statusCode(204)
.body(notNullValue());

var resource = context.getCredential(resourceId1).orElseThrow(() -> new EdcException("Failed to retrieve credential with id %s".formatted(resourceId1)));
assertThat(resource.getVerifiableCredential().credential()).usingRecursiveComparison().isEqualTo(credential2);
});
}

@Test
void delete(IdentityHubEndToEndTestContext context) {
var superUserKey = context.createSuperUser();
var user = "user1";
var token = context.createParticipant(user);

assertThat(Arrays.asList(token, superUserKey))
.allSatisfy(t -> {
var credential = context.createCredential();
var resourceId = context.storeCredential(credential, user);
context.getIdentityApiEndpoint().baseRequest()
.contentType(JSON)
.header(new Header("x-api-key", t))
.delete("/v1alpha/participants/%s/credentials/%s".formatted(toBase64(user), resourceId))
.then()
.log().ifValidationFails()
.statusCode(204)
.body(notNullValue());

var resource = context.getCredential(resourceId);
assertThat(resource.isEmpty()).isTrue();
});
}

private String toBase64(String s) {
return Base64.getUrlEncoder().encodeToString(s.getBytes());
}

private VerifiableCredentialManifest.Builder createManifest(String participantId, VerifiableCredential vc) {
return VerifiableCredentialManifest.Builder.newInstance()
.verifiableCredentialContainer(new VerifiableCredentialContainer("rawVc", CredentialFormat.JWT, vc))
.participantId(participantId);
}

}

@Nested
@EndToEndTest
@ExtendWith(IdentityHubEndToEndExtension.InMemory.class)
class InMemory extends Tests {
}

@Nested
@PostgresqlIntegrationTest
@ExtendWith(IdentityHubEndToEndExtension.Postgres.class)
class Postgres extends Tests {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
import com.nimbusds.jose.jwk.Curve;
import org.eclipse.edc.iam.did.spi.document.DidDocument;
import org.eclipse.edc.iam.did.spi.document.Service;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer;
import org.eclipse.edc.identithub.spi.did.DidDocumentService;
import org.eclipse.edc.identityhub.participantcontext.ApiTokenGenerator;
import org.eclipse.edc.identityhub.spi.authentication.ServicePrincipal;
Expand All @@ -27,17 +32,22 @@
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus;
import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource;
import org.eclipse.edc.junit.extensions.EmbeddedRuntime;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.security.Vault;

import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
Expand All @@ -63,6 +73,28 @@ public String createParticipant(String participantId) {
return createParticipant(participantId, List.of());
}

public VerifiableCredential createCredential() {
return VerifiableCredential.Builder.newInstance()
.id(UUID.randomUUID().toString())
.type("test-type")
.issuanceDate(Instant.now())
.issuer(new Issuer("did:web:issuer"))
.credentialSubject(CredentialSubject.Builder.newInstance().id("id").claim("foo", "bar").build())
.build();
}

public String storeCredential(VerifiableCredential credential, String participantId) {
var resource = VerifiableCredentialResource.Builder.newInstance()
.id(UUID.randomUUID().toString())
.state(VcStatus.ISSUED)
.participantId(participantId)
.holderId("holderId")
.issuerId("issuerId")
.credential(new VerifiableCredentialContainer("rawVc", CredentialFormat.JWT, credential))
.build();
runtime.getService(CredentialStore.class).create(resource).orElseThrow(f -> new EdcException(f.getFailureDetail()));
return resource.getId();
}

public String createParticipant(String participantId, List<String> roles) {
var manifest = ParticipantManifest.Builder.newInstance()
Expand Down Expand Up @@ -161,4 +193,11 @@ public ParticipantContext getParticipant(String participantId) {
.orElseThrow(f -> new EdcException(f.getFailureDetail()));
}

public Optional<VerifiableCredentialResource> getCredential(String credentialId) {
return runtime.getService(CredentialStore.class)
.query(QuerySpec.Builder.newInstance().filter(new Criterion("id", "=", credentialId)).build())
.orElseThrow(f -> new EdcException(f.getFailureDetail()))
.stream().findFirst();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"version": "1.0.0-alpha",
"version": "1.1.0-alpha",
"urlPath": "/v1alpha",
"lastUpdated": "2024-08-22T09:20:00Z",
"maturity": null
Expand Down
Loading

0 comments on commit 1408a52

Please sign in to comment.