From 7d6df9ce4d4594a3e4f44b879612ed3788ff1a11 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Mon, 6 May 2024 16:57:22 +0200 Subject: [PATCH 1/9] add status field + timestamp --- .../model/VerifiableCredentialResource.java | 48 +++++++++++++++++++ .../VerifiableCredentialResourceTest.java | 2 + 2 files changed, 50 insertions(+) diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java index fd9bb26e3..e60512f82 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java @@ -19,13 +19,20 @@ import org.eclipse.edc.identityhub.spi.participantcontext.model.IdentityResource; import org.eclipse.edc.policy.model.Policy; +import java.time.Instant; + /** * Represents a Verifiable Credential Resource. * The Verifiable Credential Resource extends the Identity Resource class and adds additional properties specific to verifiable credentials, * specifically the issuance and re-issuance policies as well as a representation of the VC */ public class VerifiableCredentialResource extends IdentityResource { + private static final String EXPIRED = "expired"; + private static final String REVOKED = "revoked"; + private static final String SUSPENDED = "suspended"; private int state; + private String credentialStatus; + private Instant timeOfLastStatusUpdate; private Policy issuancePolicy; private Policy reissuancePolicy; private VerifiableCredentialContainer verifiableCredential; @@ -51,10 +58,51 @@ public Policy getReissuancePolicy() { return reissuancePolicy; } + public boolean isExpired() { + return EXPIRED.equalsIgnoreCase(credentialStatus); + } + + public boolean isRevoked() { + return REVOKED.equalsIgnoreCase(credentialStatus); + } + + public boolean isSuspended() { + return SUSPENDED.equalsIgnoreCase(credentialStatus); + } + + public void suspend() { + setCredentialStatus(SUSPENDED); + } + + public void setExpired() { + setCredentialStatus(EXPIRED); + } + + public void revoke() { + setCredentialStatus(REVOKED); + } + + public void clearStatus() { + setCredentialStatus(null); + } + public VerifiableCredentialContainer getVerifiableCredential() { return verifiableCredential; } + public Instant getTimeOfLastStatusUpdate() { + return timeOfLastStatusUpdate; + } + + public String getCredentialStatus() { + return credentialStatus; + } + + public void setCredentialStatus(String status) { + credentialStatus = status; + timeOfLastStatusUpdate = Instant.now(); + } + public static class Builder extends IdentityResource.Builder { protected Builder(VerifiableCredentialResource resource) { diff --git a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java index 1be96f6b7..f9a41c6e2 100644 --- a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java +++ b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java @@ -47,5 +47,7 @@ void verifyBuilder_assertDefaultValues() { assertThat(vc.getClock()).isNotNull(); assertThat(vc.getId()).isNotNull(); assertThat(vc.getStateAsEnum()).isEqualTo(VcState.INITIAL); + assertThat(vc.getTimeOfLastStatusUpdate()).isNull(); + assertThat(vc.getCredentialStatus()).isNull(); } } \ No newline at end of file From d32281972ff58485cadd60559745a10523f1719f Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Tue, 7 May 2024 07:29:00 +0200 Subject: [PATCH 2/9] wip --- .../query/CredentialQueryResolverImpl.java | 6 +- .../sql/credentials/SqlCredentialStore.java | 4 +- .../store/test/CredentialStoreTestBase.java | 84 +++++++++++++++++-- .../{VcState.java => VcIssuanceState.java} | 9 +- .../verifiablecredentials/model/VcStatus.java | 44 ++++++++++ .../model/VerifiableCredentialResource.java | 39 +++++---- .../VerifiableCredentialResourceTest.java | 2 +- 7 files changed, 158 insertions(+), 30 deletions(-) rename spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/{VcState.java => VcIssuanceState.java} (75%) create mode 100644 spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java diff --git a/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java b/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java index 82824d9f8..8fa562fb0 100644 --- a/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java +++ b/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java @@ -28,6 +28,7 @@ import org.eclipse.edc.spi.result.Result; import java.time.Instant; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -94,7 +95,7 @@ public QueryResult query(String participantContextId, PresentationQueryMessage q // filter out any expired, revoked or suspended credentials return isValidQuery ? QueryResult.success(requestedCredentials.stream() - .filter(this::filterInvalidCredentials) + .filter(this::filterInvalidCredentials) // we still have to filter invalid creds, b/c a revocation may not have been detected yet .map(VerifiableCredentialResource::getVerifiableCredential)) : QueryResult.unauthorized("Invalid query: requested Credentials outside of scope."); } @@ -154,8 +155,9 @@ private Result> queryCredentials(List Assertions.assertThat(result).hasSize(2) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(expectedCred, secondCred)); + } + @Test void query_whenNotFound() { var resources = range(0, 5) @@ -400,7 +468,7 @@ void update() { var result = getStore().create(credential.build()); assertThat(result).isSucceeded(); - var updateRes = getStore().update(credential.state(VcState.ISSUED).build()); + var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).build()); assertThat(updateRes).isSucceeded(); } @@ -409,14 +477,14 @@ void update_whenIdChanges_fails() { var credential = createCredentialBuilder(); var result = getStore().create(credential.build()); - var updateRes = getStore().update(credential.state(VcState.ISSUED).id("another-id").build()); + var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).id("another-id").build()); assertThat(updateRes).isFailed().detail().contains("with ID 'another-id' does not exist."); } @Test void update_whenNotExists() { var credential = createCredentialBuilder(); - var updateRes = getStore().update(credential.state(VcState.ISSUED).id("another-id").build()); + var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).id("another-id").build()); assertThat(updateRes).isFailed().detail().contains("with ID 'another-id' does not exist."); } @@ -447,10 +515,10 @@ protected VerifiableCredentialResource.Builder createCredentialBuilder() { return VerifiableCredentialResource.Builder.newInstance() .issuerId("test-issuer") .holderId("test-holder") - .state(VcState.ISSUED) + .state(VcIssuanceState.ISSUED) .participantId(TEST_PARTICIPANT_CONTEXT_ID) .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JSON_LD, createVerifiableCredential().build())) - .id("test-id"); + .id(UUID.randomUUID().toString()); } @NotNull diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcState.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java similarity index 75% rename from spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcState.java rename to spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java index cdb68be85..e446df9a8 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcState.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java @@ -16,7 +16,10 @@ import java.util.Arrays; -public enum VcState { +/** + * Indicates the state of issuance of a credential. This is orthogonal to the status of a credential, which could be "valid", "expired, "suspended", "revoked", etc. + */ +public enum VcIssuanceState { INITIAL(100), REQUESTING(200), REQUESTED(300), @@ -29,11 +32,11 @@ public enum VcState { private final int code; - VcState(int code) { + VcIssuanceState(int code) { this.code = code; } - public static VcState from(int code) { + public static VcIssuanceState from(int code) { return Arrays.stream(values()).filter(tps -> tps.code == code).findFirst().orElse(null); } diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java new file mode 100644 index 000000000..65344f6ff --- /dev/null +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.spi.verifiablecredentials.model; + +import java.util.List; + +public enum VcStatus { + VALID(0), + EXPIRED(100), + NOT_YET_VALID(200), + SUSPENDED(300), + REVOKED(400), + OTHER(500) + + public static final List KNOWN_STATUS_VALUES = List.of( + VALID, EXPIRED, NOT_YET_VALID, SUSPENDED, REVOKED, OTHER + ); + private final int code; + + VcStatus(int code) { + this.code = code; + } + + public static VcStatus from(int code) { + return KNOWN_STATUS_VALUES.stream().filter(tps -> tps.code() == code).findFirst().orElse(new VcStatus(code, null)); + } + + + public int code() { + return code; + } +} diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java index e60512f82..f3ae08224 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java @@ -18,20 +18,22 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.participantcontext.model.IdentityResource; import org.eclipse.edc.policy.model.Policy; +import org.jetbrains.annotations.NotNull; import java.time.Instant; +import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.EXPIRED; +import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.REVOKED; +import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.SUSPENDED; + /** * Represents a Verifiable Credential Resource. * The Verifiable Credential Resource extends the Identity Resource class and adds additional properties specific to verifiable credentials, * specifically the issuance and re-issuance policies as well as a representation of the VC */ public class VerifiableCredentialResource extends IdentityResource { - private static final String EXPIRED = "expired"; - private static final String REVOKED = "revoked"; - private static final String SUSPENDED = "suspended"; private int state; - private String credentialStatus; + private int credentialStatus = VcStatus.VALID.code(); private Instant timeOfLastStatusUpdate; private Policy issuancePolicy; private Policy reissuancePolicy; @@ -46,8 +48,8 @@ public int getState() { } @JsonIgnore - public VcState getStateAsEnum() { - return VcState.from(state); + public VcIssuanceState getStateAsEnum() { + return VcIssuanceState.from(state); } public Policy getIssuancePolicy() { @@ -59,15 +61,15 @@ public Policy getReissuancePolicy() { } public boolean isExpired() { - return EXPIRED.equalsIgnoreCase(credentialStatus); + return EXPIRED.code() == credentialStatus; } public boolean isRevoked() { - return REVOKED.equalsIgnoreCase(credentialStatus); + return REVOKED.code() == credentialStatus; } public boolean isSuspended() { - return SUSPENDED.equalsIgnoreCase(credentialStatus); + return SUSPENDED.code() == credentialStatus; } public void suspend() { @@ -94,15 +96,19 @@ public Instant getTimeOfLastStatusUpdate() { return timeOfLastStatusUpdate; } - public String getCredentialStatus() { + public int getCredentialStatus() { return credentialStatus; } - public void setCredentialStatus(String status) { - credentialStatus = status; + public void setCredentialStatus(VcStatus status) { + credentialStatus = status.code(); timeOfLastStatusUpdate = Instant.now(); } + public VcStatus getCredentialStatusEnum() { + return VcStatus.from(credentialStatus); + } + public static class Builder extends IdentityResource.Builder { protected Builder(VerifiableCredentialResource resource) { @@ -113,7 +119,7 @@ public static Builder newInstance() { return new Builder(new VerifiableCredentialResource()); } - public Builder state(VcState state) { + public Builder state(VcIssuanceState state) { entity.state = state.code(); return self(); } @@ -133,6 +139,11 @@ public Builder credential(VerifiableCredentialContainer credential) { return self(); } + public Builder credentialStatus(@NotNull VcStatus status) { + entity.credentialStatus = status.code(); + return this; + } + @Override public Builder self() { return this; @@ -141,7 +152,7 @@ public Builder self() { @Override public VerifiableCredentialResource build() { if (entity.state == 0) { - entity.state = VcState.INITIAL.code(); + entity.state = VcIssuanceState.INITIAL.code(); } return super.build(); } diff --git a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java index f9a41c6e2..5ff2e99f6 100644 --- a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java +++ b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java @@ -46,7 +46,7 @@ void verifyBuilder_assertDefaultValues() { assertThat(vc.getClock()).isNotNull(); assertThat(vc.getId()).isNotNull(); - assertThat(vc.getStateAsEnum()).isEqualTo(VcState.INITIAL); + assertThat(vc.getStateAsEnum()).isEqualTo(VcIssuanceState.INITIAL); assertThat(vc.getTimeOfLastStatusUpdate()).isNull(); assertThat(vc.getCredentialStatus()).isNull(); } From 447571af9187f0aa31e3eebc1f0338499e39c437 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Tue, 7 May 2024 08:19:40 +0200 Subject: [PATCH 3/9] add status flag to SQL stack --- e2e-tests/api-tests/build.gradle.kts | 1 + .../sql/identity-hub-credentials-store-sql/docs/schema.sql | 4 +++- .../store/sql/credentials/BaseSqlDialectStatements.java | 2 ++ .../store/sql/credentials/CredentialStoreStatements.java | 4 ++++ .../identityhub/store/sql/credentials/SqlCredentialStore.java | 4 ++++ .../schema/postgres/VerifiableCredentialResourceMapping.java | 2 ++ .../identityhub/spi/verifiablecredentials/model/VcStatus.java | 4 ++-- 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/e2e-tests/api-tests/build.gradle.kts b/e2e-tests/api-tests/build.gradle.kts index 0ed2d2fb9..691b573ed 100644 --- a/e2e-tests/api-tests/build.gradle.kts +++ b/e2e-tests/api-tests/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { testImplementation(libs.awaitility) testImplementation(libs.testcontainers.junit) // needed for the Participant + testImplementation(project(":core:lib:credential-query-lib")) testImplementation(testFixtures(project(":spi:verifiable-credential-spi"))) testImplementation(testFixtures(libs.edc.testfixtures.managementapi)) testImplementation(libs.nimbus.jwt) diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql b/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql index 4e2c182ed..442b4eb18 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql +++ b/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql @@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS credential_resource issuer_id VARCHAR NOT NULL, holder_id VARCHAR NOT NULL, vc_state INTEGER NOT NULL, + credential_status INTEGER DEFAULT 0 NOT NULL, -- revocation/expiry status of the credential issuance_policy JSON, reissuance_policy JSON, raw_vc VARCHAR NOT NULL, -- Representation of the VC exactly as it was received by the issuer. Can be JWT or JSON(-LD) @@ -31,5 +32,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS credential_resource_credential_id_uindex ON cr COMMENT ON COLUMN credential_resource.id IS 'ID of the VC, duplicated here for indexing purposes'; COMMENT ON COLUMN credential_resource.raw_vc IS 'Representation of the VC exactly as it was received by the issuer. Can be JWT or JSON(-LD) '; COMMENT ON COLUMN credential_resource.vc_format IS '0 = JSON-LD, 1 = JWT'; -COMMENT ON COLUMN credential_resource.verifiable_credential is 'JSON-representation of the VerifiableCredential'; +COMMENT ON COLUMN credential_resource.verifiable_credential IS 'JSON-representation of the VerifiableCredential'; +COMMENT ON COLUMN credential_resource.credential_status IS 'Revocation/expiry status of the credential, 0 = VALID' diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java index 1172fcd01..f869902f7 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java @@ -30,6 +30,7 @@ public String getInsertTemplate() { .column(getIssuerIdColumn()) .column(getHolderIdColumn()) .column(getVcStateColumn()) + .column(getCredentialStatusColumn()) .jsonColumn(getIssuancePolicyColumn()) .jsonColumn(getReissuancePolicyColumn()) .column(getVcFormatColumn()) @@ -47,6 +48,7 @@ public String getUpdateTemplate() { .column(getIssuerIdColumn()) .column(getHolderIdColumn()) .column(getVcStateColumn()) + .column(getCredentialStatusColumn()) .jsonColumn(getIssuancePolicyColumn()) .jsonColumn(getReissuancePolicyColumn()) .column(getVcFormatColumn()) diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java index 15e233efe..35c4ef8a6 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java @@ -70,6 +70,10 @@ default String getParticipantIdColumn() { return "participant_id"; } + default String getCredentialStatusColumn() { + return "credential_status"; + } + String getInsertTemplate(); String getUpdateTemplate(); diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java index ff493948b..84bf4c09e 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java @@ -20,6 +20,7 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.store.CredentialStore; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcIssuanceState; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.persistence.EdcPersistenceException; @@ -68,6 +69,7 @@ public StoreResult create(VerifiableCredentialResource credentialResource) credentialResource.getIssuerId(), credentialResource.getHolderId(), credentialResource.getState(), + credentialResource.getCredentialStatus(), toJson(credentialResource.getIssuancePolicy()), toJson(credentialResource.getReissuancePolicy()), credentialResource.getVerifiableCredential().format().ordinal(), @@ -109,6 +111,7 @@ public StoreResult update(VerifiableCredentialResource credentialResource) credentialResource.getIssuerId(), credentialResource.getHolderId(), credentialResource.getState(), + credentialResource.getCredentialStatus(), toJson(credentialResource.getIssuancePolicy()), toJson(credentialResource.getReissuancePolicy()), credentialResource.getVerifiableCredential().format().ordinal(), @@ -163,6 +166,7 @@ private VerifiableCredentialResource mapResultSet(ResultSet resultSet) throws Ex .issuerId(resultSet.getString(statements.getIssuerIdColumn())) .holderId(resultSet.getString(statements.getHolderIdColumn())) .state(VcIssuanceState.from(resultSet.getInt(statements.getVcStateColumn()))) + .credentialStatus(VcStatus.from(resultSet.getInt(statements.getCredentialStatusColumn()))) .issuancePolicy(fromJson(resultSet.getString(statements.getIssuancePolicyColumn()), Policy.class)) .reissuancePolicy(fromJson(resultSet.getString(statements.getReissuancePolicyColumn()), Policy.class)) .credential(vcc) diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java index 143477b74..1f7a5b325 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java @@ -29,6 +29,7 @@ public class VerifiableCredentialResourceMapping extends TranslationMapping { public static final String FIELD_ISSUER_ID = "issuerId"; public static final String FIELD_HOLDER_ID = "holderId"; public static final String FIELD_STATE = "state"; + public static final String FIELD_CRED_STATUS = "credentialStatus"; public static final String FIELD_ISSUANCE_POLICY = "issuancePolicy"; public static final String FIELD_REISSUANCE_POLICY = "reissuancePolicy"; public static final String FIELD_VERIFIABLE_CREDENTIAL = "verifiableCredential"; @@ -40,6 +41,7 @@ public VerifiableCredentialResourceMapping(CredentialStoreStatements statements) add(FIELD_ISSUER_ID, statements.getIssuerIdColumn()); add(FIELD_HOLDER_ID, statements.getHolderIdColumn()); add(FIELD_STATE, statements.getVcStateColumn()); + add(FIELD_CRED_STATUS, statements.getCredentialStatusColumn()); add(FIELD_ISSUANCE_POLICY, statements.getIssuancePolicyColumn()); add(FIELD_REISSUANCE_POLICY, statements.getReissuancePolicyColumn()); add(FIELD_VERIFIABLE_CREDENTIAL, new VerifiableCredentialContainerMapping(statements)); diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java index 65344f6ff..0ae80b20e 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java @@ -22,7 +22,7 @@ public enum VcStatus { NOT_YET_VALID(200), SUSPENDED(300), REVOKED(400), - OTHER(500) + OTHER(500); public static final List KNOWN_STATUS_VALUES = List.of( VALID, EXPIRED, NOT_YET_VALID, SUSPENDED, REVOKED, OTHER @@ -34,7 +34,7 @@ public enum VcStatus { } public static VcStatus from(int code) { - return KNOWN_STATUS_VALUES.stream().filter(tps -> tps.code() == code).findFirst().orElse(new VcStatus(code, null)); + return KNOWN_STATUS_VALUES.stream().filter(tps -> tps.code() == code).findFirst().orElse(OTHER); } From 3e52b7f13999cb5f625bb61cd545757eb3ebc9a3 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Tue, 7 May 2024 12:03:20 +0200 Subject: [PATCH 4/9] collapse credential status --- .../tests/PresentationApiComponentTest.java | 43 +++++++---------- .../IdentityHubRuntimeConfiguration.java | 3 +- .../identityhub/tests/fixtures/TestData.java | 4 -- .../src/test/resources/private-key.pem | 5 ++ .../src/test/resources/public-key.pem | 4 ++ .../docs/schema.sql | 2 - .../credentials/BaseSqlDialectStatements.java | 2 - .../CredentialStoreStatements.java | 4 -- .../sql/credentials/SqlCredentialStore.java | 6 +-- .../VerifiableCredentialResourceMapping.java | 2 - .../store/test/CredentialStoreTestBase.java | 32 ++++++------- .../build.gradle.kts | 1 + .../model/VcIssuanceState.java | 46 ------------------- .../verifiablecredentials/model/VcStatus.java | 27 ++++++----- .../model/VerifiableCredentialResource.java | 32 ++++--------- .../VerifiableCredentialResourceTest.java | 3 +- .../VerifiableCredentialTestUtil.java | 6 ++- 17 files changed, 73 insertions(+), 149 deletions(-) create mode 100644 e2e-tests/api-tests/src/test/resources/private-key.pem create mode 100644 e2e-tests/api-tests/src/test/resources/public-key.pem delete mode 100644 spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java index 52097450c..21925dd37 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java @@ -23,10 +23,6 @@ import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest; -import org.eclipse.edc.identityhub.spi.verifiablecredentials.generator.VerifiablePresentationService; -import org.eclipse.edc.identityhub.spi.verifiablecredentials.resolution.CredentialQueryResolver; -import org.eclipse.edc.identityhub.spi.verifiablecredentials.resolution.QueryResult; -import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubRuntimeConfiguration; import org.eclipse.edc.identityhub.tests.fixtures.TestData; import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; @@ -35,23 +31,16 @@ import org.eclipse.edc.spi.result.Result; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.ArgumentMatchers; import java.util.Base64; import java.util.List; import java.util.Map; -import java.util.stream.Stream; import static io.restassured.http.ContentType.JSON; import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil.generateSiToken; -import static org.eclipse.edc.spi.result.Result.failure; -import static org.eclipse.edc.spi.result.Result.success; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -71,16 +60,16 @@ public class PresentationApiComponentTest { ], "@type": "PresentationQueryMessage", "scope":[ - "test-scope1" + "org.eclipse.edc.vc.type:TestScope1:read" ] } """; private static final String TEST_PARTICIPANT_CONTEXT_ID = "test-participant"; private static final String TEST_PARTICIPANT_CONTEXT_ID_ENCODED = Base64.getUrlEncoder().encodeToString(TEST_PARTICIPANT_CONTEXT_ID.getBytes()); // todo: these mocks should be replaced, once their respective implementations exist! - private static final CredentialQueryResolver CREDENTIAL_QUERY_RESOLVER = mock(); - private static final VerifiablePresentationService PRESENTATION_GENERATOR = mock(); - private static final AccessTokenVerifier ACCESS_TOKEN_VERIFIER = mock(); +// private static final CredentialQueryResolver CREDENTIAL_QUERY_RESOLVER = mock(); +// private static final VerifiablePresentationService PRESENTATION_GENERATOR = mock(); + // private static final AccessTokenVerifier ACCESS_TOKEN_VERIFIER = mock(); private static final DidPublicKeyResolver DID_PUBLIC_KEY_RESOLVER = mock(); @RegisterExtension @@ -88,9 +77,9 @@ public class PresentationApiComponentTest { static { runtime = new EdcRuntimeExtension(":launcher", "identity-hub", IDENTITY_HUB_PARTICIPANT.controlPlaneConfiguration()); - runtime.registerServiceMock(CredentialQueryResolver.class, CREDENTIAL_QUERY_RESOLVER); - runtime.registerServiceMock(VerifiablePresentationService.class, PRESENTATION_GENERATOR); - runtime.registerServiceMock(AccessTokenVerifier.class, ACCESS_TOKEN_VERIFIER); +// runtime.registerServiceMock(CredentialQueryResolver.class, CREDENTIAL_QUERY_RESOLVER); +// runtime.registerServiceMock(VerifiablePresentationService.class, PRESENTATION_GENERATOR); +// runtime.registerServiceMock(AccessTokenVerifier.class, ACCESS_TOKEN_VERIFIER); runtime.registerServiceMock(DidPublicKeyResolver.class, DID_PUBLIC_KEY_RESOLVER); } @@ -158,7 +147,7 @@ void query_withPresentationDefinition_shouldReturn503() { void query_tokenVerificationFails_shouldReturn401() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); - when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(failure("token not verified")); +// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(failure("token not verified")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) .header(AUTHORIZATION, token) @@ -175,8 +164,8 @@ void query_tokenVerificationFails_shouldReturn401() { void query_queryResolutionFails_shouldReturn403() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); - when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); - when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.unauthorized("scope mismatch!")); +// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); +// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.unauthorized("scope mismatch!")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) @@ -194,9 +183,9 @@ void query_queryResolutionFails_shouldReturn403() { void query_presentationGenerationFails_shouldReturn500() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); - when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); - when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); - when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(failure("generator test error")); +// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); +// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); +// when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(failure("generator test error")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) @@ -212,9 +201,9 @@ void query_presentationGenerationFails_shouldReturn500() { void query_success() throws JOSEException { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); - when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); - when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); - when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(success(createPresentationResponse())); +// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("org.eclipse.edc.vc.type:TestScope1:read"))); +// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); +// when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(success(createPresentationResponse())); when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java index dca816d2c..6d824ab87 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java @@ -15,7 +15,6 @@ package org.eclipse.edc.identityhub.tests.fixtures; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.junit.testfixtures.TestUtils; import java.net.URI; import java.util.HashMap; @@ -50,7 +49,7 @@ public Map controlPlaneConfiguration() { put("web.http.management.port", String.valueOf(managementEndpoint.getUrl().getPort())); put("web.http.management.path", managementEndpoint.getUrl().getPath()); put("edc.connector.name", name); - put("edc.ih.iam.publickey.path", TestUtils.getFileFromResourceName("testkey.pem").getAbsolutePath()); + put("edc.ih.iam.publickey.alias", "public-key-alias"); put("edc.ih.iam.id", "did:web:consumer"); } }; diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java index 1a818f6b6..f54342774 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java @@ -18,12 +18,10 @@ public interface TestData { // taken from https://www.w3.org/TR/vc-data-model/#example-a-simple-example-of-a-verifiable-credential String VP_EXAMPLE = """ { - "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], - "id": "http://example.edu/credentials/1872", "type": ["VerifiableCredential", "AlumniCredential"], "issuer": "https://example.edu/issuers/565049", @@ -41,8 +39,6 @@ public interface TestData { }] } }, - - "proof": { "type": "RsaSignature2018", "created": "2017-06-18T21:19:10Z", diff --git a/e2e-tests/api-tests/src/test/resources/private-key.pem b/e2e-tests/api-tests/src/test/resources/private-key.pem new file mode 100644 index 000000000..18683140f --- /dev/null +++ b/e2e-tests/api-tests/src/test/resources/private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMH5nIIP0d/AI1D1L6DpIscPL3EX5x5XHxrBnt2+TduzoAoGCCqGSM49 +AwEHoUQDQgAET6VaLBE16dj2WcqcCaF6M1ORlaQhrT5bEhY8JUiDwRCFxVvg+cSY +fZMxhs+T5y3AfHejXkk0g6Ehg8HNHeJh/g== +-----END EC PRIVATE KEY----- diff --git a/e2e-tests/api-tests/src/test/resources/public-key.pem b/e2e-tests/api-tests/src/test/resources/public-key.pem new file mode 100644 index 000000000..8085f6095 --- /dev/null +++ b/e2e-tests/api-tests/src/test/resources/public-key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET6VaLBE16dj2WcqcCaF6M1ORlaQh +rT5bEhY8JUiDwRCFxVvg+cSYfZMxhs+T5y3AfHejXkk0g6Ehg8HNHeJh/g== +-----END PUBLIC KEY----- diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql b/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql index 442b4eb18..140d8f923 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql +++ b/extensions/store/sql/identity-hub-credentials-store-sql/docs/schema.sql @@ -20,7 +20,6 @@ CREATE TABLE IF NOT EXISTS credential_resource issuer_id VARCHAR NOT NULL, holder_id VARCHAR NOT NULL, vc_state INTEGER NOT NULL, - credential_status INTEGER DEFAULT 0 NOT NULL, -- revocation/expiry status of the credential issuance_policy JSON, reissuance_policy JSON, raw_vc VARCHAR NOT NULL, -- Representation of the VC exactly as it was received by the issuer. Can be JWT or JSON(-LD) @@ -33,5 +32,4 @@ COMMENT ON COLUMN credential_resource.id IS 'ID of the VC, duplicated here for i COMMENT ON COLUMN credential_resource.raw_vc IS 'Representation of the VC exactly as it was received by the issuer. Can be JWT or JSON(-LD) '; COMMENT ON COLUMN credential_resource.vc_format IS '0 = JSON-LD, 1 = JWT'; COMMENT ON COLUMN credential_resource.verifiable_credential IS 'JSON-representation of the VerifiableCredential'; -COMMENT ON COLUMN credential_resource.credential_status IS 'Revocation/expiry status of the credential, 0 = VALID' diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java index f869902f7..1172fcd01 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/BaseSqlDialectStatements.java @@ -30,7 +30,6 @@ public String getInsertTemplate() { .column(getIssuerIdColumn()) .column(getHolderIdColumn()) .column(getVcStateColumn()) - .column(getCredentialStatusColumn()) .jsonColumn(getIssuancePolicyColumn()) .jsonColumn(getReissuancePolicyColumn()) .column(getVcFormatColumn()) @@ -48,7 +47,6 @@ public String getUpdateTemplate() { .column(getIssuerIdColumn()) .column(getHolderIdColumn()) .column(getVcStateColumn()) - .column(getCredentialStatusColumn()) .jsonColumn(getIssuancePolicyColumn()) .jsonColumn(getReissuancePolicyColumn()) .column(getVcFormatColumn()) diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java index 35c4ef8a6..15e233efe 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/CredentialStoreStatements.java @@ -70,10 +70,6 @@ default String getParticipantIdColumn() { return "participant_id"; } - default String getCredentialStatusColumn() { - return "credential_status"; - } - String getInsertTemplate(); String getUpdateTemplate(); diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java index 84bf4c09e..f3b900ac1 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java @@ -19,7 +19,6 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcIssuanceState; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.policy.model.Policy; @@ -69,7 +68,6 @@ public StoreResult create(VerifiableCredentialResource credentialResource) credentialResource.getIssuerId(), credentialResource.getHolderId(), credentialResource.getState(), - credentialResource.getCredentialStatus(), toJson(credentialResource.getIssuancePolicy()), toJson(credentialResource.getReissuancePolicy()), credentialResource.getVerifiableCredential().format().ordinal(), @@ -111,7 +109,6 @@ public StoreResult update(VerifiableCredentialResource credentialResource) credentialResource.getIssuerId(), credentialResource.getHolderId(), credentialResource.getState(), - credentialResource.getCredentialStatus(), toJson(credentialResource.getIssuancePolicy()), toJson(credentialResource.getReissuancePolicy()), credentialResource.getVerifiableCredential().format().ordinal(), @@ -165,8 +162,7 @@ private VerifiableCredentialResource mapResultSet(ResultSet resultSet) throws Ex .timestamp(resultSet.getLong(statements.getCreateTimestampColumn())) .issuerId(resultSet.getString(statements.getIssuerIdColumn())) .holderId(resultSet.getString(statements.getHolderIdColumn())) - .state(VcIssuanceState.from(resultSet.getInt(statements.getVcStateColumn()))) - .credentialStatus(VcStatus.from(resultSet.getInt(statements.getCredentialStatusColumn()))) + .state(VcStatus.from(resultSet.getInt(statements.getVcStateColumn()))) .issuancePolicy(fromJson(resultSet.getString(statements.getIssuancePolicyColumn()), Policy.class)) .reissuancePolicy(fromJson(resultSet.getString(statements.getReissuancePolicyColumn()), Policy.class)) .credential(vcc) diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java index 1f7a5b325..143477b74 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java @@ -29,7 +29,6 @@ public class VerifiableCredentialResourceMapping extends TranslationMapping { public static final String FIELD_ISSUER_ID = "issuerId"; public static final String FIELD_HOLDER_ID = "holderId"; public static final String FIELD_STATE = "state"; - public static final String FIELD_CRED_STATUS = "credentialStatus"; public static final String FIELD_ISSUANCE_POLICY = "issuancePolicy"; public static final String FIELD_REISSUANCE_POLICY = "reissuancePolicy"; public static final String FIELD_VERIFIABLE_CREDENTIAL = "verifiableCredential"; @@ -41,7 +40,6 @@ public VerifiableCredentialResourceMapping(CredentialStoreStatements statements) add(FIELD_ISSUER_ID, statements.getIssuerIdColumn()); add(FIELD_HOLDER_ID, statements.getHolderIdColumn()); add(FIELD_STATE, statements.getVcStateColumn()); - add(FIELD_CRED_STATUS, statements.getCredentialStatusColumn()); add(FIELD_ISSUANCE_POLICY, statements.getIssuancePolicyColumn()); add(FIELD_REISSUANCE_POLICY, statements.getReissuancePolicyColumn()); add(FIELD_VERIFIABLE_CREDENTIAL, new VerifiableCredentialContainerMapping(statements)); diff --git a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java index 87955d0e2..656169633 100644 --- a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java +++ b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java @@ -22,7 +22,6 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcIssuanceState; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.spi.query.Criterion; @@ -38,7 +37,8 @@ import java.util.UUID; import static java.util.stream.IntStream.range; -import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.OTHER; +import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.INITIAL; +import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.REQUESTING; import static org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus.REVOKED; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; @@ -172,12 +172,12 @@ void query_byParticipantIdAndType() { @Test void query_byVcState() { var creds = createCredentials(); - var expectedCred = createCredentialBuilder().state(VcIssuanceState.REISSUE_REQUESTED).id("id-test").build(); + var expectedCred = createCredentialBuilder().state(VcStatus.REQUESTED).id("id-test").build(); creds.add(expectedCred); creds.forEach(getStore()::create); var query = QuerySpec.Builder.newInstance() - .filter(new Criterion("state", "=", VcIssuanceState.REISSUE_REQUESTED.code())) + .filter(new Criterion("state", "=", VcStatus.REQUESTED.code())) .build(); assertThat(getStore().query(query)).isSucceeded() @@ -369,15 +369,15 @@ void query_byStatus() { var expectedCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JSON_LD, createVerifiableCredential().build())) - .credentialStatus(REVOKED) + .state(REVOKED) .build(); var secondCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JWT, createVerifiableCredential().build())) - .credentialStatus(OTHER) + .state(REQUESTING) .build(); var thirdCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JWT, createVerifiableCredential().build())) - .credentialStatus(REVOKED) + .state(REVOKED) .build(); creds.add(expectedCred); creds.add(secondCred); @@ -385,7 +385,7 @@ void query_byStatus() { creds.forEach(getStore()::create); var query = QuerySpec.Builder.newInstance() - .filter(new Criterion("credentialStatus", "=", REVOKED.code())) + .filter(new Criterion("state", "=", REVOKED.code())) .build(); assertThat(getStore().query(query)).isSucceeded() @@ -401,15 +401,15 @@ void query_byStatusMultiple() { var expectedCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JSON_LD, createVerifiableCredential().build())) - .credentialStatus(REVOKED) + .state(REVOKED) .build(); var secondCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JWT, createVerifiableCredential().build())) - .credentialStatus(OTHER) + .state(INITIAL) .build(); var thirdCred = createCredentialBuilder() .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JWT, createVerifiableCredential().build())) - .credentialStatus(VcStatus.NOT_YET_VALID) + .state(VcStatus.NOT_YET_VALID) .build(); creds.add(expectedCred); creds.add(secondCred); @@ -417,7 +417,7 @@ void query_byStatusMultiple() { creds.forEach(getStore()::create); var query = QuerySpec.Builder.newInstance() - .filter(new Criterion("credentialStatus", "in", List.of(OTHER.code(), REVOKED.code()))) + .filter(new Criterion("state", "in", List.of(INITIAL.code(), REVOKED.code()))) .build(); assertThat(getStore().query(query)).isSucceeded() @@ -468,7 +468,7 @@ void update() { var result = getStore().create(credential.build()); assertThat(result).isSucceeded(); - var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).build()); + var updateRes = getStore().update(credential.state(VcStatus.ISSUED).build()); assertThat(updateRes).isSucceeded(); } @@ -477,14 +477,14 @@ void update_whenIdChanges_fails() { var credential = createCredentialBuilder(); var result = getStore().create(credential.build()); - var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).id("another-id").build()); + var updateRes = getStore().update(credential.state(VcStatus.ISSUED).id("another-id").build()); assertThat(updateRes).isFailed().detail().contains("with ID 'another-id' does not exist."); } @Test void update_whenNotExists() { var credential = createCredentialBuilder(); - var updateRes = getStore().update(credential.state(VcIssuanceState.ISSUED).id("another-id").build()); + var updateRes = getStore().update(credential.state(VcStatus.ISSUED).id("another-id").build()); assertThat(updateRes).isFailed().detail().contains("with ID 'another-id' does not exist."); } @@ -515,7 +515,7 @@ protected VerifiableCredentialResource.Builder createCredentialBuilder() { return VerifiableCredentialResource.Builder.newInstance() .issuerId("test-issuer") .holderId("test-holder") - .state(VcIssuanceState.ISSUED) + .state(VcStatus.ISSUED) .participantId(TEST_PARTICIPANT_CONTEXT_ID) .credential(new VerifiableCredentialContainer(EXAMPLE_VC, CredentialFormat.JSON_LD, createVerifiableCredential().build())) .id(UUID.randomUUID().toString()); diff --git a/spi/verifiable-credential-spi/build.gradle.kts b/spi/verifiable-credential-spi/build.gradle.kts index f442ac4b0..d462244f3 100644 --- a/spi/verifiable-credential-spi/build.gradle.kts +++ b/spi/verifiable-credential-spi/build.gradle.kts @@ -28,4 +28,5 @@ dependencies { testImplementation(libs.edc.lib.json) testFixturesImplementation(libs.nimbus.jwt) testFixturesImplementation(libs.edc.spi.identity.did) + testFixturesImplementation(libs.edc.common.crypto) } diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java deleted file mode 100644 index e446df9a8..000000000 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcIssuanceState.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.identityhub.spi.verifiablecredentials.model; - -import java.util.Arrays; - -/** - * Indicates the state of issuance of a credential. This is orthogonal to the status of a credential, which could be "valid", "expired, "suspended", "revoked", etc. - */ -public enum VcIssuanceState { - INITIAL(100), - REQUESTING(200), - REQUESTED(300), - ISSUING(400), - ISSUED(500), - REISSUE_REQUESTING(600), - REISSUE_REQUESTED(700), - TERMINATED(800), - ERROR(900); - - private final int code; - - VcIssuanceState(int code) { - this.code = code; - } - - public static VcIssuanceState from(int code) { - return Arrays.stream(values()).filter(tps -> tps.code == code).findFirst().orElse(null); - } - - public int code() { - return code; - } -} diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java index 0ae80b20e..7cb9615c0 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VcStatus.java @@ -14,19 +14,23 @@ package org.eclipse.edc.identityhub.spi.verifiablecredentials.model; -import java.util.List; +import java.util.Arrays; +/** + * Indicates the state of a credential. This tracks the credential from when the IdentityHub requests it to when it expires. + */ public enum VcStatus { - VALID(0), - EXPIRED(100), - NOT_YET_VALID(200), - SUSPENDED(300), - REVOKED(400), - OTHER(500); + INITIAL(100), + REQUESTING(200), + REQUESTED(300), + ISSUING(400), + ISSUED(500), + REVOKED(600), + SUSPENDED(700), + EXPIRED(800), + NOT_YET_VALID(900), + ERROR(-100); - public static final List KNOWN_STATUS_VALUES = List.of( - VALID, EXPIRED, NOT_YET_VALID, SUSPENDED, REVOKED, OTHER - ); private final int code; VcStatus(int code) { @@ -34,10 +38,9 @@ public enum VcStatus { } public static VcStatus from(int code) { - return KNOWN_STATUS_VALUES.stream().filter(tps -> tps.code() == code).findFirst().orElse(OTHER); + return Arrays.stream(values()).filter(tps -> tps.code == code).findFirst().orElse(null); } - public int code() { return code; } diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java index f3ae08224..0e4ef207b 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java @@ -18,7 +18,6 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.participantcontext.model.IdentityResource; import org.eclipse.edc.policy.model.Policy; -import org.jetbrains.annotations.NotNull; import java.time.Instant; @@ -33,7 +32,6 @@ */ public class VerifiableCredentialResource extends IdentityResource { private int state; - private int credentialStatus = VcStatus.VALID.code(); private Instant timeOfLastStatusUpdate; private Policy issuancePolicy; private Policy reissuancePolicy; @@ -48,8 +46,8 @@ public int getState() { } @JsonIgnore - public VcIssuanceState getStateAsEnum() { - return VcIssuanceState.from(state); + public VcStatus getStateAsEnum() { + return VcStatus.from(state); } public Policy getIssuancePolicy() { @@ -61,15 +59,15 @@ public Policy getReissuancePolicy() { } public boolean isExpired() { - return EXPIRED.code() == credentialStatus; + return EXPIRED.code() == state; } public boolean isRevoked() { - return REVOKED.code() == credentialStatus; + return REVOKED.code() == state; } public boolean isSuspended() { - return SUSPENDED.code() == credentialStatus; + return SUSPENDED.code() == state; } public void suspend() { @@ -84,10 +82,6 @@ public void revoke() { setCredentialStatus(REVOKED); } - public void clearStatus() { - setCredentialStatus(null); - } - public VerifiableCredentialContainer getVerifiableCredential() { return verifiableCredential; } @@ -96,17 +90,14 @@ public Instant getTimeOfLastStatusUpdate() { return timeOfLastStatusUpdate; } - public int getCredentialStatus() { - return credentialStatus; - } public void setCredentialStatus(VcStatus status) { - credentialStatus = status.code(); + state = status.code(); timeOfLastStatusUpdate = Instant.now(); } public VcStatus getCredentialStatusEnum() { - return VcStatus.from(credentialStatus); + return VcStatus.from(state); } public static class Builder extends IdentityResource.Builder { @@ -119,7 +110,7 @@ public static Builder newInstance() { return new Builder(new VerifiableCredentialResource()); } - public Builder state(VcIssuanceState state) { + public Builder state(VcStatus state) { entity.state = state.code(); return self(); } @@ -139,11 +130,6 @@ public Builder credential(VerifiableCredentialContainer credential) { return self(); } - public Builder credentialStatus(@NotNull VcStatus status) { - entity.credentialStatus = status.code(); - return this; - } - @Override public Builder self() { return this; @@ -152,7 +138,7 @@ public Builder self() { @Override public VerifiableCredentialResource build() { if (entity.state == 0) { - entity.state = VcIssuanceState.INITIAL.code(); + entity.state = VcStatus.INITIAL.code(); } return super.build(); } diff --git a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java index 5ff2e99f6..676f58e2e 100644 --- a/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java +++ b/spi/verifiable-credential-spi/src/test/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResourceTest.java @@ -46,8 +46,7 @@ void verifyBuilder_assertDefaultValues() { assertThat(vc.getClock()).isNotNull(); assertThat(vc.getId()).isNotNull(); - assertThat(vc.getStateAsEnum()).isEqualTo(VcIssuanceState.INITIAL); + assertThat(vc.getStateAsEnum()).isEqualTo(VcStatus.INITIAL); assertThat(vc.getTimeOfLastStatusUpdate()).isNull(); - assertThat(vc.getCredentialStatus()).isNull(); } } \ No newline at end of file diff --git a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/VerifiableCredentialTestUtil.java b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/VerifiableCredentialTestUtil.java index e986bb000..94402315a 100644 --- a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/VerifiableCredentialTestUtil.java +++ b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/VerifiableCredentialTestUtil.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.jwk.Curve; @@ -25,6 +24,7 @@ import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.security.token.jwt.CryptoConverter; /** * Util class to manipulate VerifiableCredentials in tests. @@ -49,7 +49,9 @@ public static ECKey generateEcKey(String kid) { public static SignedJWT buildSignedJwt(JWTClaimsSet claims, ECKey jwk) { try { - var jwsHeader = new JWSHeader.Builder(JWSAlgorithm.ES256) + var signer = CryptoConverter.createSigner(jwk); + var algorithm = CryptoConverter.getRecommendedAlgorithm(signer); + var jwsHeader = new JWSHeader.Builder(algorithm) .keyID(jwk.getKeyID()) .build(); var jws = new SignedJWT(jwsHeader, claims); From d4a6c97125a657ac977fc93f0ad1479d22c5bb64 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 8 May 2024 07:09:37 +0200 Subject: [PATCH 5/9] store keys and credentials in vault/store (wip) --- .../tests/PresentationApiComponentTest.java | 59 ++++++++++++------- .../IdentityHubRuntimeConfiguration.java | 3 +- .../identityhub/tests/fixtures/TestData.java | 2 +- .../src/test/resources/private-key.pem | 5 -- .../src/test/resources/public-key.pem | 4 -- .../api-tests/src/test/resources/testkey.pem | 11 ---- .../testfixtures/JwtCreationUtil.java | 6 +- 7 files changed, 43 insertions(+), 47 deletions(-) delete mode 100644 e2e-tests/api-tests/src/test/resources/private-key.pem delete mode 100644 e2e-tests/api-tests/src/test/resources/public-key.pem delete mode 100644 e2e-tests/api-tests/src/test/resources/testkey.pem diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java index 21925dd37..a0643ac09 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java @@ -23,18 +23,19 @@ import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest; +import org.eclipse.edc.identityhub.spi.store.CredentialStore; import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubRuntimeConfiguration; import org.eclipse.edc.identityhub.tests.fixtures.TestData; import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import java.util.Base64; import java.util.List; -import java.util.Map; import static io.restassured.http.ContentType.JSON; import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; @@ -64,12 +65,8 @@ public class PresentationApiComponentTest { ] } """; - private static final String TEST_PARTICIPANT_CONTEXT_ID = "test-participant"; + private static final String TEST_PARTICIPANT_CONTEXT_ID = "did:web:consumer"; private static final String TEST_PARTICIPANT_CONTEXT_ID_ENCODED = Base64.getUrlEncoder().encodeToString(TEST_PARTICIPANT_CONTEXT_ID.getBytes()); - // todo: these mocks should be replaced, once their respective implementations exist! -// private static final CredentialQueryResolver CREDENTIAL_QUERY_RESOLVER = mock(); -// private static final VerifiablePresentationService PRESENTATION_GENERATOR = mock(); - // private static final AccessTokenVerifier ACCESS_TOKEN_VERIFIER = mock(); private static final DidPublicKeyResolver DID_PUBLIC_KEY_RESOLVER = mock(); @RegisterExtension @@ -77,9 +74,6 @@ public class PresentationApiComponentTest { static { runtime = new EdcRuntimeExtension(":launcher", "identity-hub", IDENTITY_HUB_PARTICIPANT.controlPlaneConfiguration()); -// runtime.registerServiceMock(CredentialQueryResolver.class, CREDENTIAL_QUERY_RESOLVER); -// runtime.registerServiceMock(VerifiablePresentationService.class, PRESENTATION_GENERATOR); -// runtime.registerServiceMock(AccessTokenVerifier.class, ACCESS_TOKEN_VERIFIER); runtime.registerServiceMock(DidPublicKeyResolver.class, DID_PUBLIC_KEY_RESOLVER); } @@ -147,7 +141,6 @@ void query_withPresentationDefinition_shouldReturn503() { void query_tokenVerificationFails_shouldReturn401() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); -// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(failure("token not verified")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) .header(AUTHORIZATION, token) @@ -164,8 +157,6 @@ void query_tokenVerificationFails_shouldReturn401() { void query_queryResolutionFails_shouldReturn403() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); -// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); -// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.unauthorized("scope mismatch!")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) @@ -183,9 +174,6 @@ void query_queryResolutionFails_shouldReturn403() { void query_presentationGenerationFails_shouldReturn500() { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); -// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("test-scope1"))); -// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); -// when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(failure("generator test error")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) @@ -198,12 +186,9 @@ void query_presentationGenerationFails_shouldReturn500() { } @Test - void query_success() throws JOSEException { + void query_success_noCredentials() throws JOSEException { createParticipant(TEST_PARTICIPANT_CONTEXT_ID); var token = generateSiToken(); -// when(ACCESS_TOKEN_VERIFIER.verify(eq(token), anyString())).thenReturn(success(List.of("org.eclipse.edc.vc.type:TestScope1:read"))); -// when(CREDENTIAL_QUERY_RESOLVER.query(anyString(), any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); -// when(PRESENTATION_GENERATOR.createPresentation(anyString(), anyList(), eq(null), any())).thenReturn(success(createPresentationResponse())); when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); @@ -215,7 +200,32 @@ void query_success() throws JOSEException { .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) .then() .statusCode(200) - .log().ifValidationFails() + .log().ifError() + .extract().body().as(JsonObject.class); + + assertThat(response) + .hasEntrySatisfying("type", jsonValue -> assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) + .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)); + + } + + @Test + void query_success() throws JOSEException { + createParticipant(TEST_PARTICIPANT_CONTEXT_ID); + var store = runtime.getContext().getService(CredentialStore.class); + //todo: store credential + var token = generateSiToken(); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + var response = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(200) + .log().ifError() .extract().body().as(JsonObject.class); assertThat(response) @@ -227,19 +237,24 @@ void query_success() throws JOSEException { private PresentationResponseMessage createPresentationResponse() { var submission = new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("input-id", "ldp-vp", "foo"))); return PresentationResponseMessage.Builder.newinstance() - .presentation(List.of(TestData.VP_EXAMPLE)) + .presentation(List.of(TestData.VC_EXAMPLE)) .presentationSubmission(submission) .build(); } private void createParticipant(String participantId) { var service = runtime.getContext().getService(ParticipantContextService.class); + var vault = runtime.getContext().getService(Vault.class); + + var key = JwtCreationUtil.CONSUMER_KEY; + vault.storeSecret(key.getKeyID(), key.toPublicJWK().toJSONString()); + var manifest = ParticipantManifest.Builder.newInstance() .participantId(participantId) .did("did:web:%s".formatted(participantId)) .active(true) .key(KeyDescriptor.Builder.newInstance() - .keyGeneratorParams(Map.of("algorithm", "EC")) + .publicKeyJwk(key.toPublicJWK().toJSONObject()) .privateKeyAlias("%s-privatekey-alias".formatted(participantId)) .keyId("key-1") .build()) diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java index 6d824ab87..e28ddcc8a 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/IdentityHubRuntimeConfiguration.java @@ -15,6 +15,7 @@ package org.eclipse.edc.identityhub.tests.fixtures; import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; import java.net.URI; import java.util.HashMap; @@ -49,7 +50,7 @@ public Map controlPlaneConfiguration() { put("web.http.management.port", String.valueOf(managementEndpoint.getUrl().getPort())); put("web.http.management.path", managementEndpoint.getUrl().getPath()); put("edc.connector.name", name); - put("edc.ih.iam.publickey.alias", "public-key-alias"); + put("edc.ih.iam.publickey.alias", JwtCreationUtil.CONSUMER_KEY.getKeyID()); put("edc.ih.iam.id", "did:web:consumer"); } }; diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java index f54342774..7cdef1f40 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java @@ -16,7 +16,7 @@ public interface TestData { // taken from https://www.w3.org/TR/vc-data-model/#example-a-simple-example-of-a-verifiable-credential - String VP_EXAMPLE = """ + String VC_EXAMPLE = """ { "@context": [ "https://www.w3.org/2018/credentials/v1", diff --git a/e2e-tests/api-tests/src/test/resources/private-key.pem b/e2e-tests/api-tests/src/test/resources/private-key.pem deleted file mode 100644 index 18683140f..000000000 --- a/e2e-tests/api-tests/src/test/resources/private-key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIMH5nIIP0d/AI1D1L6DpIscPL3EX5x5XHxrBnt2+TduzoAoGCCqGSM49 -AwEHoUQDQgAET6VaLBE16dj2WcqcCaF6M1ORlaQhrT5bEhY8JUiDwRCFxVvg+cSY -fZMxhs+T5y3AfHejXkk0g6Ehg8HNHeJh/g== ------END EC PRIVATE KEY----- diff --git a/e2e-tests/api-tests/src/test/resources/public-key.pem b/e2e-tests/api-tests/src/test/resources/public-key.pem deleted file mode 100644 index 8085f6095..000000000 --- a/e2e-tests/api-tests/src/test/resources/public-key.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET6VaLBE16dj2WcqcCaF6M1ORlaQh -rT5bEhY8JUiDwRCFxVvg+cSYfZMxhs+T5y3AfHejXkk0g6Ehg8HNHeJh/g== ------END PUBLIC KEY----- diff --git a/e2e-tests/api-tests/src/test/resources/testkey.pem b/e2e-tests/api-tests/src/test/resources/testkey.pem deleted file mode 100644 index 18d27fb25..000000000 --- a/e2e-tests/api-tests/src/test/resources/testkey.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN RSA PUBLIC KEY----- -MIIBigKCAYEAvpSeulSuH8TTZCzs8MXXCYwfpFR7vqJ9tRvl+8eFYxkgbohGMR0F -mLHZXrH2Q8KXqYKfjRzO5fm25+uE7io9ZIQStWgUaxPl6B2TcGQAKIcBjRG2hTtZ -NqhGdpz36hm3fRpyASV0k1cs8Vc39cA0Lu+hHP5uP5yp3p1CQ2KVKognYOsO+fQj -Zjeuv+ei4ZcOyL/IyPbHPF4hXhPPN9g5bH6jAMBXaDPpwMHr3ArI3v3YjCGQbLpb -VNWMYVkquUIEXVLaH7vVcacPg4jpIICDhtycqa3rQgr/d0lyrseIyZP/52jLudaL -G7/aLyhlqoIW3nGooNan0f5dS1ctdTwFRTjqAboODVoDJwGI9UQVJESHk878QjA5 -r8IzMaR1nGRd29I8TGJp5XZ2Yf9iIqzapABqvGcDMzKiJ9UPCiyCEP15Rtl5ne1y -WY9oXwgP0FNOVpcIACFwl/lxvorQ/Seg7BfgBTvOn3TUHTVgX5TFw8hg8pSTM+f3 -rwWREnTDY69JAgMBAAE= ------END RSA PUBLIC KEY----- diff --git a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java index 3b0c50684..d56473a0e 100644 --- a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java +++ b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java @@ -39,7 +39,7 @@ public class JwtCreationUtil { * @return The generated self-issued token. */ public static String generateSiToken() { - return generateSiToken("consumer-id", "did:web:consumer", "provider-id", "did:web:provider"); + return generateSiToken("did:web:consumer", "did:web:consumer", "did:web:provider", "did:web:provider"); } /** @@ -52,8 +52,8 @@ public static String generateSiToken() { * @return The generated self-issued token. */ public static String generateSiToken(String consumerIdentifier, String consumerDid, String providerIdentifier, String providerDid) { - var accessToken = generateJwt(consumerDid, consumerDid, providerIdentifier, Map.of("scope", TEST_SCOPE), CONSUMER_KEY); - return generateJwt(consumerIdentifier, providerDid, providerDid, Map.of("client_id", providerIdentifier, "token", accessToken), PROVIDER_KEY); + var accessToken = generateJwt(consumerDid, consumerDid, providerDid, Map.of("scope", TEST_SCOPE), CONSUMER_KEY); + return generateJwt(consumerDid, providerDid, providerDid, Map.of("client_id", providerDid, "token", accessToken), PROVIDER_KEY); } /** From 250e35333b50237f8f2a98860f65565fcf705cbd Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 8 May 2024 13:35:08 +0200 Subject: [PATCH 6/9] updated PresentationApiComponentTest --- .../identityhub/DefaultServicesExtension.java | 21 +- .../core/CoreServicesExtension.java | 18 +- .../AccessTokenVerifierImplTest.java | 6 +- .../query/CredentialQueryResolverImpl.java | 9 +- .../tests/PresentationApiComponentTest.java | 266 ----------- .../tests/PresentationApiEndToEndTest.java | 442 ++++++++++++++++++ .../identityhub/tests/fixtures/TestData.java | 31 +- .../testfixtures/JwtCreationUtil.java | 12 +- 8 files changed, 504 insertions(+), 301 deletions(-) delete mode 100644 e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiComponentTest.java create mode 100644 e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java index b017610b3..79826befb 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java @@ -15,6 +15,8 @@ package org.eclipse.edc.identityhub; import org.eclipse.edc.iam.identitytrust.spi.verification.SignatureSuiteRegistry; +import org.eclipse.edc.iam.verifiablecredentials.StatusList2021RevocationService; +import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; import org.eclipse.edc.identityhub.accesstoken.rules.ClaimIsPresentRule; import org.eclipse.edc.identityhub.defaults.InMemoryCredentialStore; import org.eclipse.edc.identityhub.defaults.InMemoryKeyPairResourceStore; @@ -28,8 +30,10 @@ import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; import static org.eclipse.edc.identityhub.DefaultServicesExtension.NAME; @@ -42,10 +46,14 @@ public class DefaultServicesExtension implements ServiceExtension { public static final String NAME = "IdentityHub Default Services Extension"; - - + public static final long DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS = 15 * 60 * 1000L; + @Setting(value = "Validity period of cached StatusList2021 credential entries in milliseconds.", defaultValue = DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS + "", type = "long") + public static final String REVOCATION_CACHE_VALIDITY = "edc.iam.credential.revocation.cache.validity"; @Inject private TokenValidationRulesRegistry registry; + @Inject + private TypeManager typeManager; + private RevocationListService revocationService; @Override public String name() { @@ -84,6 +92,15 @@ public ScopeToCriterionTransformer createScopeTransformer(ServiceExtensionContex return new EdcScopeToCriterionTransformer(); } + @Provider + public RevocationListService createRevocationListService(ServiceExtensionContext context) { + if (revocationService == null) { + var validity = context.getConfig().getLong(REVOCATION_CACHE_VALIDITY, DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS); + revocationService = new StatusList2021RevocationService(typeManager.getMapper(), validity); + } + return revocationService; + } + @Provider(isDefault = true) public SignatureSuiteRegistry createSignatureSuiteRegistry() { return new InMemorySignatureSuiteRegistry(); diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java index 77f96c144..24ac7727d 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java @@ -16,7 +16,6 @@ import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.iam.identitytrust.spi.verification.SignatureSuiteRegistry; -import org.eclipse.edc.iam.verifiablecredentials.StatusList2021RevocationService; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; import org.eclipse.edc.identithub.verifiablepresentation.PresentationCreatorRegistryImpl; @@ -82,9 +81,6 @@ public class CoreServicesExtension implements ServiceExtension { @Setting(value = "Public key in PEM format") public static final String PUBLIC_KEY_PEM = "edc.ih.iam.publickey.pem"; - public static final long DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS = 15 * 60 * 1000L; - @Setting(value = "Validity period of cached StatusList2021 credential entries in milliseconds.", defaultValue = DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS + "", type = "long") - public static final String REVOCATION_CACHE_VALIDITY = "edc.iam.credential.revocation.cache.validity"; public static final String PRESENTATION_EXCHANGE_V_1_JSON = "presentation-exchange.v1.json"; public static final String PRESENTATION_QUERY_V_08_JSON = "iatp.v08.json"; @@ -122,7 +118,7 @@ public class CoreServicesExtension implements ServiceExtension { private SignatureSuiteRegistry suiteRegistry; @Inject private KeyPairService keyPairService; - + @Inject private RevocationListService revocationService; @Override @@ -146,7 +142,7 @@ public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext con @Provider public CredentialQueryResolver createCredentialQueryResolver(ServiceExtensionContext context) { - return new CredentialQueryResolverImpl(credentialStore, transformer, createRevocationListService(context), context.getMonitor().withPrefix("Credential Query")); + return new CredentialQueryResolverImpl(credentialStore, transformer, revocationService, context.getMonitor().withPrefix("Credential Query")); } @Provider @@ -162,18 +158,10 @@ public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionC return presentationCreatorRegistry; } - @Provider - public RevocationListService createRevocationListService(ServiceExtensionContext context) { - if (revocationService == null) { - var validity = context.getConfig().getLong(REVOCATION_CACHE_VALIDITY, DEFAULT_REVOCATION_CACHE_VALIDITY_MILLIS); - revocationService = new StatusList2021RevocationService(typeManager.getMapper(), validity); - } - return revocationService; - } @Provider public VerifiablePresentationService presentationGenerator(ServiceExtensionContext context) { - return new VerifiablePresentationServiceImpl(CredentialFormat.JSON_LD, presentationCreatorRegistry(context), context.getMonitor()); + return new VerifiablePresentationServiceImpl(CredentialFormat.JWT, presentationCreatorRegistry(context), context.getMonitor()); } diff --git a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java index 0c1fe53ae..be1e28fc8 100644 --- a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java +++ b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java @@ -55,7 +55,7 @@ class AccessTokenVerifierImplTest { void verify_validSiToken_validAccessToken() { when(tokenValidationSerivce.validate(anyString(), any(), anyList())) .thenReturn(Result.success(idToken)); - AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")) + AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")) .isSucceeded() .satisfies(strings -> Assertions.assertThat(strings).containsOnly(JwtCreationUtil.TEST_SCOPE)); verify(tokenValidationSerivce, times(2)).validate(anyString(), any(PublicKeyResolver.class), anyList()); @@ -66,7 +66,7 @@ void verify_validSiToken_validAccessToken() { void verify_siTokenValidationFails() { when(tokenValidationSerivce.validate(anyString(), any(), anyList())) .thenReturn(Result.failure("test-failure")); - AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")).isFailed() + AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")).isFailed() .detail().contains("test-failure"); } @@ -75,7 +75,7 @@ void verify_noAccessTokenClaim() { when(tokenValidationSerivce.validate(anyString(), any(PublicKeyResolver.class), anyList())) .thenReturn(Result.failure("no access token")); - AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")).isFailed() + AbstractResultAssert.assertThat(verifier.verify(JwtCreationUtil.generateSiToken(OWN_DID, OTHER_PARTICIPANT_DID), "did:web:test_participant")).isFailed() .detail().contains("no access token"); verify(tokenValidationSerivce).validate(anyString(), any(PublicKeyResolver.class), anyList()); } diff --git a/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java b/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java index 8fa562fb0..b8aca9d6b 100644 --- a/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java +++ b/core/lib/credential-query-lib/src/main/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImpl.java @@ -18,6 +18,7 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer; import org.eclipse.edc.identityhub.spi.store.CredentialStore; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.verifiablecredentials.resolution.CredentialQueryResolver; import org.eclipse.edc.identityhub.spi.verifiablecredentials.resolution.QueryResult; @@ -28,7 +29,6 @@ import org.eclipse.edc.spi.result.Result; import java.time.Instant; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -112,7 +112,7 @@ private boolean filterInvalidCredentials(VerifiableCredentialResource verifiable monitor.warning("Credential '%s' is expired.".formatted(credential.getId())); return false; } - var revocationResult = revocationService.checkValidity(credential); + var revocationResult = credential.getCredentialStatus().isEmpty() ? Result.success() : revocationService.checkValidity(credential); if (revocationResult.failed()) { monitor.warning("Credential '%s' not valid: %s".formatted(credential.getId(), revocationResult.getFailureDetail())); return false; @@ -155,9 +155,10 @@ private Result> queryCredentials(List assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) - .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)); - - } - - @Test - void query_success() throws JOSEException { - createParticipant(TEST_PARTICIPANT_CONTEXT_ID); - var store = runtime.getContext().getService(CredentialStore.class); - //todo: store credential - var token = generateSiToken(); - when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); - when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); - - var response = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() - .contentType(JSON) - .header(AUTHORIZATION, token) - .body(VALID_QUERY_WITH_SCOPE) - .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) - .then() - .statusCode(200) - .log().ifError() - .extract().body().as(JsonObject.class); - - assertThat(response) - .hasEntrySatisfying("type", jsonValue -> assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) - .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)); - - } - - private PresentationResponseMessage createPresentationResponse() { - var submission = new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("input-id", "ldp-vp", "foo"))); - return PresentationResponseMessage.Builder.newinstance() - .presentation(List.of(TestData.VC_EXAMPLE)) - .presentationSubmission(submission) - .build(); - } - - private void createParticipant(String participantId) { - var service = runtime.getContext().getService(ParticipantContextService.class); - var vault = runtime.getContext().getService(Vault.class); - - var key = JwtCreationUtil.CONSUMER_KEY; - vault.storeSecret(key.getKeyID(), key.toPublicJWK().toJSONString()); - - var manifest = ParticipantManifest.Builder.newInstance() - .participantId(participantId) - .did("did:web:%s".formatted(participantId)) - .active(true) - .key(KeyDescriptor.Builder.newInstance() - .publicKeyJwk(key.toPublicJWK().toJSONObject()) - .privateKeyAlias("%s-privatekey-alias".formatted(participantId)) - .keyId("key-1") - .build()) - .build(); - service.createParticipantContext(manifest) - .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); - } - -} diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java new file mode 100644 index 000000000..79aef5a66 --- /dev/null +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/PresentationApiEndToEndTest.java @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.tests; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.gen.ECKeyGenerator; +import com.nimbusds.jwt.SignedJWT; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; +import org.eclipse.edc.iam.identitytrust.spi.model.PresentationResponseMessage; +import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.credentialservice.InputDescriptorMapping; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.credentialservice.PresentationSubmission; +import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; +import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor; +import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest; +import org.eclipse.edc.identityhub.spi.store.CredentialStore; +import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VcStatus; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; +import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubRuntimeConfiguration; +import org.eclipse.edc.identityhub.tests.fixtures.TestData; +import org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.Vault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.text.ParseException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import static io.restassured.http.ContentType.JSON; +import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.identityhub.verifiablecredentials.testfixtures.JwtCreationUtil.generateSiToken; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@EndToEndTest +public class PresentationApiEndToEndTest { + + protected static final IdentityHubRuntimeConfiguration IDENTITY_HUB_PARTICIPANT = IdentityHubRuntimeConfiguration.Builder.newInstance() + .name("identity-hub") + .id("identity-hub") + .build(); + private static final String KEY_RESOURCE_ID = "key-1"; + private static final String VALID_QUERY_WITH_SCOPE = """ + { + "@context": [ + "https://identity.foundation/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type": "PresentationQueryMessage", + "scope":[ + "org.eclipse.edc.vc.type:AlumniCredential:read" + ] + } + """; + private static final String VALID_QUERY_WITH_ADDITIONAL_SCOPE = """ + { + "@context": [ + "https://identity.foundation/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type": "PresentationQueryMessage", + "scope":[ + "org.eclipse.edc.vc.type:AlumniCredential:read", + "org.eclipse.edc.vc.type:SuperSecretCredential:*" + ] + } + """; + private static final String TEST_PARTICIPANT_CONTEXT_ID = "did:web:consumer"; + private static final String TEST_PARTICIPANT_CONTEXT_ID_ENCODED = Base64.getUrlEncoder().encodeToString(TEST_PARTICIPANT_CONTEXT_ID.getBytes()); + private static final DidPublicKeyResolver DID_PUBLIC_KEY_RESOLVER = mock(); + private static final RevocationListService REVOCATION_LIST_SERVICE = mock(); + private static final ObjectMapper OBJECT_MAPPER = JacksonJsonLd.createObjectMapper() + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT); + @RegisterExtension + static EdcRuntimeExtension runtime; + + static { + runtime = new EdcRuntimeExtension(":launcher", "identity-hub", IDENTITY_HUB_PARTICIPANT.controlPlaneConfiguration()); + runtime.registerServiceMock(DidPublicKeyResolver.class, DID_PUBLIC_KEY_RESOLVER); + runtime.registerServiceMock(RevocationListService.class, REVOCATION_LIST_SERVICE); + } + + @Test + void query_tokenNotPresent_shouldReturn401() { + createParticipant(); + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType("application/json") + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(401) + .extract().body().asString(); + } + + @Test + void query_validationError_shouldReturn400() { + createParticipant(); + var query = """ + { + "@context": [ + "https://identity.foundation/participants/test-participant/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type": "PresentationQueryMessage" + } + """; + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, generateSiToken()) + .body(query) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(400) + .extract().body().asString(); + + } + + @Test + void query_withPresentationDefinition_shouldReturn503() { + createParticipant(); + var query = """ + { + "@context": [ + "https://identity.foundation/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type": "PresentationQueryMessage", + "presentationDefinition":{ + } + } + """; + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, generateSiToken()) + .body(query) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(503) + .extract().body().asString(); + } + + @Test + void query_tokenVerificationFails_shouldReturn401() throws JOSEException { + createParticipant(); + + var spoofedKey = new ECKeyGenerator(Curve.P_256).keyID("did:web:provider#key1").generate(); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(spoofedKey.toPublicKey())); + + var token = generateSiToken(); + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(401) + .log().ifValidationFails() + .body("[0].type", equalTo("AuthenticationFailed")) + .body("[0].message", equalTo("ID token verification failed: Token verification failed")); + } + + @Test + void query_credentialQueryResolverFails_shouldReturn403() throws JOSEException, JsonProcessingException { + createParticipant(); + var token = generateSiToken(); + + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + // create the credential in the store + var store = runtime.getContext().getService(CredentialStore.class); + var cred = OBJECT_MAPPER.readValue(TestData.VC_EXAMPLE, VerifiableCredential.class); + var res = VerifiableCredentialResource.Builder.newInstance() + .state(VcStatus.ISSUED) + .credential(new VerifiableCredentialContainer(TestData.VC_EXAMPLE, CredentialFormat.JWT, cred)) + .issuerId("https://example.edu/issuers/565049") + .holderId("did:example:ebfeb1f712ebc6f1c276e12ec21") + .participantId(TEST_PARTICIPANT_CONTEXT_ID) + .build(); + store.create(res); + + // create another credential in the store + var cred2 = OBJECT_MAPPER.readValue(TestData.VC_EXAMPLE_2, VerifiableCredential.class); + var res2 = VerifiableCredentialResource.Builder.newInstance() + .state(VcStatus.ISSUED) + .credential(new VerifiableCredentialContainer(TestData.VC_EXAMPLE_2, CredentialFormat.JWT, cred2)) + .issuerId("https://example.edu/issuers/12345") + .holderId("did:example:ebfeb1f712ebc6f1c276e12ec21") + .participantId(TEST_PARTICIPANT_CONTEXT_ID) + .build(); + store.create(res2); + + + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_ADDITIONAL_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .log().ifError() + .statusCode(403) + .body("[0].type", equalTo("NotAuthorized")) + .body("[0].message", equalTo("Invalid query: requested Credentials outside of scope.")); + } + + @Test + void query_presentationGenerationFails_shouldReturn500() { + createParticipant(); + var token = generateSiToken(); + + // delete the key, so the presentation generation will fail + var kpStore = runtime.getService(KeyPairResourceStore.class); + kpStore.deleteById(KEY_RESOURCE_ID) + .orElseThrow(f -> new EdcException(f.getFailureDetail())); + IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(500) + .log().ifValidationFails(); + } + + @Test + void query_success_noCredentials() throws JOSEException { + createParticipant(); + var token = generateSiToken(); + + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + var response = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(JsonObject.class); + + assertThat(response) + .hasEntrySatisfying("type", jsonValue -> assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) + .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)) + .hasEntrySatisfying("presentation", jsonValue -> assertThat(extractCredentials(((JsonString) jsonValue).getString())).isEmpty()); + + } + + @Test + void query_success_containsCredential() throws JOSEException, JsonProcessingException { + createParticipant(); + var store = runtime.getContext().getService(CredentialStore.class); + var cred = OBJECT_MAPPER.readValue(TestData.VC_EXAMPLE, VerifiableCredential.class); + var res = VerifiableCredentialResource.Builder.newInstance() + .state(VcStatus.ISSUED) + .credential(new VerifiableCredentialContainer(TestData.VC_EXAMPLE, CredentialFormat.JWT, cred)) + .issuerId("https://example.edu/issuers/565049") + .holderId("did:example:ebfeb1f712ebc6f1c276e12ec21") + .participantId(TEST_PARTICIPANT_CONTEXT_ID) + .build(); + + store.create(res); + var token = generateSiToken(); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + var response = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(JsonObject.class); + + assertThat(response) + .hasEntrySatisfying("type", jsonValue -> assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) + .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)) + .hasEntrySatisfying("presentation", jsonValue -> { + assertThat(jsonValue.getValueType()).isEqualTo(JsonValue.ValueType.STRING); + var vpToken = ((JsonString) jsonValue).getString(); + assertThat(vpToken).isNotNull(); + assertThat(extractCredentials(vpToken)).isNotEmpty(); + }); + + } + + + @ParameterizedTest(name = "VcState code: {0}") + @ValueSource(ints = { 600, 700, 800, 900 }) + void query_shouldFilterOutInvalidCreds(int vcStateCode) throws JOSEException, JsonProcessingException { + createParticipant(); + var store = runtime.getContext().getService(CredentialStore.class); + + // modify VC content, so that it becomes either not-yet-valid or expired + var vcContent = TestData.VC_EXAMPLE; + if (vcStateCode == VcStatus.EXPIRED.code()) { + var expirationInPast = Instant.now().minus(1, ChronoUnit.DAYS).toString(); + vcContent = vcContent.replaceAll("\"expirationDate\": \"2999-01-01T19:23:24Z\",", "\"expirationDate\": \"" + expirationInPast + "\","); + } else if (vcStateCode == VcStatus.NOT_YET_VALID.code()) { + var futureIssuance = Instant.now().plus(1, ChronoUnit.DAYS).toString(); + vcContent = vcContent.replaceAll("\"issuanceDate\": \".*\",", "\"issuanceDate\": \"" + futureIssuance + "\","); + } + + + var cred = OBJECT_MAPPER.readValue(vcContent, VerifiableCredential.class); + // inject a CredentialStatus object, that triggers the revocation check + if (vcStateCode == VcStatus.SUSPENDED.code()) { + cred.getCredentialStatus().add(new CredentialStatus("test-cred-stat-id", "StatusList2021Entry", + Map.of("statusListCredential", "https://university.example/credentials/status/3", + "statusPurpose", "suspension", + "statusListIndex", 69))); + when(REVOCATION_LIST_SERVICE.checkValidity(any(VerifiableCredential.class))) + .thenReturn(Result.failure("suspended")); + } + // create the credential in the store + var res = VerifiableCredentialResource.Builder.newInstance() + .state(VcStatus.from(vcStateCode)) + .credential(new VerifiableCredentialContainer(vcContent, CredentialFormat.JWT, cred)) + .issuerId("https://example.edu/issuers/565049") + .holderId("did:example:ebfeb1f712ebc6f1c276e12ec21") + .participantId(TEST_PARTICIPANT_CONTEXT_ID) + .build(); + store.create(res); + + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:consumer#key1"))).thenReturn(Result.success(JwtCreationUtil.CONSUMER_KEY.toPublicKey())); + when(DID_PUBLIC_KEY_RESOLVER.resolveKey(eq("did:web:provider#key1"))).thenReturn(Result.success(JwtCreationUtil.PROVIDER_KEY.toPublicKey())); + + var token = generateSiToken(); + var response = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() + .contentType(JSON) + .header(AUTHORIZATION, token) + .body(VALID_QUERY_WITH_SCOPE) + .post("/v1/participants/%s/presentations/query".formatted(TEST_PARTICIPANT_CONTEXT_ID_ENCODED)) + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(JsonObject.class); + + assertThat(response) + .hasEntrySatisfying("type", jsonValue -> assertThat(jsonValue.toString()).contains("PresentationResponseMessage")) + .hasEntrySatisfying("@context", jsonValue -> assertThat(jsonValue.asJsonArray()).hasSize(2)) + .hasEntrySatisfying("presentation", jsonValue -> { + assertThat(jsonValue.getValueType()).isEqualTo(JsonValue.ValueType.STRING); + var vpToken = ((JsonString) jsonValue).getString(); + assertThat(vpToken).isNotNull(); + assertThat(extractCredentials(vpToken)).isEmpty(); // credential should be filtered out + }); + + } + + /** + * extracts a (potentially empty) list of verifiable credentials from a JWT-VP + */ + @SuppressWarnings("unchecked") + private List extractCredentials(String vpToken) { + try { + var jwt = SignedJWT.parse(vpToken); + var vpClaim = jwt.getJWTClaimsSet().getClaim("vp"); + if (vpClaim == null) return List.of(); + + Map map = (Map) OBJECT_MAPPER.convertValue(vpClaim, Map.class); + + return (List) map.get("verifiableCredential"); + + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private PresentationResponseMessage createPresentationResponse() { + var submission = new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("input-id", "ldp-vp", "foo"))); + return PresentationResponseMessage.Builder.newinstance() + .presentation(List.of(TestData.VC_EXAMPLE)) + .presentationSubmission(submission) + .build(); + } + + private void createParticipant() { + var service = runtime.getContext().getService(ParticipantContextService.class); + var vault = runtime.getContext().getService(Vault.class); + + var key = JwtCreationUtil.CONSUMER_KEY; + var privateKeyAlias = "%s-privatekey-alias".formatted(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID); + vault.storeSecret(key.getKeyID(), key.toPublicJWK().toJSONString()); + vault.storeSecret(privateKeyAlias, key.toJSONString()); + var manifest = ParticipantManifest.Builder.newInstance() + .participantId(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID) + .did("did:web:%s".formatted(PresentationApiEndToEndTest.TEST_PARTICIPANT_CONTEXT_ID)) + .active(true) + .key(KeyDescriptor.Builder.newInstance() + .publicKeyJwk(key.toPublicJWK().toJSONObject()) + .privateKeyAlias(privateKeyAlias) + .keyId(KEY_RESOURCE_ID) + .build()) + .build(); + service.createParticipantContext(manifest) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + } + +} diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java index 7cdef1f40..2e001a08d 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java @@ -26,6 +26,7 @@ public interface TestData { "type": ["VerifiableCredential", "AlumniCredential"], "issuer": "https://example.edu/issuers/565049", "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2999-01-01T19:23:24Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "alumniOf": { @@ -44,10 +45,32 @@ public interface TestData { "created": "2017-06-18T21:19:10Z", "proofPurpose": "assertionMethod", "verificationMethod": "https://example.edu/issuers/565049#key-1", - "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5X - sITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUc - X16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtj - PAYuNzVBAh4vGHSrQyHUdBBPM" + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" + } + } + """; + + // this VC is + String VC_EXAMPLE_2 = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/1872", + "type": ["VerifiableCredential", "SuperSecretCredential"], + "issuer": "https://example.edu/issuers/12345", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "super-secret-content": 42 + }, + "proof": { + "type": "RsaSignature2018", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.edu/issuers/565049#key-1", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" } } """; diff --git a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java index d56473a0e..5002d6c0c 100644 --- a/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java +++ b/spi/verifiable-credential-spi/src/testFixtures/java/org/eclipse/edc/identityhub/verifiablecredentials/testfixtures/JwtCreationUtil.java @@ -29,7 +29,7 @@ * Utility class to generate various JWTs */ public class JwtCreationUtil { - public static final String TEST_SCOPE = "org.eclipse.edc.vc.type:SomeTestCredential:read"; + public static final String TEST_SCOPE = "org.eclipse.edc.vc.type:AlumniCredential:read"; public static final ECKey CONSUMER_KEY = generateEcKey("did:web:consumer#key1"); public static final ECKey PROVIDER_KEY = generateEcKey("did:web:provider#key1"); @@ -39,19 +39,17 @@ public class JwtCreationUtil { * @return The generated self-issued token. */ public static String generateSiToken() { - return generateSiToken("did:web:consumer", "did:web:consumer", "did:web:provider", "did:web:provider"); + return generateSiToken("did:web:consumer", "did:web:provider"); } /** * Generates a self-issued token. * - * @param consumerIdentifier The consumer participant agent identifier. - * @param consumerDid The consumer DID. - * @param providerIdentifier The provider participant agent identifier. - * @param providerDid The provider DID. + * @param consumerDid The consumer DID. + * @param providerDid The provider DID. * @return The generated self-issued token. */ - public static String generateSiToken(String consumerIdentifier, String consumerDid, String providerIdentifier, String providerDid) { + public static String generateSiToken(String consumerDid, String providerDid) { var accessToken = generateJwt(consumerDid, consumerDid, providerDid, Map.of("scope", TEST_SCOPE), CONSUMER_KEY); return generateJwt(consumerDid, providerDid, providerDid, Map.of("client_id", providerDid, "token", accessToken), PROVIDER_KEY); } From 912116960ab6fc11d3ef277d009d22dbdeb918af Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 8 May 2024 14:40:37 +0200 Subject: [PATCH 7/9] fix tests, checkstyle, DEPENDENCIES --- DEPENDENCIES | 12 +++++++----- .../verification/AccessTokenVerifierImplTest.java | 2 +- .../query/CredentialQueryResolverImplTest.java | 8 +++++++- .../model/VerifiableCredentialResource.java | 7 +++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/DEPENDENCIES b/DEPENDENCIES index b88d066e2..38c991628 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -27,12 +27,10 @@ maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.14 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.1, Apache-2.0, approved, #8802 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.16.2, Apache-2.0, approved, #11855 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.17.1, Apache-2.0, approved, #13669 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.17.0, Apache-2.0, approved, #14161 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.17.1, Apache-2.0, approved, #14161 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.0, Apache-2.0, approved, #4699 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.1, Apache-2.0, approved, #7930 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.16.2, Apache-2.0, approved, #11853 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.0, Apache-2.0, approved, #14160 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.1, Apache-2.0, approved, #14160 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.17.1, Apache-2.0, approved, #14194 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.1, Apache-2.0, approved, #9236 @@ -362,17 +360,21 @@ maven/mavencentral/org.jetbrains/annotations/24.1.0, Apache-2.0, approved, clear maven/mavencentral/org.junit-pioneer/junit-pioneer/2.2.0, EPL-2.0, approved, #11857 maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.1, EPL-2.0, approved, #9714 maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.2, EPL-2.0, approved, #9714 +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.9.2, EPL-2.0, approved, #3133 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.1, EPL-2.0, approved, #9711 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.2, EPL-2.0, approved, #9711 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.9.2, EPL-2.0, approved, #3125 maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.1, EPL-2.0, approved, #9708 -maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.2, EPL-2.0, approved, #9708 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.9.2, EPL-2.0, approved, #3134 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.1, EPL-2.0, approved, #9715 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.2, EPL-2.0, approved, #9715 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.9.2, EPL-2.0, approved, #3130 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.1, EPL-2.0, approved, #9709 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.2, EPL-2.0, approved, #9709 -maven/mavencentral/org.junit.platform/junit-platform-launcher/1.10.2, EPL-2.0, approved, #9704 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.9.2, EPL-2.0, approved, #3128 +maven/mavencentral/org.junit.platform/junit-platform-launcher/1.10.1, EPL-2.0, approved, #9704 +maven/mavencentral/org.junit.platform/junit-platform-launcher/1.9.2, EPL-2.0, approved, #3132 maven/mavencentral/org.junit/junit-bom/5.10.1, EPL-2.0, approved, #9844 -maven/mavencentral/org.junit/junit-bom/5.10.2, EPL-2.0, approved, #9844 maven/mavencentral/org.junit/junit-bom/5.9.2, EPL-2.0, approved, #4711 maven/mavencentral/org.jvnet.mimepull/mimepull/1.9.15, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ21484 maven/mavencentral/org.mock-server/mockserver-client-java/5.15.0, Apache-2.0 AND LGPL-3.0-only, approved, #9324 diff --git a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java index be1e28fc8..7455760f7 100644 --- a/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java +++ b/core/lib/accesstoken-lib/src/test/java/org/eclipse/edc/identityhub/accesstoken/verification/AccessTokenVerifierImplTest.java @@ -48,7 +48,7 @@ class AccessTokenVerifierImplTest { private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(tokenValidationSerivce, publicKeySupplier, tokenValidationRulesRegistry, mock(), pkResolver); private final ClaimToken idToken = ClaimToken.Builder.newInstance() .claim("token", "test-at") - .claim("scope", "org.eclipse.edc.vc.type:SomeTestCredential:read") + .claim("scope", "org.eclipse.edc.vc.type:AlumniCredential:read") .build(); @Test diff --git a/core/lib/credential-query-lib/src/test/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImplTest.java b/core/lib/credential-query-lib/src/test/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImplTest.java index 1a864100e..5a89b0e4b 100644 --- a/core/lib/credential-query-lib/src/test/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImplTest.java +++ b/core/lib/credential-query-lib/src/test/java/org/eclipse/edc/identityhub/query/CredentialQueryResolverImplTest.java @@ -17,6 +17,7 @@ import org.eclipse.edc.iam.identitytrust.spi.model.PresentationQueryMessage; import org.eclipse.edc.iam.verifiablecredentials.spi.RevocationListService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; 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; @@ -274,7 +275,12 @@ void query_whenNotYetValidCredential_doesNotInclude() { @Test void query_whenRevokedCredential_doesNotInclude() { when(revocationServiceMock.checkValidity(any())).thenReturn(Result.failure("revoked")); - var credential = createCredential("TestCredential").build(); + var credential = createCredential("TestCredential") + .credentialStatus(new CredentialStatus("test-cred-stat-id", "StatusList2021Entry", + Map.of("statusListCredential", "https://university.example/credentials/status/3", + "statusPurpose", "suspension", + "statusListIndex", 69))) + .build(); var resource = createCredentialResource(credential).build(); when(storeMock.query(any())).thenAnswer(i -> success(List.of(resource))); var res = resolver.query(TEST_PARTICIPANT_CONTEXT_ID, diff --git a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java index 0e4ef207b..bffca81de 100644 --- a/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java +++ b/spi/verifiable-credential-spi/src/main/java/org/eclipse/edc/identityhub/spi/verifiablecredentials/model/VerifiableCredentialResource.java @@ -58,14 +58,17 @@ public Policy getReissuancePolicy() { return reissuancePolicy; } + @JsonIgnore public boolean isExpired() { return EXPIRED.code() == state; } + @JsonIgnore public boolean isRevoked() { return REVOKED.code() == state; } + @JsonIgnore public boolean isSuspended() { return SUSPENDED.code() == state; } @@ -96,10 +99,6 @@ public void setCredentialStatus(VcStatus status) { timeOfLastStatusUpdate = Instant.now(); } - public VcStatus getCredentialStatusEnum() { - return VcStatus.from(state); - } - public static class Builder extends IdentityResource.Builder { protected Builder(VerifiableCredentialResource resource) { From fbf9d3128ae0f6192724bdd96ffac0da303d9eb2 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 8 May 2024 14:48:43 +0200 Subject: [PATCH 8/9] moved test data to static files --- .../identityhub/tests/fixtures/TestData.java | 60 ++----------------- .../src/test/resources/vc_example_1.json | 37 ++++++++++++ .../src/test/resources/vc_example_2.json | 24 ++++++++ 3 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 e2e-tests/api-tests/src/test/resources/vc_example_1.json create mode 100644 e2e-tests/api-tests/src/test/resources/vc_example_2.json diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java index 2e001a08d..019a1989e 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/fixtures/TestData.java @@ -14,64 +14,12 @@ package org.eclipse.edc.identityhub.tests.fixtures; +import org.eclipse.edc.junit.testfixtures.TestUtils; + public interface TestData { // taken from https://www.w3.org/TR/vc-data-model/#example-a-simple-example-of-a-verifiable-credential - String VC_EXAMPLE = """ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "id": "http://example.edu/credentials/1872", - "type": ["VerifiableCredential", "AlumniCredential"], - "issuer": "https://example.edu/issuers/565049", - "issuanceDate": "2010-01-01T19:23:24Z", - "expirationDate": "2999-01-01T19:23:24Z", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "alumniOf": { - "id": "did:example:c276e12ec21ebfeb1f712ebc6f1", - "name": [{ - "value": "Example University", - "lang": "en" - }, { - "value": "Exemple d'Université", - "lang": "fr" - }] - } - }, - "proof": { - "type": "RsaSignature2018", - "created": "2017-06-18T21:19:10Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "https://example.edu/issuers/565049#key-1", - "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" - } - } - """; + String VC_EXAMPLE = TestUtils.getResourceFileContentAsString("vc_example_1.json"); // this VC is - String VC_EXAMPLE_2 = """ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "id": "http://example.edu/credentials/1872", - "type": ["VerifiableCredential", "SuperSecretCredential"], - "issuer": "https://example.edu/issuers/12345", - "issuanceDate": "2010-01-01T19:23:24Z", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "super-secret-content": 42 - }, - "proof": { - "type": "RsaSignature2018", - "created": "2017-06-18T21:19:10Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "https://example.edu/issuers/565049#key-1", - "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" - } - } - """; + String VC_EXAMPLE_2 = TestUtils.getResourceFileContentAsString("vc_example_2.json"); } diff --git a/e2e-tests/api-tests/src/test/resources/vc_example_1.json b/e2e-tests/api-tests/src/test/resources/vc_example_1.json new file mode 100644 index 000000000..37da332a2 --- /dev/null +++ b/e2e-tests/api-tests/src/test/resources/vc_example_1.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/1872", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "issuer": "https://example.edu/issuers/565049", + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2999-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "alumniOf": { + "id": "did:example:c276e12ec21ebfeb1f712ebc6f1", + "name": [ + { + "value": "Example University", + "lang": "en" + }, + { + "value": "Exemple d'Université", + "lang": "fr" + } + ] + } + }, + "proof": { + "type": "RsaSignature2018", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.edu/issuers/565049#key-1", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" + } +} \ No newline at end of file diff --git a/e2e-tests/api-tests/src/test/resources/vc_example_2.json b/e2e-tests/api-tests/src/test/resources/vc_example_2.json new file mode 100644 index 000000000..2bfc7c08e --- /dev/null +++ b/e2e-tests/api-tests/src/test/resources/vc_example_2.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/1872", + "type": [ + "VerifiableCredential", + "SuperSecretCredential" + ], + "issuer": "https://example.edu/issuers/12345", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "super-secret-content": 42 + }, + "proof": { + "type": "RsaSignature2018", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.edu/issuers/565049#key-1", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" + } +} \ No newline at end of file From 81e46b587270e2a40c925d47ec8d04cfcd7e1879 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 8 May 2024 14:53:31 +0200 Subject: [PATCH 9/9] DEPENDENCIES --- DEPENDENCIES | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/DEPENDENCIES b/DEPENDENCIES index 38c991628..4e6c8b71e 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -360,21 +360,17 @@ maven/mavencentral/org.jetbrains/annotations/24.1.0, Apache-2.0, approved, clear maven/mavencentral/org.junit-pioneer/junit-pioneer/2.2.0, EPL-2.0, approved, #11857 maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.1, EPL-2.0, approved, #9714 maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.2, EPL-2.0, approved, #9714 -maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.9.2, EPL-2.0, approved, #3133 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.1, EPL-2.0, approved, #9711 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.2, EPL-2.0, approved, #9711 -maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.9.2, EPL-2.0, approved, #3125 maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.1, EPL-2.0, approved, #9708 -maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.9.2, EPL-2.0, approved, #3134 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.2, EPL-2.0, approved, #9708 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.1, EPL-2.0, approved, #9715 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.2, EPL-2.0, approved, #9715 -maven/mavencentral/org.junit.platform/junit-platform-commons/1.9.2, EPL-2.0, approved, #3130 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.1, EPL-2.0, approved, #9709 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.2, EPL-2.0, approved, #9709 -maven/mavencentral/org.junit.platform/junit-platform-engine/1.9.2, EPL-2.0, approved, #3128 -maven/mavencentral/org.junit.platform/junit-platform-launcher/1.10.1, EPL-2.0, approved, #9704 -maven/mavencentral/org.junit.platform/junit-platform-launcher/1.9.2, EPL-2.0, approved, #3132 +maven/mavencentral/org.junit.platform/junit-platform-launcher/1.10.2, EPL-2.0, approved, #9704 maven/mavencentral/org.junit/junit-bom/5.10.1, EPL-2.0, approved, #9844 +maven/mavencentral/org.junit/junit-bom/5.10.2, EPL-2.0, approved, #9844 maven/mavencentral/org.junit/junit-bom/5.9.2, EPL-2.0, approved, #4711 maven/mavencentral/org.jvnet.mimepull/mimepull/1.9.15, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ21484 maven/mavencentral/org.mock-server/mockserver-client-java/5.15.0, Apache-2.0 AND LGPL-3.0-only, approved, #9324