Convenience library based on okhttp and gson to interact with aries cloud agent python (aca-py) instances.
It is currently work in progress and not all endpoints of the agent are present in the client.
<dependency>
<groupId>network.idu.acapy</groupId>
<artifactId>aries-client-python</artifactId>
<version>0.7.0-pre.3.1</version>
</dependency>
For a aca-py 0.6.0 compatible client version you can use the following repository:
<repositories>
<repository>
<id>acapy-java-client</id>
<url>https://nexus.bosch-digital.com/repository/bds-all</url>
</repository>
</repositories>
<dependency>
<groupId>org.hyperledger</groupId>
<artifactId>aries-client-python</artifactId>
<version>0.23.0</version>
</dependency>
-
Why don't you use swagger codegen?
For a long time aca-py's swagger.json was not really in sync with the code base. This has been hugely improved lately, so I started to generate model classes based on the stable releases found on dockerhub. There are still issues with complex structures, so one can not simply use the models 1:1, instead each one has to be checked manually before using it. This is tedious work and might take a while to complete. Also, the api is complex so that I found it useful to introduce helper methods directly in the model classes to make them more accessible.
-
Why is endpoint X, or field Y missing?
aca-py's api is changing rapidly with each release, and until most of the classes are using the generated models this can happen. So, if you are missing something create a PR with a fix or open an issue.
Client Version | ACA-PY Version |
---|---|
0.7.0-pre.2 | 0.7.0-pre.2 |
0.7.0-pre.3.1 | 0.7.0-pre.3 |
Method | Endpoint | Implemented |
---|---|---|
action-menu | ||
POST | /action-menu/{conn_id}/close | ✅ |
POST | /action-menu/{conn_id}/fetch | ✅ |
POST | /action-menu/{conn_id}/perform | ✅ |
POST | /action-menu/{conn_id}/request | ✅ |
POST | /action-menu/{conn_id}/send-menu | ✅ |
basicmessage | ||
POST | /connections/{conn_id}/send-message | ✅ |
connection | ||
GET | /connections | ✅ |
POST | /connections/create-invitation | ✅ |
POST | /connections/create-static | ✅ |
POST | /connections/receive-invitation | ✅ |
GET | /connections/{conn_id}} | ✅ |
DELETE | /connections/{conn_id} | ✅ |
POST | /connections/{conn_id}/accept-invitation | ✅ |
POST | /connections/{conn_id}/accept-request | ✅ |
GET | /connections/{conn_id}/endpoints | ✅ |
POST | /connections/{conn_id}/establish-inbound/{ref_id} | ✅ |
GET | /connections/{conn_id}/metadata | ✅ |
POST | /connections/{conn_id}/metadata | ✅ |
credential-definition | ||
POST | /credential-definitions | ✅ |
GET | /credential-definitions/created | ✅ |
GET | /credential-definitions/{cred_def_id} | ✅ |
credentials | ||
GET | /credentials/mime-types/{credential_id} | ✅ |
GET | /credentials/revoked/{credential_id} | ✅ |
GET | /credential/{credential_id} | ✅ |
DELETE | /credential/{credential_id} | ✅ |
GET | /credentials | ✅ |
did-exchange | ||
POST | /didexchange/create-request | ✅ |
POST | /didexchange/receive-request | ✅ |
POST | /didexchange/{conn_id}/accept-invitation | ✅ |
POST | /didexchange/{conn_id}/accept-request | ✅ |
endorse-transaction | ||
POST | /transaction/{tran_id}/resend | ✅ |
POST | /transactions | ✅ |
POST | /transactions/create-request | ✅ |
POST | /transactions/{conn_id}/set-endorser-info | ✅ |
POST | /transactions/{conn_id}/set-endorser-role | ✅ |
POST | /transactions/{tran_id} | ✅ |
POST | /transactions/{tran_id}/cancel | ✅ |
POST | /transactions/{tran_id}/endorse | ✅ |
POST | /transactions/{tran_id}/refuse | ✅ |
POST | /transactions/{tran_id}/write | ✅ |
introduction | ||
issue-credential v1.0 | ||
POST | /issue-credential/create | ✅ |
GET | /issue-credential/records | ✅ |
GET | /issue-credential/records/{cred_ex_id} | ✅ |
DELETE | /issue-credential/records/{cred_ex_id} | ✅ |
POST | /issue-credential/records/{cred_ex_id}/issue | ✅ |
POST | /issue-credential/records/{cred_ex_id}/problem-report | ✅ |
POST | /issue-credential/records/{cred_ex_id}/send-offer | ✅ |
POST | /issue-credential/records/{cred_ex_id}/send-request | ✅ |
POST | /issue-credential/records/{cred_ex_id}/store | ✅ |
POST | /issue-credential/send | ✅ |
POST | /issue-credential/send-offer | ✅ |
POST | /issue-credential/send-proposal | ✅ |
issue-credential v2.0 | ||
jsonld | ||
POST | /jsonld/sign | ✅ |
POST | /jsonld/verify | ✅ |
ledger | ||
GET | /ledger/did-endpoint | ✅ |
GET | /ledger/did-verkey | ✅ |
GET | /ledger/taa | ✅ |
POST | /ledger/taa/accept | ✅ |
mediation | ||
multitenancy | ||
POST | /multitenancy/wallet | ✅ |
GET | /multitenancy/wallet/{wallet_id} | ✅ |
PUT | /multitenancy/wallet/{wallet_id} | ✅ |
POST | /multitenancy/wallet/{wallet_id}/remove | ✅ |
POST | /multitenancy/wallet/{wallet_id}/token | ✅ |
GET | /multitenancy/wallets | ✅ |
out-of-band | ||
POST | /out-of-band/create-invitation | ✅ |
POST | /out-of-band/receive-invitation | ✅ |
present-proof | ||
POST | /present-proof/create-request | ✅ |
GET | /present-proof/records | ✅ |
GET | /present-proof/records/{pres_ex_id} | ✅ |
DELETE | /present-proof/records/{pres_ex_id} | ✅ |
GET | /present-proof/records/{pres_ex_id}/credentials | ✅ |
POST | /present-proof/records/{pres_ex_id}/problem-report | ✅ |
POST | /present-proof/records/{pres_ex_id}/send-presentation | ✅ |
POST | /present-proof/records/{pres_ex_id}/send-request | ✅ |
POST | /present-proof/records/{pres_ex_id}/verify-presentation | ✅ |
POST | /present-proof/send-proposal | ✅ |
POST | /present-proof/send-request | ✅ |
resolver | ||
GET | /resolver/resolve/{did} | ✅ |
revocation | ||
GET | /revocation/active-registry/{cred_def_id} | ✅ |
POST | /revocation/clear-pending-revocations | ✅ |
POST | /revocation/create-registry | ✅ |
POST | /revocation/publish-revocations | ✅ |
GET | /revocation/registries/created | ✅ |
GET | /revocation/registry/{rev_reg_id} | ✅ |
PATCH | /revocation/registry/{rev_reg_id} | ✅ |
POST | /revocation/revoke | ✅ |
schema | ||
POST | /schemas | ✅ |
GET | /schemas/{schema_id} | ✅ |
server | ||
GET | /status/config | ✅ |
GET | /status/live | ✅ |
GET | /status/ready | ✅ |
trustping | ||
POST | /connections/{conn_id}/send-ping | ✅ |
wallet | ||
GET | /wallet/did | ✅ |
POST | /wallet/did/create | ✅ |
GET | /wallet/did/public | ✅ |
GET | /wallet/get-did-endpoint | ✅ |
POST | /wallet/set-did-endpoint | ✅ |
The default assumes you are running against a single wallet. In case of multi tenancy with base and sub wallets the bearerToken needs to be set as well.
AriesClient ac = AriesClient
.builder()
.url("https://myacapy.com:8031")
.apiKey("secret") // optional - admin api key if set
.bearerToken("123.456.789") // optional - jwt token - only when running in multi tennant mode
.build();
The library assumes credentials are flat Pojo's like:
@Data @NoArgsConstructor @Builder
@AttributeGroupName("referent") // the referent that should be matched in the proof request
public final class MyCredential {
private String street;
@AttributeName("e-mail")
private String email; // schema attribute name is e-mail
@AttributeName(excluded = true)
private String comment; // internal field
}
How fields are serialised/deserialized can be changed by using the @AttributeName
or @AttributeGroupName
annotations.
ac.connectionsReceiveInvitation(
ReceiveInvitationRequest.builder()
.did(did)
.label(label)
.build(),
ConnectionReceiveInvitationFilter
.builder()
.alias("alias")
.build())
.ifPresent(connection -> {
log.debug("{}", connection.getConnectionId());
});
MyCredential myCredential = MyCredential
.builder()
.email("[email protected]")
.build();
ac.issueCredentialSend(
new V1CredentialProposalRequest(connectionId, credentialdefinitionId, myCredential));
PresentProofRequest proofRequest = PresentProofRequestHelper.buildForEachAttribute(
connectionId,
MyCredential.class,
ProofRestrictions.builder()
.credentialDefinitionId(credentialDefinitionId)
.build());
ac.presentProofSendRequest(proofRequest);
Webhook controller example
@Controller
public class WebhookController {
@Inject private EventHandler handler;
@Post("/webhook/{topic}")
public void ariesEvent(
@PathVariable String topic,
@Body String message) {
handler.handleEvent(topic, message);
}
}
Websocket client example
import org.hyperledger.aries.config.GsonConfig;
import org.hyperledger.aries.webhook.WebsocketMessage;
public class AcyPyWebsocketClient extends WebSocketClient {
private Gson gson = GsonConfig.defaultConfig();
private EventHandler handler = new MyHandler();
@OnMessage
public void onMessage(String message) {
WebsocketMessage msg = gson.fromJson(message, WebsocketMessage.class);
handler.handleEvent(msg.getTopic(), msg.getPayload());
}
public static void main(String[] args) throws URISyntaxException {
AcyPyWebsocketClient c = new AcyPyWebsocketClient(new URI(
"ws://localhost:8031/ws"));
c.connect();
}
}
Your event handler can then extend the abstract EventHandler class which takes care of type conversion so that you can immediately implement your business logic.
@Singleton
public class MyHandler extends EventHandler {
@Override
public void handleProof(PresentProofPresentation proof) {
if (PresentationExchangeRole.VERIFIER.equals(proof.getRole())
&& PresentationExchangeState.VERIFIED.equals(proof.getState())) { // received a validated proof
MyCredential myCredential = proof.from(MyCredential.class);
// If the presentation is based on multiple credentials this can be done multiple times
// given that the POJO is annotated with @AttributeGroup e.g.
MyOtherCredential otherCredential = proof.from(MyOtherCredential.class);
}
}
}
Connectionless proofs are more a thing of mobile wallets, because mostly they involve something that is presented to a human like a barcode, but the java client supports this by providing models and builders.
A flow has the usually following steps:
- The user is presented with a QRCode that contains an invitation URL like: https://myhost.com/url/1234
- The server side HTTP handler of this URL responds with an HTTP.FOUND response that has the proof request encoded in the m parameter
- The mobile wallet tries to match a stored credential, and then responds with a proof presentation if possible
- The server side WebhookHandler waits for the proof and then triggers further actions
@Get("/url/{requestId}")
public HttpResponse<Object> connectionLessProof(@QueryValue String requestId) {
boolean matchingRequest = false; // TODO manage request states
String proofRequestBase64 = ""; // TODO on how to build this see the example below
if (matchingRequest) {
return HttpResponse
.status(HttpStatus.FOUND)
.header("location", deploymentUri + "?m=" + proofRequestBase64;
}
return HttpResponse.notFound();
}
Proof Request Builder Example
ProofRequestPresentationBuilder builder = new ProofRequestPresentationBuilder(ariesClient);
PresentProofRequest presentProofRequest = PresentProofRequestHelper.buildForEachAttribute(
connectionId,
List.of("name", "email"),
ProofRestrictions
.builder()
.schemaId("WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0")
.build());
Optional<String> base64 = builder.buildRequest(presentProofRequest);