Skip to content

Commit

Permalink
✨ Implement DID Exchange and DID Rotate methods and 🗑️ deprecate conn…
Browse files Browse the repository at this point in the history
…ections protocol (#1119)

* 🗑️ mark deprecated routes

* 🚧 initial implementation of new did-exchange routes

* 🎨 modify default extra settings example

* 🔧 Update default pylint config

* 🎨 update docstrings and available optional params

* 🚧 e2e test under construction

* ✅ fix up create request tests

* 🐛 fix protocol: /1.1 doesn't work ...

* 🐛 use_public_did must be false in accept-request

* ✅ working tests for accept-request

* 🎨 remove unused endpoint and update route names

* 🎨 update route names

* 🎨 accept reject reason sa body instead of param

* ⬆️ Use latest cloudcontroller

* ⬆️ Update lock files

* ✨ implement did-rotate endpoints

* 🔧 add max-positional-arguments to pylintrc

* ⬆️ use latest cloudcontroller

* ⬆️ Update lock files

* ✨ Use did-rotate/hangup in deletion of connection record (if using didexchange protocol)

* ✅ Fix deleting records when using oob connections

* ✅ assert connections are complete for both parties

* ✅ e2e tests for did-rotate

* ⬆️ Helmfile `0.169`, Helm `3.16.2`, Tailscale `1.76.0` (#1123)

* 📌 Pin `xk6` and plugin versions (#1124)

* `xk6-sse` isn't compatible with `k6>=0.53`
* Pin `xk6` and all plugins to the latest compatible versions
* Also bump Golang to `1.23`

* 📌 Explicitly pin k6 to `v0.52.0` (#1125)

* ⬆️ Update lock files

* 🎨

* ✅ 100% unit test coverage for new did-exchange and did-rotate methods

* ✅ fixed up delete connection test

* 🐛 Reconfigure ACAPY_AUTO_ACCEPT_REQUESTS for Faber after test completes

* ✅ add cleaning up of connection records, for regression fixtures not to get bloated

---------

Co-authored-by: Robbie Blaine <[email protected]>
  • Loading branch information
ff137 and rblaine95 authored Oct 17, 2024
1 parent db2ee32 commit c78bd72
Show file tree
Hide file tree
Showing 18 changed files with 1,452 additions and 409 deletions.
9 changes: 6 additions & 3 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,14 @@ exclude-too-few-public-methods=
# R0901)
ignored-parents=

# Maximum number of positional arguments for function / method.
max-positional-arguments=10

# Maximum number of arguments for function / method.
max-args=5
max-args=10

# Maximum number of attributes for a class (see R0902).
max-attributes=7
max-attributes=9

# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
Expand All @@ -298,7 +301,7 @@ max-bool-expr=5
max-branches=12

# Maximum number of locals for function / method body.
max-locals=15
max-locals=20

# Maximum number of parents for a class (see R0901).
max-parents=7
Expand Down
2 changes: 1 addition & 1 deletion app/models/tenants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
ExtraSettings_field = Field(
None,
description="Optional per-tenant settings to configure wallet behaviour for advanced users.",
examples=[{"ACAPY_AUTO_PING_CONNECTION": True}],
examples=[{"ACAPY_AUTO_ACCEPT_INVITES": False}],
)


Expand Down
200 changes: 92 additions & 108 deletions app/poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package-mode = false
python = "^3.12"

aiohttp = "~3.10.5"
aries-cloudcontroller = "==1.0.0b0"
aries-cloudcontroller = "==1.0.1b0"
base58 = "~2.1.1"
fastapi = "~0.115.0"
httpx = "~0.27.0"
Expand Down
286 changes: 281 additions & 5 deletions app/routes/connections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from typing import List, Optional

from aries_cloudcontroller import CreateInvitationRequest, InvitationResult
from aries_cloudcontroller import (
CreateInvitationRequest,
DIDRotateRequestJSON,
DIDXRejectRequest,
Hangup,
InvitationResult,
Rotate,
)
from fastapi import APIRouter, Depends

from app.dependencies.acapy_clients import client_from_auth
Expand Down Expand Up @@ -30,6 +37,7 @@
@router.post(
"/create-invitation",
summary="Create a Connection Invitation",
deprecated=True,
response_model=InvitationResult,
)
async def create_invitation(
Expand Down Expand Up @@ -91,6 +99,7 @@ async def create_invitation(
@router.post(
"/accept-invitation",
summary="Accept a Connection Invitation",
deprecated=True,
response_model=Connection,
)
async def accept_invitation(
Expand Down Expand Up @@ -262,7 +271,11 @@ async def delete_connection_by_id(
---
This endpoint deletes a connection record by id.
The other party will still have their record of the connection, but it will become unusable.
If the connection uses the didexchange protocol, then we hangup the connection, such that the other party also has
their record deleted.
If the connection uses the deprecated connections protocol, then we just delete the record. The other party will
still have their record of the connection, but it will become unusable.
Parameters:
---
Expand All @@ -277,10 +290,273 @@ async def delete_connection_by_id(
bound_logger.debug("DELETE request received: Delete connection by ID")

async with client_from_auth(auth) as aries_controller:
await handle_acapy_call(
# Fetch connection record, and check if it uses didexchange protocol
conn_record = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.connection.get_connection,
conn_id=connection_id,
)
connection_protocol = conn_record.connection_protocol or ""
is_did_exchange_protocol = "didexchange" in connection_protocol

if is_did_exchange_protocol:
# If it uses didexchange protocol, then we hangup the connection
await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.did_rotate.hangup,
conn_id=connection_id,
)
bound_logger.debug("Successfully hung up connection.")
else:
# If it uses connections protocol, then we just delete the record
await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.connection.delete_connection,
conn_id=connection_id,
)
bound_logger.debug("Successfully deleted connection by ID.")


@router.post(
"/did-exchange/create-request",
summary="Create a DID Exchange Request",
response_model=Connection,
)
async def create_did_exchange_request(
their_public_did: str,
alias: Optional[str] = None,
goal: Optional[str] = None,
goal_code: Optional[str] = None,
my_label: Optional[str] = None,
use_did: Optional[str] = None,
use_did_method: Optional[str] = None,
use_public_did: bool = False,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> Connection:
"""
Create a DID Exchange request
---
This endpoint allows you to initiate a DID Exchange request with another party using their public DID.
The goal and goal_code parameters provide additional context for the request.
Only one of `use_did`, `use_did_method` or `use_public_did` should be specified. If none of these are specified,
a new local DID will be created for this connection.
Parameters:
---
their_public_did: str
The DID of the party you want to connect to.
alias: str, optional
An alias for the connection. Defaults to None.
goal: str, optional
Optional self-attested string for sharing the intent of the connection.
goal_code: str, optional
Optional self-attested code for sharing the intent of the connection.
my_label: str, optional
Your label for the request.
use_did: str, optional
Your local DID to use for the connection.
use_did_method: str, optional
The method to use for the connection: "did:peer:2" or "did:peer:4".
use_public_did: bool
Use your public DID for this connection. Defaults to False.
Returns:
---
Connection
The connection record created by the DID exchange request.
"""
bound_logger = logger.bind(
body={
"their_public_did": their_public_did,
"alias": alias,
"goal": goal,
"goal_code": goal_code,
"my_label": my_label,
"use_did": use_did,
"use_did_method": use_did_method,
"use_public_did": use_public_did,
}
)
bound_logger.debug("POST request received: Create DID exchange request")

async with client_from_auth(auth) as aries_controller:
connection_record = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.did_exchange.create_request,
their_public_did=their_public_did,
alias=alias,
auto_accept=True,
goal=goal,
goal_code=goal_code,
my_label=my_label,
protocol="didexchange/1.0",
use_did=use_did,
use_did_method=use_did_method,
use_public_did=use_public_did,
)

result = conn_record_to_connection(connection_record)
bound_logger.debug("Successfully created DID exchange request.")
return result


@router.post(
"/did-exchange/accept-request",
summary="Accept a DID Exchange Request",
response_model=Connection,
)
async def accept_did_exchange_request(
connection_id: str,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> Connection:
"""
Accept a stored DID Exchange request
---
This endpoint allows you to accept a request by providing the connection ID.
Parameters:
---
connection_id: str
The ID of the connection request you want to accept.
Returns:
---
Connection
The connection record created by accepting the DID exchange request.
"""
bound_logger = logger.bind(body={"connection_id": connection_id})
bound_logger.debug("POST request received: Accept DID exchange request")

async with client_from_auth(auth) as aries_controller:
connection_record = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.did_exchange.accept_request,
conn_id=connection_id,
use_public_did=False,
# todo: if use_public_did=True, then agent raises:
# DIDXManagerError: did_rotate~attach required if no signed doc attachment
)

result = conn_record_to_connection(connection_record)
bound_logger.debug("Successfully accepted DID exchange request.")
return result


@router.post(
"/did-exchange/reject",
summary="Reject or Abandon a DID Exchange",
response_model=Connection,
)
async def reject_did_exchange(
connection_id: str,
body: Optional[DIDXRejectRequest] = None,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> Connection:
"""
Reject or abandon a DID Exchange
---
This endpoint allows you to reject or abandon a DID Exchange request. You can optionally provide a reason
for the rejection.
Returns:
---
Connection
The connection record after rejecting the DID exchange request.
"""
bound_logger = logger.bind(body={"connection_id": connection_id})
bound_logger.debug("POST request received: Reject DID exchange")

async with client_from_auth(auth) as aries_controller:
connection_record = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.did_exchange.reject,
conn_id=connection_id,
body=body,
)

result = conn_record_to_connection(connection_record)
bound_logger.debug("Successfully rejected DID exchange.")
return result


@router.post(
"/did-rotate",
summary="Begin DID Rotation",
response_model=Rotate,
)
async def rotate_did(
connection_id: str,
to_did: str,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> Rotate:
"""
Begin the rotation of a DID as a rotator.
---
This endpoint allows you to begin the DID rotation for an existing connection. The `to_did` parameter specifies
the new DID that the rotating party is rotating to.
Parameters:
---
connection_id: str
The ID of the connection for which the DID is to be rotated.
to_did: str
The new DID that the rotating party is rotating to.
Returns:
---
Rotate
The record after the DID rotation is initiated.
"""
bound_logger = logger.bind(body={"connection_id": connection_id, "to_did": to_did})
bound_logger.debug("POST request received: Rotate DID")

async with client_from_auth(auth) as aries_controller:
rotate = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.did_rotate.rotate,
conn_id=connection_id,
body=DIDRotateRequestJSON(to_did=to_did),
)

bound_logger.debug("Successfully initiated DID rotation.")
return rotate


@router.post(
"/did-rotate/hangup",
summary="Hangup DID Rotation",
response_model=Hangup,
)
async def hangup_did_rotation(
connection_id: str,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> Hangup:
"""
Send a hangup for a DID rotation as the rotator.
---
This endpoint allows you to hangup a DID rotation process for an existing connection.
Parameters:
---
connection_id: str
The ID of the connection for which the DID rotation is being hung up.
Returns:
---
Hangup
The record after the DID rotation is hung up.
"""
bound_logger = logger.bind(body={"connection_id": connection_id})
bound_logger.debug("POST request received: Hangup DID rotation")

async with client_from_auth(auth) as aries_controller:
hangup = await handle_acapy_call(
logger=bound_logger,
acapy_call=aries_controller.connection.delete_connection,
acapy_call=aries_controller.did_rotate.hangup,
conn_id=connection_id,
)

bound_logger.debug("Successfully deleted connection by ID.")
bound_logger.debug("Successfully hung up DID rotation.")
return hangup
Loading

0 comments on commit c78bd72

Please sign in to comment.