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

refactor(identityHub): client and verifier abstraction over the VC format #76

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions client-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ dependencies {
annotationProcessor(libs.picocli.codegen)

implementation(project(":core:identity-hub-client"))
implementation(project(":extensions:credentials:identity-hub-credentials-jwt"))
implementation(project(":extensions:identity-hub-verifier-jwt"))


implementation(edc.spi.identity.did)
implementation(libs.jackson.databind)
implementation(libs.okhttp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import picocli.CommandLine;
import picocli.CommandLine.Command;
Expand Down Expand Up @@ -69,7 +70,11 @@ public Integer call() throws Exception {
throw new CliException("Error while signing Verifiable Credential", e);
}

command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, signedJwt);
var result = command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, new JwtCredentialEnvelope(signedJwt));

if (result.failed()) {
throw new CliException("Error while adding the Verifiable credential to the Identity Hub");
}

out.println("Verifiable Credential added successfully");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import okhttp3.OkHttpClient;
import org.eclipse.edc.identityhub.client.IdentityHubClientImpl;
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService;
import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer;
import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistryImpl;
import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService;
import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.spi.monitor.ConsoleMonitor;
import picocli.CommandLine;
import picocli.CommandLine.Command;
Expand Down Expand Up @@ -58,7 +60,11 @@ private void init() {
var okHttpClient = new OkHttpClient.Builder().build();
var objectMapper = new ObjectMapper();
var monitor = new ConsoleMonitor();
identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor);

var registry = new CredentialEnvelopeTransformerRegistryImpl();
registry.register(new JwtCredentialEnvelopeTransformer(objectMapper));

identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor, registry);
verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(objectMapper, monitor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.spi.result.Result;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.ParentCommand;
import picocli.CommandLine.Spec;

import java.text.ParseException;
import java.util.Map;
import java.util.concurrent.Callable;

import static java.util.stream.Collectors.toList;
Expand All @@ -48,18 +46,12 @@ public Integer call() throws Exception {
throw new CliException("Failed to get verifiable credentials: " + result.getFailureDetail());
}
var vcs = result.getContent().stream()
.map(this::getClaims)
.map(envelope -> envelope.toVerifiableCredential(MAPPER))
.map(Result::getContent)
.collect(toList());
MAPPER.writeValue(out, vcs);
out.println();
return 0;
}

