diff --git a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationServiceTest.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationServiceTest.java index 28271ace9b0..06d87b0d6df 100644 --- a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationServiceTest.java +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationServiceTest.java @@ -20,12 +20,19 @@ import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockserver.integration.ClientAndServer; import org.mockserver.model.HttpResponse; import org.mockserver.verify.VerificationTimes; import java.util.Map; +import java.util.stream.Stream; import static org.eclipse.edc.iam.verifiablecredentials.spi.model.statuslist.StatusList2021Credential.STATUS_LIST_CREDENTIAL; import static org.eclipse.edc.iam.verifiablecredentials.spi.model.statuslist.StatusList2021Credential.STATUS_LIST_INDEX; @@ -45,7 +52,7 @@ class StatusList2021RevocationServiceTest { void setup() { clientAndServer = ClientAndServer.startClientAndServer("localhost", getFreePort()); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT)); + .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_INTERMEDIATE)); } @AfterEach @@ -53,11 +60,12 @@ void tearDown() { clientAndServer.stop(); } - @Test - void checkRevocation_whenSubjectIsArray() { + @ParameterizedTest + @ArgumentsSource(ArraySubjectProvider.class) + void checkRevocation_whenSubjectIsArray(String testData) { clientAndServer.reset(); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY)); + .respond(HttpResponse.response().withStatusCode(200).withBody(testData)); var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", Map.of(STATUS_LIST_PURPOSE, "revocation", STATUS_LIST_INDEX, NOT_REVOKED_INDEX, @@ -110,11 +118,12 @@ void checkRevocation_whenCached_valid() { clientAndServer.verify(request(), VerificationTimes.exactly(1)); } - @Test - void getStatusPurposes_whenSingleCredentialStatusRevoked() { + @ParameterizedTest + @ArgumentsSource(SingleSubjectProvider.class) + void getStatusPurposes_whenSingleCredentialStatusRevoked(String testData) { clientAndServer.reset(); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT)); + .respond(HttpResponse.response().withStatusCode(200).withBody(testData)); var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", Map.of(STATUS_LIST_PURPOSE, "revocation", STATUS_LIST_INDEX, REVOKED_INDEX, @@ -124,11 +133,12 @@ void getStatusPurposes_whenSingleCredentialStatusRevoked() { .isEqualTo("revocation"); } - @Test - void getStatusPurposes_whenMultipleCredentialStatusRevoked() { + @ParameterizedTest + @ArgumentsSource(ArraySubjectProvider.class) + void getStatusPurposes_whenMultipleCredentialStatusRevoked(String testData) { clientAndServer.reset(); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY)); + .respond(HttpResponse.response().withStatusCode(200).withBody(testData)); var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", Map.of(STATUS_LIST_PURPOSE, "revocation", STATUS_LIST_INDEX, REVOKED_INDEX, @@ -138,11 +148,12 @@ void getStatusPurposes_whenMultipleCredentialStatusRevoked() { .isEqualTo("revocation"); } - @Test - void getStatusPurpose_whenCredentialStatusNotActive() { + @ParameterizedTest + @ArgumentsSource(SingleSubjectProvider.class) + void getStatusPurpose_whenCredentialStatusNotActive(String testData) { clientAndServer.reset(); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT)); + .respond(HttpResponse.response().withStatusCode(200).withBody(testData)); var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", Map.of(STATUS_LIST_PURPOSE, "revocation", STATUS_LIST_INDEX, NOT_REVOKED_INDEX, @@ -152,15 +163,40 @@ void getStatusPurpose_whenCredentialStatusNotActive() { .isNull(); } - @Test - void getStatusPurpose_whenNoCredentialStatus() { + @ParameterizedTest + @ArgumentsSource(SingleSubjectProvider.class) + void getStatusPurpose_whenNoCredentialStatus(String testData) { clientAndServer.reset(); clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) - .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT)); + .respond(HttpResponse.response().withStatusCode(200).withBody(testData)); var credential = TestFunctions.createCredentialBuilder().build(); assertThat(revocationService.getStatusPurpose(credential)) .isNotNull() .isSucceeded(); } + + private static class SingleSubjectProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of(Named.of("VC (intermediate)", TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_INTERMEDIATE)), + Arguments.of(Named.of("VC 1.1", TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_1_0)), + Arguments.of(Named.of("VC 2.0", TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_2_0)) + + ); + } + } + + private static class ArraySubjectProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of(Named.of("VC 1.1", TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_1_0)), + Arguments.of(Named.of("VC (intermediate)", TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_INTERMEDIATE)), + Arguments.of(Named.of("VC 2.0", TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_2_0)) + + ); + } + } } \ No newline at end of file diff --git a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/TestData.java b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/TestData.java index fc8b2e97fbd..e756127cfec 100644 --- a/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/TestData.java +++ b/extensions/common/iam/verifiable-credentials/src/test/java/org/eclipse/edc/iam/verifiablecredentials/TestData.java @@ -16,7 +16,7 @@ public class TestData { // test data taken from https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#example-example-statuslist2021credential-0 - public static final String STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY = """ + public static final String STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_INTERMEDIATE = """ { "@context": [ "https://www.w3.org/2018/credentials/v1", @@ -37,7 +37,7 @@ public class TestData { } """; - public static final String STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT = """ + public static final String STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_INTERMEDIATE = """ { "@context": [ "https://www.w3.org/2018/credentials/v1", @@ -55,4 +55,85 @@ public class TestData { } } """; + + public static final String STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_2_0 = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "https://example.com/credentials/status/3", + "type": ["VerifiableCredential", "StatusList2021Credential"], + "issuer": "did:example:12345", + "validFrom": "2021-04-05T14:27:40Z", + "credentialSubject": [ + { + "id": "https://example.com/status/3#list", + "type": "StatusList2021", + "https://w3id.org/vc/status-list#statusPurpose": "revocation", + "https://w3id.org/vc/status-list#encodedList": "H4sIAAAAAAAAA+3BIQEAAAACIP+vcKozLEADAAAAAAAAAAAAAAAAAAAAvA0cOP65AEAAAA" + } + ] + } + """; + + public static final String STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_2_0 = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "https://example.com/credentials/status/3", + "type": ["VerifiableCredential", "StatusList2021Credential"], + "issuer": "did:example:12345", + "validFrom": "2021-04-05T14:27:40Z", + "credentialSubject": { + "id": "https://example.com/status/3#list", + "type": "StatusList2021", + "https://w3id.org/vc/status-list#statusPurpose": "revocation", + "https://w3id.org/vc/status-list#encodedList": "H4sIAAAAAAAAA+3BIQEAAAACIP+vcKozLEADAAAAAAAAAAAAAAAAAAAAvA0cOP65AEAAAA" + } + } + """; + + + public static final String STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY_1_0 = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "https://example.com/credentials/status/3", + "type": ["VerifiableCredential", "StatusList2021Credential"], + "issuer": "did:example:12345", + "issuanceDate": "2021-04-05T14:27:40Z", + "credentialSubject": [ + { + "id": "https://example.com/status/3#list", + "type": "StatusList2021", + "https://w3id.org/vc/status-list#statusPurpose": "revocation", + "https://w3id.org/vc/status-list#encodedList": "H4sIAAAAAAAAA+3BIQEAAAACIP+vcKozLEADAAAAAAAAAAAAAAAAAAAAvA0cOP65AEAAAA" + } + ] + } + """; + + public static final String STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT_1_0 = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "https://example.com/credentials/status/3", + "type": ["VerifiableCredential", "StatusList2021Credential"], + "issuer": "did:example:12345", + "issuanceDate": "2021-04-05T14:27:40Z", + "credentialSubject": { + "id": "https://example.com/status/3#list", + "type": "StatusList2021", + "https://w3id.org/vc/status-list#statusPurpose": "revocation", + "https://w3id.org/vc/status-list#encodedList": "H4sIAAAAAAAAA+3BIQEAAAACIP+vcKozLEADAAAAAAAAAAAAAAAAAAAAvA0cOP65AEAAAA" + } + } + """; } diff --git a/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/model/VerifiableCredential.java b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/model/VerifiableCredential.java index 97620bedc93..d97d61cfd5a 100644 --- a/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/model/VerifiableCredential.java +++ b/spi/common/verifiable-credentials-spi/src/main/java/org/eclipse/edc/iam/verifiablecredentials/spi/model/VerifiableCredential.java @@ -71,12 +71,13 @@ public Issuer getIssuer() { return issuer; } - @JsonAlias({ "issued" }) // some credentials like StatusList2021 don't adhere to the spec + @JsonAlias({ "issued", "validFrom" }) // some credentials like StatusList2021 don't adhere to the spec @NotNull public Instant getIssuanceDate() { return issuanceDate; } + @JsonAlias({ "validUntil" }) public Instant getExpirationDate() { return expirationDate; }