Skip to content

Commit

Permalink
refactor(identityHub): client and verifier abstraction over the VC fo…
Browse files Browse the repository at this point in the history
…rmat (#76)

* feat(IdentityHub): refactor client and verifier for handling VC in
multiple format

* fix after review

* fix after review
  • Loading branch information
wolf4ood authored Jan 3, 2023
1 parent 2107838 commit d44f0d5
Show file tree
Hide file tree
Showing 55 changed files with 890 additions and 308 deletions.
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);

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

0 comments on commit d44f0d5

Please sign in to comment.