Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use events to decouple services #245

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/identity-hub-did/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ dependencies {
api(project(":spi:identity-hub-did-spi"))

implementation(libs.edc.core.connector) // for the reflection-based query resolver

implementation(libs.edc.common.crypto)

testImplementation(libs.edc.junit)
testImplementation(libs.edc.ext.jsonld)
testImplementation(testFixtures(project(":spi:identity-hub-spi")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

import org.eclipse.edc.iam.did.spi.document.DidDocument;
import org.eclipse.edc.iam.did.spi.document.Service;
import org.eclipse.edc.iam.did.spi.document.VerificationMethod;
import org.eclipse.edc.identithub.did.spi.DidDocumentPublisherRegistry;
import org.eclipse.edc.identithub.did.spi.DidDocumentService;
import org.eclipse.edc.identithub.did.spi.model.DidResource;
import org.eclipse.edc.identithub.did.spi.model.DidState;
import org.eclipse.edc.identithub.did.spi.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.events.keypair.KeyPairAdded;
import org.eclipse.edc.identityhub.spi.events.keypair.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextCreated;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextDeleted;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextUpdated;
import org.eclipse.edc.security.token.jwt.CryptoConverter;
import org.eclipse.edc.spi.event.Event;
import org.eclipse.edc.spi.event.EventEnvelope;
import org.eclipse.edc.spi.event.EventSubscriber;
Expand All @@ -32,8 +36,12 @@
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.AbstractResult;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.spi.security.KeyParserRegistry;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Collection;
import java.util.stream.Collectors;

Expand All @@ -49,12 +57,14 @@ public class DidDocumentServiceImpl implements DidDocumentService, EventSubscrib
private final DidResourceStore didResourceStore;
private final DidDocumentPublisherRegistry registry;
private final Monitor monitor;
private final KeyParserRegistry keyParserRegistry;

public DidDocumentServiceImpl(TransactionContext transactionContext, DidResourceStore didResourceStore, DidDocumentPublisherRegistry registry, Monitor monitor) {
public DidDocumentServiceImpl(TransactionContext transactionContext, DidResourceStore didResourceStore, DidDocumentPublisherRegistry registry, Monitor monitor, KeyParserRegistry keyParserRegistry) {
this.transactionContext = transactionContext;
this.didResourceStore = didResourceStore;
this.registry = registry;
this.monitor = monitor;
this.keyParserRegistry = keyParserRegistry;
}

@Override
Expand Down Expand Up @@ -209,11 +219,61 @@ public <E extends Event> void on(EventEnvelope<E> eventEnvelope) {
updated(event);
} else if (payload instanceof ParticipantContextDeleted event) {
deleted(event);
} else if (payload instanceof KeyPairAdded event) {
keypairAdded(event);
} else if (payload instanceof KeyPairRevoked event) {
keypairRevoked(event);
} else {
monitor.warning("KeyPairServiceImpl Received an event with unexpected payload type: %s".formatted(payload.getClass()));
}
}

private void keypairRevoked(KeyPairRevoked event) {
var diddocs = findByParticipantId(event.getParticipantId());
var keyId = event.getKeyId();

var errors = diddocs.stream()
.peek(didResource -> didResource.getDocument().getVerificationMethod().removeIf(vm -> vm.getId().equals(keyId)))
.map(didResourceStore::update)
.filter(StoreResult::failed)
.map(AbstractResult::getFailureDetail)
.collect(Collectors.joining(","));

if (!errors.isEmpty()) {
monitor.warning("Updating DID documents after adding a KeyPair failed: %s".formatted(errors));
}
}

private void keypairAdded(KeyPairAdded event) {
var diddocs = findByParticipantId(event.getParticipantId());
paullatzelsperger marked this conversation as resolved.
Show resolved Hide resolved
var serialized = event.getPublicKeySerialized();
var publicKey = keyParserRegistry.parse(serialized);

if (publicKey.failed()) {
monitor.warning("Error adding KeyPair '%s' to DID Document of participant '%s': %s".formatted(event.getKeyId(), event.getParticipantId(), publicKey.getFailureDetail()));
return;
}

var jwk = CryptoConverter.createJwk(new KeyPair((PublicKey) publicKey.getContent(), null));


paullatzelsperger marked this conversation as resolved.
Show resolved Hide resolved
var errors = diddocs.stream()
.peek(dd -> dd.getDocument().getVerificationMethod().add(VerificationMethod.Builder.newInstance()
.id(event.getKeyId())
.publicKeyJwk(jwk.toJSONObject())
.controller(dd.getDocument().getId())
.build()))
.map(didResourceStore::update)
.filter(StoreResult::failed)
.map(AbstractResult::getFailureDetail)
.collect(Collectors.joining(","));

if (!errors.isEmpty()) {
monitor.warning("Updating DID documents after adding a KeyPair failed: %s".formatted(errors));
}

}

private void updated(ParticipantContextUpdated event) {
var newState = event.getNewState();
var forParticipant = findByParticipantId(event.getParticipantId());
Expand Down Expand Up @@ -259,9 +319,6 @@ private void created(ParticipantContextCreated event) {
.id(manifest.getDid())
.service(manifest.getServiceEndpoints().stream().toList())
// updating and adding a verification method happens as a result of the KeyPairAddedEvent
// .verificationMethod(List.of(VerificationMethod.Builder.newInstance()
// .publicKeyJwk(publicKey.toJSONObject())
// .build()))
.build();
store(doc, manifest.getParticipantId())
.compose(u -> manifest.isActive() ? publish(doc.getId()) : success())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import org.eclipse.edc.identithub.did.spi.DidDocumentPublisherRegistry;
import org.eclipse.edc.identithub.did.spi.DidDocumentService;
import org.eclipse.edc.identithub.did.spi.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.events.keypair.KeyPairAdded;
import org.eclipse.edc.identityhub.spi.events.keypair.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextCreated;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextDeleted;
import org.eclipse.edc.identityhub.spi.events.participant.ParticipantContextUpdated;
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.spi.event.EventRouter;
import org.eclipse.edc.spi.security.KeyParserRegistry;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.transaction.spi.TransactionContext;
Expand All @@ -43,6 +46,9 @@ public class DidServicesExtension implements ServiceExtension {

private DidDocumentPublisherRegistry didPublisherRegistry;

@Inject
private KeyParserRegistry keyParserRegistry;

@Override
public String name() {
return NAME;
Expand All @@ -58,10 +64,12 @@ public DidDocumentPublisherRegistry getDidPublisherRegistry() {

@Provider
public DidDocumentService createDidDocumentService(ServiceExtensionContext context) {
var service = new DidDocumentServiceImpl(transactionContext, didResourceStore, getDidPublisherRegistry(), context.getMonitor());
var service = new DidDocumentServiceImpl(transactionContext, didResourceStore, getDidPublisherRegistry(), context.getMonitor(), keyParserRegistry);
eventRouter.registerSync(ParticipantContextCreated.class, service);
eventRouter.registerSync(ParticipantContextUpdated.class, service);
eventRouter.registerSync(ParticipantContextDeleted.class, service);
eventRouter.registerSync(KeyPairAdded.class, service);
eventRouter.registerSync(KeyPairRevoked.class, service);
return service;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

package org.eclipse.edc.identityhub.did;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.edc.connector.core.security.KeyParserRegistryImpl;
import org.eclipse.edc.connector.core.security.keyparsers.JwkParser;
import org.eclipse.edc.connector.core.security.keyparsers.PemParser;
import org.eclipse.edc.iam.did.spi.document.DidDocument;
import org.eclipse.edc.iam.did.spi.document.Service;
import org.eclipse.edc.iam.did.spi.document.VerificationMethod;
Expand Down Expand Up @@ -53,7 +57,10 @@ void setUp() {
var trx = new NoopTransactionContext();
when(publisherRegistry.getPublisher(startsWith("did:web:"))).thenReturn(publisherMock);

service = new DidDocumentServiceImpl(trx, storeMock, publisherRegistry, mock());
var registry = new KeyParserRegistryImpl();
registry.register(new JwkParser(new ObjectMapper(), mock()));
registry.register(new PemParser(mock()));
service = new DidDocumentServiceImpl(trx, storeMock, publisherRegistry, mock(), registry);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public KeyPairEventListenerImpl(Clock clock, EventRouter eventRouter) {
public void added(KeyPairResource keyPair) {
var event = KeyPairAdded.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyId(keyPair.getId())
.publicKey(keyPair.getSerializedPublicKey())
.build();
publish(event);
}
Expand All @@ -47,7 +48,7 @@ public void added(KeyPairResource keyPair) {
public void revoked(KeyPairResource keyPair) {
var event = KeyPairRevoked.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyId(keyPair.getId())
.build();
publish(event);
}
Expand All @@ -56,7 +57,7 @@ public void revoked(KeyPairResource keyPair) {
public void rotated(KeyPairResource keyPair) {
var event = KeyPairRotated.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyId(keyPair.getId())
.build();
publish(event);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ void addKeyPair() {
.body(notNullValue());
verify(subscriber).on(argThat(env -> {
var evt = (KeyPairAdded) env.getPayload();
return evt.getParticipantId().equals(user1) && evt.getKeyPairResourceId().equals(keyDesc.getKeyId());
return evt.getParticipantId().equals(user1) && evt.getKeyId().equals(keyDesc.getKeyId());
}));
}

Expand Down Expand Up @@ -191,7 +191,7 @@ void addKeyPair_notAuthorized() {

verify(subscriber, never()).on(argThat(env -> {
if (env.getPayload() instanceof KeyPairAdded evt) {
return evt.getKeyPairResourceId().equals(keyDesc.getKeyId());
return evt.getKeyId().equals(keyDesc.getKeyId());
}
return false;
}));
Expand Down Expand Up @@ -230,7 +230,7 @@ void rotate() {
// verify that the correct "added" event fired
verify(subscriber).on(argThat(env -> {
if (env.getPayload() instanceof KeyPairAdded evt) {
return evt.getKeyPairResourceId().equals(keyDesc.getKeyId());
return evt.getKeyId().equals(keyDesc.getKeyId());
}
return false;
}));
Expand Down Expand Up @@ -264,7 +264,7 @@ void rotate_notAuthorized() {
// make sure that the event to add the _new_ keypair was never fired
verify(subscriber, never()).on(argThat(env -> {
if (env.getPayload() instanceof KeyPairRotated evt) {
return evt.getParticipantId().equals(user1) && evt.getKeyPairResourceId().equals(keyDesc.getKeyId());
return evt.getParticipantId().equals(user1) && evt.getKeyId().equals(keyDesc.getKeyId());
}
return false;
}));
Expand All @@ -286,6 +286,9 @@ void revoke() {
.log().ifValidationFails()
.statusCode(204)
.body(notNullValue());

assertThat(getDidForParticipant(user1)).hasSize(1)
.allSatisfy(dd -> assertThat(dd.getVerificationMethod()).noneMatch(vm -> vm.getId().equals(keyId)));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ void createNewUser_principalIsAdmin() {
verify(subscriber).on(argThat(env -> ((ParticipantContextCreated) env.getPayload()).getParticipantId().equals(manifest.getParticipantId())));

assertThat(getKeyPairsForParticipant(manifest)).hasSize(1);
assertThat(getDidForParticipant(manifest.getParticipantId())).hasSize(1);
assertThat(getDidForParticipant(manifest.getParticipantId())).hasSize(1)
.allSatisfy(dd -> assertThat(dd.getVerificationMethod()).hasSize(1));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@
*/
@JsonDeserialize(builder = KeyPairAdded.Builder.class)
public class KeyPairAdded extends KeyPairEvent {
private String publicKeySerialized;

@Override
public String name() {
return "keypair.added";
}

public String getPublicKeySerialized() {
return publicKeySerialized;
}

@JsonPOJOBuilder(withPrefix = "")
public static class Builder extends KeyPairEvent.Builder<KeyPairAdded, Builder> {

Expand All @@ -40,6 +46,11 @@ public KeyPairAdded.Builder self() {
return this;
}

public Builder publicKey(String publicKey) {
event.publicKeySerialized = publicKey;
return this;
}

@JsonCreator
public static Builder newInstance() {
return new KeyPairAdded.Builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
*/
public abstract class KeyPairEvent extends Event {
protected String participantId;
protected String keyPairResourceId;
protected String keyId;

/**
* The ID of the {@link org.eclipse.edc.identityhub.spi.model.KeyPairResource}
* The ID of the Key stored in the {@link org.eclipse.edc.identityhub.spi.model.KeyPairResource}
*/
public String getKeyPairResourceId() {
return keyPairResourceId;
public String getKeyId() {
return keyId;
}

/**
Expand All @@ -54,8 +54,8 @@ public B participantId(String assetId) {
return self();
}

public B keyPairResourceId(String keyPairResourceId) {
event.keyPairResourceId = keyPairResourceId;
public B keyId(String keyId) {
event.keyId = keyId;
return self();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class KeyPairAddedTest {

@Test
void verify_serDes() throws JsonProcessingException {
var evt = KeyPairAdded.Builder.newInstance().keyPairResourceId("resource-id")
var evt = KeyPairAdded.Builder.newInstance().keyId("resource-id")
.participantId("participant-id")
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class KeyPairRevokedTest {

@Test
void verify_serDes() throws JsonProcessingException {
var evt = KeyPairRevoked.Builder.newInstance().keyPairResourceId("resource-id")
var evt = KeyPairRevoked.Builder.newInstance().keyId("resource-id")
.participantId("participant-id")
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class KeyPairRotatedTest {

@Test
void verify_serDes() throws JsonProcessingException {
var evt = KeyPairRotated.Builder.newInstance().keyPairResourceId("resource-id")
var evt = KeyPairRotated.Builder.newInstance().keyId("resource-id")
.participantId("participant-id")
.build();

Expand Down
Loading