From dab85b68437ae190e55412b5620fc4e9c052b1e3 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:34:37 +0200 Subject: [PATCH] chore: align createParticipantContext operation with DR (#445) --- .../did/DidDocumentServiceImpl.java | 1 + .../did/DidDocumentServiceImplTest.java | 1 + .../ParticipantContextEventCoordinator.java | 2 +- ...articipantContextEventCoordinatorTest.java | 35 +++++++-- .../ParticipantContextApiEndToEndTest.java | 77 ++++++++++++++++++- 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/core/identity-hub-did/src/main/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImpl.java b/core/identity-hub-did/src/main/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImpl.java index 0f14dccfa..80759d4c3 100644 --- a/core/identity-hub-did/src/main/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImpl.java +++ b/core/identity-hub-did/src/main/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImpl.java @@ -74,6 +74,7 @@ public ServiceResult store(DidDocument document, String participantId) { .document(document) .did(document.getId()) .participantId(participantId) + .state(DidState.GENERATED.code()) .build(); var result = didResourceStore.save(res); return result.succeeded() ? diff --git a/core/identity-hub-did/src/test/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImplTest.java b/core/identity-hub-did/src/test/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImplTest.java index ea40da204..4ad2bb584 100644 --- a/core/identity-hub-did/src/test/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImplTest.java +++ b/core/identity-hub-did/src/test/java/org/eclipse/edc/identityhub/did/DidDocumentServiceImplTest.java @@ -68,6 +68,7 @@ void store() { var doc = createDidDocument().build(); when(storeMock.save(argThat(dr -> dr.getDocument().equals(doc)))).thenReturn(StoreResult.success()); assertThat(service.store(doc, "test-participant")).isSucceeded(); + verify(storeMock).save(argThat(didResource -> didResource.getState() == DidState.GENERATED.code())); } @Test diff --git a/core/identity-hub-participants/src/main/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinator.java b/core/identity-hub-participants/src/main/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinator.java index e47705295..30eff60a9 100644 --- a/core/identity-hub-participants/src/main/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinator.java +++ b/core/identity-hub-participants/src/main/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinator.java @@ -61,9 +61,9 @@ public void on(EventEnvelope event) { .build(); didDocumentService.store(doc, manifest.getParticipantId()) - .compose(u -> manifest.isActive() ? didDocumentService.publish(doc.getId()) : success()) // adding the keypair event will cause the DidDocumentService to update the DID. .compose(u -> keyPairService.addKeyPair(createdEvent.getParticipantId(), createdEvent.getManifest().getKey(), true)) + .compose(u -> manifest.isActive() ? didDocumentService.publish(doc.getId()) : success()) .onFailure(f -> monitor.warning("%s".formatted(f.getFailureDetail()))); } else { diff --git a/core/identity-hub-participants/src/test/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinatorTest.java b/core/identity-hub-participants/src/test/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinatorTest.java index bf90eef1b..59dae5647 100644 --- a/core/identity-hub-participants/src/test/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinatorTest.java +++ b/core/identity-hub-participants/src/test/java/org/eclipse/edc/identityhub/participantcontext/ParticipantContextEventCoordinatorTest.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -79,37 +80,55 @@ void onParticipantCreated_didDocumentServiceStoreFailure() { } @Test - void onParticipantCreated_didDocumentServicePublishFailure() { + void onParticipantCreated_active_didDocumentServicePublishFailure() { var participantId = "test-id"; when(didDocumentService.store(any(), eq(participantId))).thenReturn(ServiceResult.success()); + when(keyPairService.addKeyPair(eq(participantId), any(), anyBoolean())).thenReturn(ServiceResult.success()); when(didDocumentService.publish(anyString())).thenReturn(ServiceResult.badRequest("foobar")); coordinator.on(envelope(ParticipantContextCreated.Builder.newInstance() .participantId(participantId) - .manifest(createManifest().build()) + .manifest(createManifest().active(true).build()) .build())); verify(didDocumentService).store(any(), eq(participantId)); + verify(keyPairService).addKeyPair(eq(participantId), any(), eq(true)); verify(didDocumentService).publish(anyString()); verify(monitor).warning("foobar"); - verifyNoMoreInteractions(keyPairService, didDocumentService); + verifyNoMoreInteractions(didDocumentService); } @Test - void onParticipantCreated_keyPairServiceFailure() { + void onParticipantCreated_notActive_shouldNotPublish() { var participantId = "test-id"; when(didDocumentService.store(any(), eq(participantId))).thenReturn(ServiceResult.success()); - when(didDocumentService.publish(anyString())).thenReturn(ServiceResult.success()); - when(keyPairService.addKeyPair(eq(participantId), any(), anyBoolean())).thenReturn(ServiceResult.notFound("foobar")); + when(keyPairService.addKeyPair(eq(participantId), any(), anyBoolean())).thenReturn(ServiceResult.success()); coordinator.on(envelope(ParticipantContextCreated.Builder.newInstance() .participantId(participantId) - .manifest(createManifest().build()) + .manifest(createManifest().active(false).build()) + .build())); + + verify(didDocumentService).store(any(), eq(participantId)); + verify(keyPairService).addKeyPair(eq(participantId), any(), eq(true)); + verify(didDocumentService, never()).publish(anyString()); + verifyNoMoreInteractions(didDocumentService); + } + + @Test + void onParticipantCreated_active_whenKeyPairServiceFailure_shouldNotPublish() { + var participantId = "test-id"; + when(didDocumentService.store(any(), eq(participantId))).thenReturn(ServiceResult.success()); + when(keyPairService.addKeyPair(eq(participantId), any(KeyDescriptor.class), anyBoolean())).thenReturn(ServiceResult.notFound("foobar")); + + coordinator.on(envelope(ParticipantContextCreated.Builder.newInstance() + .participantId(participantId) + .manifest(createManifest().active(true).build()) .build())); verify(didDocumentService).store(any(), eq(participantId)); - verify(didDocumentService).publish(eq("did:web:" + participantId)); verify(keyPairService).addKeyPair(eq(participantId), any(), eq(true)); + verify(didDocumentService, never()).publish(eq("did:web:" + participantId)); verify(monitor).warning("foobar"); verifyNoMoreInteractions(keyPairService, didDocumentService); } diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ParticipantContextApiEndToEndTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ParticipantContextApiEndToEndTest.java index 8e591e6f8..37964eba8 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ParticipantContextApiEndToEndTest.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ParticipantContextApiEndToEndTest.java @@ -17,14 +17,20 @@ import io.restassured.http.ContentType; import io.restassured.http.Header; import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.identithub.spi.did.DidConstants; +import org.eclipse.edc.identithub.spi.did.DidDocumentPublisher; +import org.eclipse.edc.identithub.spi.did.DidDocumentPublisherRegistry; import org.eclipse.edc.identithub.spi.did.model.DidResource; +import org.eclipse.edc.identithub.spi.did.model.DidState; import org.eclipse.edc.identithub.spi.did.store.DidResourceStore; +import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource; import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairState; import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextCreated; import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextUpdated; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContextState; +import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubEndToEndExtension; import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubEndToEndTestContext; import org.eclipse.edc.junit.annotations.EndToEndTest; @@ -33,6 +39,7 @@ import org.eclipse.edc.spi.event.EventRouter; import org.eclipse.edc.spi.event.EventSubscriber; import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.Result; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -53,21 +60,29 @@ import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; public class ParticipantContextApiEndToEndTest { abstract static class Tests { @AfterEach - void tearDown(ParticipantContextService pcService) { - // purge all users + void tearDown(ParticipantContextService pcService, DidResourceStore didResourceStore, KeyPairResourceStore keyPairResourceStore) { + // purge all users, dids, keypairs + pcService.query(QuerySpec.max()).getContent() .forEach(pc -> pcService.deleteParticipantContext(pc.getParticipantId()).getContent()); + + didResourceStore.query(QuerySpec.max()).forEach(dr -> didResourceStore.deleteById(dr.getDid()).getContent()); + + keyPairResourceStore.query(QuerySpec.max()).getContent() + .forEach(kpr -> keyPairResourceStore.deleteById(kpr.getId()).getContent()); } @Test @@ -99,7 +114,7 @@ void getUserById_notOwner_expect403(IdentityHubEndToEndTestContext context) { .did("did:web:" + user2) .apiTokenAlias(user2 + "-alias") .build(); - var apiToken2 = context.storeParticipant(user2Context); + context.storeParticipant(user2Context); //user1 attempts to read user2 -> fail context.getIdentityApiEndpoint().baseRequest() @@ -240,6 +255,62 @@ void createNewUser_whenDidAlreadyExists_expect409(IdentityHubEndToEndTestContext verify(subscriber, never()).on(argThat(env -> ((ParticipantContextCreated) env.getPayload()).getParticipantId().equals(manifest.getParticipantId()))); } + + @Test + void createNewUser_andNotActive_shouldNotPublishDid(IdentityHubEndToEndTestContext context, DidResourceStore didResourceStore, DidDocumentPublisherRegistry publisherRegistry) { + var apikey = context.createSuperUser(); + + var mockedPublisher = mock(DidDocumentPublisher.class); + when(mockedPublisher.publish(anyString())).thenReturn(Result.success()); + when(mockedPublisher.unpublish(anyString())).thenReturn(Result.success()); + publisherRegistry.addPublisher(DidConstants.DID_WEB_METHOD, mockedPublisher); + var manifest = context.createNewParticipant() + .active(false) + .build(); + + context.getIdentityApiEndpoint().baseRequest() + .header(new Header("x-api-key", apikey)) + .contentType(ContentType.JSON) + .body(manifest) + .post("/v1alpha/participants/") + .then() + .log().ifError() + .statusCode(anyOf(equalTo(200), equalTo(204))) + .body(notNullValue()); + + assertThat(context.getKeyPairsForParticipant(manifest.getParticipantId())).hasSize(1).allMatch(KeyPairResource::isDefaultPair); + assertThat(context.getDidForParticipant(manifest.getParticipantId())).hasSize(1) + .allSatisfy(dd -> assertThat(dd.getVerificationMethod()).hasSize(1)); + var storedDidResource = didResourceStore.findById(manifest.getDid()); + assertThat(storedDidResource.getState()).withFailMessage("Expected DID resource state %s, got %s", DidState.GENERATED, storedDidResource.getStateAsEnum()).isEqualTo(DidState.GENERATED.code()); + verify(mockedPublisher, never()).publish(manifest.getDid()); + } + + @Test + void createNewUser_andActive_shouldAutoPublish(IdentityHubEndToEndTestContext context, DidResourceStore didResourceStore) { + var apikey = context.createSuperUser(); + + var manifest = context.createNewParticipant() + .active(true) + .build(); + + context.getIdentityApiEndpoint().baseRequest() + .header(new Header("x-api-key", apikey)) + .contentType(ContentType.JSON) + .body(manifest) + .post("/v1alpha/participants/") + .then() + .log().ifError() + .statusCode(anyOf(equalTo(200), equalTo(204))) + .body(notNullValue()); + + assertThat(context.getKeyPairsForParticipant(manifest.getParticipantId())).hasSize(1).allMatch(KeyPairResource::isDefaultPair); + assertThat(context.getDidForParticipant(manifest.getParticipantId())).hasSize(1) + .allSatisfy(dd -> assertThat(dd.getVerificationMethod()).hasSize(1)); + var storedDidResource = didResourceStore.findById(manifest.getDid()); + assertThat(storedDidResource.getState()).withFailMessage("Expected DID resource state %s, got %s", DidState.PUBLISHED, storedDidResource.getStateAsEnum()).isEqualTo(DidState.PUBLISHED.code()); + } + @Test void activateParticipant_principalIsSuperser(IdentityHubEndToEndTestContext context, ParticipantContextService participantContextService, EventRouter router) { var superUserKey = context.createSuperUser();