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

Identity Hub CLI #12

Merged
merged 8 commits into from
Jul 28, 2022
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
21 changes: 19 additions & 2 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ name: Test Code (Style, Tests)
on:
push:
pull_request:
branches: [ main ]
paths-ignore:
- '**.md'
- 'docs/**'
Expand All @@ -29,16 +28,34 @@ jobs:

- uses: ./.github/actions/gradle-setup

- name: 'Tests'
- name: 'Build system tests launcher'
run: ./gradlew :system-tests:launcher:shadowJar

- name: 'Upgrade docker-compose (for --wait option)'
run: |
sudo curl -L https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

- name: 'Run application in docker-compose'
run: docker-compose -f system-tests/tests/docker-compose.yml up --build --wait
timeout-minutes: 10

- name: 'Unit and system tests'
run: ./gradlew test
timeout-minutes: 10
env:
INTEGRATION_TEST: true

- name: 'Publish Test Results'
uses: EnricoMi/publish-unit-test-result-action@v1
if: always()
with:
files: "**/test-results/**/*.xml"

- name: 'docker-compose logs'
run: docker-compose -f system-tests/tests/docker-compose.yml logs
if: always()

Checkstyle:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ in the detailed section referring to by linking pull requests or issues.
- Identity Hub client (#4)
- Maven artefact publication (#21)
- CredentialsVerifier implementation (#24)
- Identity Hub Command Line Interface (#25)

#### Changed

Expand Down
35 changes: 35 additions & 0 deletions client-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Command-line client

The client is a Java JAR that provides access to an identity hub service via REST.

## Running the client

To run the command line client, and list available options and commands:

```bash
cd IdentityHub
./gradlew build
java -jar client-cli/build/libs/identity-hub-cli.jar --help
```

For example, to get verifiable credentials:

```
java -jar client-cli/build/libs/identity-hub-cli.jar \
-s=http://localhost:8181/api \
vc list
```

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

```
cd IdentityHub
./gradlew publishToMavenLocal
```

```
cd OtherDirectory
mvn dependency:copy -Dartifact=org.eclipse.dataspaceconnector.identityhub:identity-hub-cli:0.0.1-SNAPSHOT:jar:all -DoutputDirectory=.
java -jar identity-hub-cli-0.0.1-SNAPSHOT-all.jar --help
```

53 changes: 53 additions & 0 deletions client-cli/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
plugins {
`java-library`
id("application")
id("com.github.johnrengelman.shadow") version "7.0.0"
`maven-publish`
}

val edcGroup: String by project
val edcVersion: String by project
val jacksonVersion: String by project
val jupiterVersion: String by project
val assertj: String by project
val mockitoVersion: String by project
val faker: String by project
val okHttpVersion: String by project
val nimbusVersion: String by project
val bouncycastleVersion: String by project
val picoCliVersion: String by project

dependencies {
api("info.picocli:picocli:${picoCliVersion}")
annotationProcessor("info.picocli:picocli-codegen:${picoCliVersion}")

implementation(project(":identity-hub-core:identity-hub-client"))
implementation("${edcGroup}:identity-did-spi:${edcVersion}")
implementation("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}")
implementation("com.squareup.okhttp3:okhttp:${okHttpVersion}")
implementation("com.nimbusds:nimbus-jose-jwt:${nimbusVersion}")
implementation("org.bouncycastle:bcpkix-jdk15on:${bouncycastleVersion}")
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}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${jupiterVersion}")
}

application {
mainClass.set("org.eclipse.dataspaceconnector.identityhub.cli.IdentityHubCli")
}

tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
mergeServiceFiles()
archiveFileName.set("identity-hub-cli.jar")
}

publishing {
publications {
create<MavenPublication>("identity-hub-cli") {
artifactId = "identity-hub-cli"
from(components["java"])
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.identityhub.cli;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.dataspaceconnector.identityhub.credentials.model.VerifiableCredential;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParentCommand;

import java.util.concurrent.Callable;

import static org.eclipse.dataspaceconnector.identityhub.credentials.CryptoUtils.readPrivateEcKey;


@Command(name = "add", description = "Adds a verifiable credential to identity hub")
class AddVerifiableCredentialCommand implements Callable<Integer> {

private static final ObjectMapper MAPPER = new ObjectMapper();

@ParentCommand
private VerifiableCredentialsCommand command;

@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;

@CommandLine.Option(names = { "-c", "--verifiable-credential" }, required = true, description = "Verifiable Credential as JSON")
private String verifiableCredentialJson;

@CommandLine.Option(names = { "-i", "--issuer" }, required = true, description = "DID of the Verifiable Credential issuer")
private String issuer;

@CommandLine.Option(names = { "-b", "--subject" }, required = true, description = "DID of the Verifiable Credential subject")
private String subject;

@CommandLine.Option(names = { "-k", "--private-key" }, required = true, description = "PEM file with EC private key for signing Verifiable Credentials")
private String privateKeyPemFile;

@Override
public Integer call() throws Exception {
var out = spec.commandLine().getOut();

VerifiableCredential vc;
try {
vc = MAPPER.readValue(verifiableCredentialJson, VerifiableCredential.class);
} catch (JsonProcessingException e) {
throw new CliException("Error while processing request json.");
}

SignedJWT signedJwt;
try {
var privateKey = readPrivateEcKey(privateKeyPemFile);
signedJwt = command.cli.verifiableCredentialsJwtService.buildSignedJwt(vc, issuer, subject, privateKey);
} catch (Exception e) {
throw new CliException("Error while signing Verifiable Credential", e);
}

command.cli.identityHubClient.addVerifiableCredential(command.cli.hubUrl, signedJwt);

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

return 0;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.identityhub.cli;

/**
* Base exception for the CLI client.
* The client should use unchecked exceptions when appropriate (e.g., non-recoverable errors) and may extend this exception.
*/
public class CliException extends RuntimeException {
public CliException(String message) {
super(message);
}

public CliException(String message, Throwable cause) {
super(message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.identityhub.cli;

import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import org.eclipse.dataspaceconnector.identityhub.client.IdentityHubClient;
import org.eclipse.dataspaceconnector.identityhub.client.IdentityHubClientImpl;
import org.eclipse.dataspaceconnector.identityhub.credentials.VerifiableCredentialsJwtService;
import org.eclipse.dataspaceconnector.identityhub.credentials.VerifiableCredentialsJwtServiceImpl;
import org.eclipse.dataspaceconnector.spi.monitor.ConsoleMonitor;
import picocli.CommandLine;
import picocli.CommandLine.Command;

@Command(name = "identity-hub-cli", mixinStandardHelpOptions = true,
description = "Client utility for MVD identity hub.",
subcommands = {
VerifiableCredentialsCommand.class
})
public class IdentityHubCli {
@CommandLine.Option(names = { "-s", "--identity-hub-url" }, required = true, description = "Identity Hub URL", defaultValue = "http://localhost:8181/api/identity-hub")
String hubUrl;

IdentityHubClient identityHubClient;

VerifiableCredentialsJwtService verifiableCredentialsJwtService;

public static void main(String... args) {
CommandLine commandLine = getCommandLine();
var exitCode = commandLine.execute(args);
System.exit(exitCode);
}

public static CommandLine getCommandLine() {
var command = new IdentityHubCli();
return new CommandLine(command).setExecutionStrategy(command::executionStrategy);
}

private int executionStrategy(CommandLine.ParseResult parseResult) {
init(); // custom initialization to be done before executing any command or subcommand
return new CommandLine.RunLast().execute(parseResult);
}

private void init() {
var okHttpClient = new OkHttpClient.Builder().build();
var objectMapper = new ObjectMapper();
var monitor = new ConsoleMonitor();
this.identityHubClient = new IdentityHubClientImpl(okHttpClient, objectMapper, monitor);
this.verifiableCredentialsJwtService = new VerifiableCredentialsJwtServiceImpl(objectMapper);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.identityhub.cli;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.nimbusds.jwt.SignedJWT;
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;

@Command(name = "list", description = "Lists verifiable credentials")
class ListVerifiableCredentialsCommand implements Callable<Integer> {

private static final ObjectMapper MAPPER = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT);

@ParentCommand
private VerifiableCredentialsCommand command;

@Spec
private CommandSpec spec;

@Override
public Integer call() throws Exception {
var out = spec.commandLine().getOut();
var result = command.cli.identityHubClient.getVerifiableCredentials(command.cli.hubUrl);
var vcs = result.getContent().stream()
.map(this::getClaims)
.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);
}
}
}
Loading