diff --git a/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationService.java b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationService.java index 74f2cbe6dd7..ce0c2e6cf4f 100644 --- a/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationService.java +++ b/extensions/common/iam/verifiable-credentials/src/main/java/org/eclipse/edc/iam/verifiablecredentials/StatusList2021RevocationService.java @@ -42,7 +42,9 @@ public class StatusList2021RevocationService implements RevocationListService { private final Cache cache; public StatusList2021RevocationService(ObjectMapper objectMapper, long cacheValidity) { - this.objectMapper = objectMapper.copy().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // let's make sure this is disabled, because the "@context" would cause problems + this.objectMapper = objectMapper.copy() + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) // technically, credential subjects and credential status can be objects AND Arrays + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // let's make sure this is disabled, because the "@context" would cause problems cache = new Cache<>(this::updateCredential, cacheValidity); } 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 40c6aa4c00c..56a86b09687 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 @@ -18,6 +18,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.eclipse.edc.iam.verifiablecredentials.spi.TestFunctions; 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.Test; import org.mockserver.integration.ClientAndServer; @@ -44,9 +45,28 @@ 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)); + .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT)); } + @AfterEach + void tearDown() { + clientAndServer.stop(); + } + + @Test + void checkRevocation_shenSubjectIsArray() { + clientAndServer.reset(); + clientAndServer.when(request().withMethod("GET").withPath("/credentials/status/3")) + .respond(HttpResponse.response().withStatusCode(200).withBody(TestData.STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY)); + var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", + Map.of(STATUS_LIST_PURPOSE, "revocation", + STATUS_LIST_INDEX, NOT_REVOKED_INDEX, + STATUS_LIST_CREDENTIAL, "http://localhost:%d/credentials/status/3".formatted(clientAndServer.getPort())))) + .build(); + assertThat(revocationService.checkValidity(credential)).isSucceeded(); + } + + @Test void checkRevocation_whenNotCached_valid() { var credential = TestFunctions.createCredentialBuilder().credentialStatus(new CredentialStatus("test-id", "StatusList2021", 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 d1cef1cf4ce..8eff30b3441 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 = """ + public static final String STATUS_LIST_CREDENTIAL_SUBJECT_IS_ARRAY = """ { "@context": [ "https://www.w3.org/2018/credentials/v1", @@ -26,12 +26,33 @@ public class TestData { "type": ["VerifiableCredential", "StatusList2021Credential"], "issuer": "did:example:12345", "issued": "2021-04-05T14:27:40Z", - "credentialSubject": [{ + "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-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA" - }] + } + ] + } + """; + + public static final String STATUS_LIST_CREDENTIAL_SINGLE_SUBJECT = """ + { + "@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", + "issued": "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-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA" + } } """; }