Skip to content

Commit

Permalink
feat(cli): resolve the enrollment url from DID (#15)
Browse files Browse the repository at this point in the history
* CLI: Resolve the enrollment url from DID  (#16)

* removed dead code

* Avoid use of Optional

Co-authored-by: Izabela Kulakowska <ikulakowska@microsoft.com>
  • Loading branch information
algattik and Izzzu authored Jul 28, 2022
1 parent daa4148 commit 0b39c0b
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/actions/gradle-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ runs:
with:
repository: eclipse-dataspaceconnector/DataSpaceConnector
path: DataSpaceConnector
ref: 9085e5e71e07b1e1eef05248dbff2c8d6b911e27
ref: 3ff940b720f44826df28e893fb31344eb6faacef

# Install Java and cache MVD Gradle build.
- uses: actions/setup-java@v2
Expand Down
6 changes: 5 additions & 1 deletion client-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ For example, to list dataspace participants:

```
java -jar client-cli/build/libs/registration-service-cli.jar \
-s=http://localhost:8182/authority \
-d=did:web:dataspaceauthoritydid \
-c=did:web:clientdid
-k=file.pem
participants list
```

More about available did:web formats: [https://w3c-ccg.github.io/did-method-web/#example-example-web-method-dids](Web DID method specification).

The client can also be run from a local Maven repository:

```
Expand Down
7 changes: 7 additions & 0 deletions client-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ val jupiterVersion: String by project
val assertj: String by project
val mockitoVersion: String by project
val faker: String by project
val edcGroup: String by project
val edcVersion: String by project
val okHttp: String by project

dependencies {
api("info.picocli:picocli:4.6.3")
Expand All @@ -19,10 +22,14 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")

testImplementation(testFixtures(project(":rest-client")))
implementation("${edcGroup}:identity-did-web:${edcVersion}")
implementation("${edcGroup}:common-util:${edcVersion}")
implementation("com.squareup.okhttp3:okhttp:${okHttp}")
testImplementation("org.assertj:assertj-core:${assertj}")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("com.github.javafaker:javafaker:${faker}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${jupiterVersion}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${jupiterVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${jupiterVersion}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,52 @@

package org.eclipse.dataspaceconnector.registration.cli;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import org.eclipse.dataspaceconnector.iam.did.web.resolution.WebDidResolver;
import org.eclipse.dataspaceconnector.registration.client.api.RegistryApi;
import org.eclipse.dataspaceconnector.spi.monitor.ConsoleMonitor;
import org.jetbrains.annotations.NotNull;
import picocli.CommandLine;
import picocli.CommandLine.Command;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;

import static org.eclipse.dataspaceconnector.registration.cli.ClientUtils.createApiClient;

@Command(name = "registration-service-cli", mixinStandardHelpOptions = true,
description = "Client utility for MVD registration service.",
subcommands = {
ParticipantsCommand.class
})
public class RegistrationServiceCli {
@CommandLine.Option(names = "-s", required = true, description = "Registration service URL", defaultValue = "http://localhost:8182/authority")

private static final ObjectMapper MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

@Deprecated
@CommandLine.Option(names = "-s", description = "Registration service URL. Deprecated. Use -d instead.", defaultValue = "http://localhost:8182/authority")
String service;

@CommandLine.Option(names = "-d", required = true, description = "Client DID")
@CommandLine.Option(names = { "-d", "--dataspace-did" }, description = "Dataspace Authority DID.", defaultValue = "")
String dataspaceDid;

@CommandLine.Option(names = { "-c", "--client-did" }, required = true, description = "Client DID.")
String clientDid;

@CommandLine.Option(names = "-k", required = true, description = "File containing the private key in PEM format")
@CommandLine.Option(names = { "-k", "--private-key" }, required = true, description = "File containing the private key in PEM format")
Path privateKeyFile;

@CommandLine.Option(names = "--http-scheme", description = "Flag to create DID URLs with http instead of https scheme. Used for testing purposes.")
boolean useHttpScheme;

RegistryApi registryApiClient;

private final ConsoleMonitor monitor = new ConsoleMonitor();

public static void main(String... args) {
CommandLine commandLine = getCommandLine();
var exitCode = commandLine.execute(args);
Expand All @@ -63,7 +84,32 @@ private void init() {
} catch (IOException e) {
throw new RuntimeException("Error reading file " + privateKeyFile, e);
}
var apiClient = ClientUtils.createApiClient(service, clientDid, privateKeyData);
this.registryApiClient = new RegistryApi(apiClient);

// TODO: temporary to preserve the backwards compatibility (https://github.com/agera-edc/MinimumViableDataspace/issues/174)
if (dataspaceDid.isEmpty()) {
var apiClient = createApiClient(service, clientDid, privateKeyData);
this.registryApiClient = new RegistryApi(apiClient);
return;
}

registryApiClient = new RegistryApi(createApiClient(registrationUrl(), clientDid, privateKeyData));
}

private String registrationUrl() {
var didWebResolver = new WebDidResolver(httpClient(), !useHttpScheme, MAPPER, monitor);
var urlResolver = new RegistrationUrlResolver(didWebResolver);
var url = urlResolver.resolveUrl(dataspaceDid);
if (url.failed()) {
throw new CliException("Error resolving the registration url.");
}
return url.getContent();
}

@NotNull
private OkHttpClient httpClient() {
return new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2022 Microsoft Corporation
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*
*/

package org.eclipse.dataspaceconnector.registration.cli;

import org.eclipse.dataspaceconnector.iam.did.spi.document.DidDocument;
import org.eclipse.dataspaceconnector.iam.did.spi.document.Service;
import org.eclipse.dataspaceconnector.iam.did.spi.resolution.DidResolver;
import org.eclipse.dataspaceconnector.spi.result.Result;
import org.jetbrains.annotations.NotNull;

/**
* Resolves the registration url from the DID.
*/
public class RegistrationUrlResolver {

public static final String REGISTRATION_URL = "RegistrationUrl";

/**
* Resolves the DID document from did:web.
*/
private final DidResolver resolver;

public RegistrationUrlResolver(DidResolver resolver) {
this.resolver = resolver;
}

@NotNull
public Result<String> resolveUrl(String did) {
Result<DidDocument> didDocument = resolver.resolve(did);
if (didDocument.failed()) {
throw new CliException("Error resolving the DID " + did);
}
return didDocument.getContent().getService()
.stream()
.filter(service -> service.getType().equals(REGISTRATION_URL))
.map(Service::getServiceEndpoint)
.findFirst()
.map(Result::success)
.orElse(Result.failure("Error resolving service endpoint from DID Document for " + did));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.eclipse.dataspaceconnector.registration.cli;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.javafaker.Faker;
Expand Down Expand Up @@ -47,7 +48,8 @@ class ParticipantsCommandTest {
Participant participant2 = createParticipant();
String serverUrl = FAKER.internet().url();
String idsUrl = FAKER.internet().url();
String did = FAKER.internet().url();
String clientDid = FAKER.internet().url();
String dataspaceDid = "did:web:" + FAKER.internet().domainName();

RegistrationServiceCli app = new RegistrationServiceCli();
CommandLine cmd = new CommandLine(app);
Expand All @@ -72,40 +74,82 @@ void list() throws Exception {
when(app.registryApiClient.listParticipants())
.thenReturn(participants);

var exitCode = executeParticipantsList();
assertThat(exitCode).isEqualTo(0);
assertThat(serverUrl).isEqualTo(app.service);

var parsedResult = MAPPER.readValue(sw.toString(), new TypeReference<List<Participant>>() {
});
assertThat(parsedResult)
.usingRecursiveFieldByFieldElementComparator()
.isEqualTo(participants);
var exitCode = executeParticipantsList("-d", dataspaceDid);
assertListParticipants(participants, exitCode, app.dataspaceDid, dataspaceDid);
}

@Test
void add() {
var exitCode = executeParticipantsAdd(idsUrl);
var exitCode = executeParticipantsAdd("-d", dataspaceDid);
assertAddParticipants(exitCode, dataspaceDid, app.dataspaceDid);
}

@Deprecated
@Test
void list_using_serviceUrl() throws Exception {
var participants = List.of(this.participant1, participant2);
when(app.registryApiClient.listParticipants())
.thenReturn(participants);

var exitCode = executeParticipantsList("-s", serverUrl);
assertListParticipants(participants, exitCode, app.service, serverUrl);
}

@Deprecated
@Test
void add_using_serviceUrl() {
var exitCode = executeParticipantsAdd("-s", serverUrl);
assertAddParticipants(exitCode, serverUrl, app.service);
}

@Deprecated
@Test
void add_both_inputs() {
var exitCode = cmd.execute(
"-c", clientDid,
"-k", privateKeyFile.toString(),
"-s", serverUrl,
"-d", dataspaceDid,
"participants", "add",
"--ids-url", idsUrl);

assertThat(exitCode).isEqualTo(0);
assertThat(dataspaceDid).isEqualTo(app.dataspaceDid);
assertThat(serverUrl).isEqualTo(app.service);
verify(app.registryApiClient).addParticipant(idsUrl);
}

private int executeParticipantsAdd(String idsUrl) {
private void assertAddParticipants(int exitCode, String serverUrl, String service) {
assertThat(exitCode).isEqualTo(0);
assertThat(serverUrl).isEqualTo(service);
verify(app.registryApiClient).addParticipant(idsUrl);
}

private void assertListParticipants(List<Participant> participants, int exitCode, String value, String expectedValue) throws JsonProcessingException {
assertThat(exitCode).isEqualTo(0);
assertThat(expectedValue).isEqualTo(value);

var parsedResult = MAPPER.readValue(sw.toString(), new TypeReference<List<Participant>>() {
});
assertThat(parsedResult)
.usingRecursiveFieldByFieldElementComparator()
.isEqualTo(participants);
}

private int executeParticipantsAdd(String inputCmd, String inputValue) {
return cmd.execute(
"-d", did,
"-c", clientDid,
"-k", privateKeyFile.toString(),
"-s", serverUrl,
inputCmd, inputValue,
"participants", "add",
"--ids-url", idsUrl);
}

private int executeParticipantsList() {
private int executeParticipantsList(String inputCmd, String inputValue) {
return cmd.execute(
"-d", did,
"-c", clientDid,
"-k", privateKeyFile.toString(),
"-s", serverUrl,
inputCmd, inputValue,
"participants", "list");
}
}
Loading

0 comments on commit 0b39c0b

Please sign in to comment.