private Map<String, Object> getClaims(SignedJWT jwt) {
try {
return jwt.getJWTClaimsSet().getClaims();
} catch (ParseException e) {
throw new CliException("Error while reading Verifiable Credentials claims", e);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.iam.did.spi.key.PrivateKeyWrapper;
import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper;
import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService;
import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService;
import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.spi.monitor.Monitor;

import java.util.Map;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
import org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.edc.spi.monitor.Monitor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -38,7 +38,7 @@
import static org.eclipse.edc.identityhub.cli.CliTestUtils.createVerifiableCredential;
import static org.eclipse.edc.identityhub.cli.CliTestUtils.signVerifiableCredential;
import static org.eclipse.edc.identityhub.cli.CliTestUtils.verifyVerifiableCredentialSignature;
import static org.eclipse.edc.identityhub.spi.credentials.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY;
import static org.eclipse.edc.identityhub.verifier.jwt.VerifiableCredentialsJwtService.VERIFIABLE_CREDENTIALS_KEY;
import static org.eclipse.edc.spi.response.StatusResult.success;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
Expand All @@ -51,9 +51,9 @@ class VerifiableCredentialsCommandTest {

private static final ObjectMapper MAPPER = new ObjectMapper();
private static final VerifiableCredential VC1 = createVerifiableCredential();
private static final SignedJWT SIGNED_VC1 = signVerifiableCredential(VC1);
private static final JwtCredentialEnvelope SIGNED_VC1 = new JwtCredentialEnvelope(signVerifiableCredential(VC1));
private static final VerifiableCredential VC2 = createVerifiableCredential();
private static final SignedJWT SIGNED_VC2 = signVerifiableCredential(VC2);
private static final JwtCredentialEnvelope SIGNED_VC2 = new JwtCredentialEnvelope(signVerifiableCredential(VC2));
private static final String HUB_URL = "http://some.test.url";

private final IdentityHubCli app = new IdentityHubCli();
Expand Down Expand Up @@ -106,7 +106,7 @@ void list() throws Exception {
var claims = MAPPER.readValue(outContent, new TypeReference<List<Map<String, Object>>>() {
});
var vcs = claims.stream()
.map(c -> MAPPER.convertValue(c.get(VERIFIABLE_CREDENTIALS_KEY), VerifiableCredential.class))
.map(c -> MAPPER.convertValue(c, VerifiableCredential.class))
.collect(Collectors.toList());

assertThat(vcs)
Expand All @@ -118,7 +118,7 @@ void list() throws Exception {
void add() throws Exception {
// arrange
var json = MAPPER.writeValueAsString(VC1);
var vcArgCaptor = ArgumentCaptor.forClass(SignedJWT.class);
var vcArgCaptor = ArgumentCaptor.forClass(JwtCredentialEnvelope.class);
doReturn(success()).when(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), vcArgCaptor.capture());

// act
Expand All @@ -131,8 +131,8 @@ void add() throws Exception {
assertThat(outContent).isEqualTo("Verifiable Credential added successfully" + System.lineSeparator());
assertThat(errContent).isEmpty();

verify(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), isA(SignedJWT.class));
var signedJwt = vcArgCaptor.getValue();
verify(app.identityHubClient).addVerifiableCredential(eq(app.hubUrl), isA(JwtCredentialEnvelope.class));
var signedJwt = vcArgCaptor.getValue().getJwtVerifiableCredentials();

// assert JWT signature
assertThat(verifyVerifiableCredentialSignature(signedJwt)).isTrue();
Expand Down
3 changes: 3 additions & 0 deletions core/identity-hub-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ dependencies {

testImplementation(project(":core:identity-hub"))
testImplementation(project(":extensions:identity-hub-api"))
testImplementation(project(":extensions:credentials:identity-hub-credentials-jwt"))
testImplementation(testFixtures(project(":spi:identity-hub-spi")))
testImplementation(edc.core.junit)
testImplementation(edc.core.identity.did)

}

publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope;
import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry;
import org.eclipse.edc.identityhub.spi.model.Descriptor;
import org.eclipse.edc.identityhub.spi.model.MessageRequestObject;
import org.eclipse.edc.identityhub.spi.model.MessageResponseObject;
Expand All @@ -38,15 +39,13 @@
import org.eclipse.edc.spi.result.Result;

import java.io.IOException;
import java.text.ParseException;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.edc.identityhub.spi.model.WebNodeInterfaceMethod.COLLECTIONS_QUERY;
import static org.eclipse.edc.identityhub.spi.model.WebNodeInterfaceMethod.COLLECTIONS_WRITE;

Expand All @@ -56,10 +55,13 @@ public class IdentityHubClientImpl implements IdentityHubClient {
private final ObjectMapper objectMapper;
private final Monitor monitor;

public IdentityHubClientImpl(OkHttpClient httpClient, ObjectMapper objectMapper, Monitor monitor) {
private final CredentialEnvelopeTransformerRegistry transformerRegistry;

public IdentityHubClientImpl(OkHttpClient httpClient, ObjectMapper objectMapper, Monitor monitor, CredentialEnvelopeTransformerRegistry transformerRegistry) {
this.httpClient = httpClient;
this.objectMapper = objectMapper;
this.monitor = monitor;
this.transformerRegistry = transformerRegistry;
}

private static Descriptor.Builder defaultDescriptor(String method) {
Expand Down Expand Up @@ -89,7 +91,7 @@ public StatusResult<JsonNode> getSelfDescription(String hubBaseUrl) {
}

@Override
public StatusResult<Collection<SignedJWT>> getVerifiableCredentials(String hubBaseUrl) {
public StatusResult<Collection<CredentialEnvelope>> getVerifiableCredentials(String hubBaseUrl) {
var descriptor = defaultDescriptor(COLLECTIONS_QUERY.getName()).build();
try (var response = httpClient.newCall(
new Request.Builder()
Expand Down Expand Up @@ -124,16 +126,26 @@ public StatusResult<Collection<SignedJWT>> getVerifiableCredentials(String hubBa
}

@Override
public StatusResult<Void> addVerifiableCredential(String hubBaseUrl, SignedJWT verifiableCredential) {
var payload = verifiableCredential.serialize().getBytes(UTF_8);
public StatusResult<Void> addVerifiableCredential(String hubBaseUrl, CredentialEnvelope verifiableCredential) {

var transformer = transformerRegistry.resolve(verifiableCredential.format());
if (transformer == null) {
return StatusResult.failure(ResponseStatus.FATAL_ERROR, format("Transformer not found for format %s", verifiableCredential.format()));
}
Result<byte[]> result = transformer.serialize(verifiableCredential);
wolf4ood marked this conversation as resolved.
Show resolved Hide resolved

if (result.failed()) {
return StatusResult.failure(ResponseStatus.FATAL_ERROR, result.getFailureDetail());
}

var descriptor = defaultDescriptor(COLLECTIONS_WRITE.getName())
.recordId(UUID.randomUUID().toString())
.dataFormat(DATA_FORMAT)
.dateCreated(Instant.now().getEpochSecond()) // TODO: this should be passed from input
.build();
try (var response = httpClient.newCall(new Request.Builder()
.url(hubBaseUrl)
.post(buildRequestBody(descriptor, payload))
.post(buildRequestBody(descriptor, result.getContent()))
.build())
.execute()) {
if (response.code() != 200) {
Expand Down Expand Up @@ -165,20 +177,14 @@ public StatusResult<Void> addVerifiableCredential(String hubBaseUrl, SignedJWT v
}
}

private Result<SignedJWT> parse(Object entry) {
try {
var record = objectMapper.convertValue(entry, Record.class);
if (DATA_FORMAT.equalsIgnoreCase(record.getDataFormat())) {
var jwt = new String(record.getData());
return Result.success(SignedJWT.parse(jwt));
} else {
return Result.failure(format("Expected dataFormat %s found %s", DATA_FORMAT, record.getDataFormat()));
}
private Result<CredentialEnvelope> parse(Object entry) {
var record = objectMapper.convertValue(entry, Record.class);
var t = transformerRegistry.resolve(record.getDataFormat());

} catch (ParseException e) {
monitor.warning("Could not parse JWT", e);
return Result.failure(e.getMessage());
if (t == null) {
return Result.failure(format("Transformer not found for format %s", record.getDataFormat()));
}
return t.parse(record.getData());
}

private RequestBody buildRequestBody(Descriptor descriptor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
package org.eclipse.edc.identityhub.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.identityhub.client.spi.IdentityHubClient;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelope;
import org.eclipse.edc.identityhub.credentials.jwt.JwtCredentialEnvelopeTransformer;
import org.eclipse.edc.identityhub.spi.credentials.model.CredentialEnvelope;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import org.eclipse.edc.identityhub.spi.credentials.transformer.CredentialEnvelopeTransformerRegistry;
import org.eclipse.edc.junit.extensions.EdcExtension;
import org.eclipse.edc.junit.testfixtures.TestUtils;
import org.eclipse.edc.spi.monitor.Monitor;
Expand All @@ -31,7 +34,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.buildSignedJwt;
import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(EdcExtension.class)
class IdentityHubClientImplIntegrationTest {
Expand All @@ -42,12 +47,14 @@ class IdentityHubClientImplIntegrationTest {
.credentialSubject(Map.of("foo", "bar"))
.build();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final CredentialEnvelopeTransformerRegistry registry = mock(CredentialEnvelopeTransformerRegistry.class);
private IdentityHubClient client;

@BeforeEach
void setUp() {
var okHttpClient = TestUtils.testOkHttpClient();
client = new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class));
when(registry.resolve(any())).thenReturn(new JwtCredentialEnvelopeTransformer(OBJECT_MAPPER));
client = new IdentityHubClientImpl(okHttpClient, OBJECT_MAPPER, mock(Monitor.class), registry);
}

@Test
Expand All @@ -62,16 +69,17 @@ void getSelfDescription() {
void addAndQueryVerifiableCredentials() {
var jws = buildSignedJwt(VERIFIABLE_CREDENTIAL, "http://test.url", "http://some.test.url", generateEcKey());

addVerifiableCredential(jws);
getVerifiableCredential(jws);
var jwsEnvelope = new JwtCredentialEnvelope(jws);
addVerifiableCredential(jwsEnvelope);
getVerifiableCredential(jwsEnvelope);
}

private void addVerifiableCredential(SignedJWT jws) {
private void addVerifiableCredential(CredentialEnvelope jws) {
var statusResult = client.addVerifiableCredential(API_URL, jws);
assertThat(statusResult.succeeded()).isTrue();
}

private void getVerifiableCredential(SignedJWT jws) {
private void getVerifiableCredential(CredentialEnvelope jws) {
var statusResult = client.getVerifiableCredentials(API_URL);
assertThat(statusResult.succeeded()).isTrue();
assertThat(statusResult.getContent()).usingRecursiveFieldByFieldElementComparator().contains(jws);
Expand Down
Loading