From ab08c6d0da8392355edb654949c3255b3d152d40 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 21 Mar 2024 23:49:25 -0400 Subject: [PATCH] feat: support creating request according to invite hs proto Signed-off-by: Daniel Bluhm --- .../protocols/didexchange/v1_0/manager.py | 164 ++++++++++++------ 1 file changed, 109 insertions(+), 55 deletions(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 2c8cab8a0d..a702298096 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -2,7 +2,7 @@ import json import logging -from typing import Optional, Sequence, Union +from typing import List, Optional, Sequence, Union from did_peer_4 import LONG_PATTERN, long_to_short @@ -24,12 +24,13 @@ from ....wallet.error import WalletError from ....wallet.key_type import ED25519 from ...coordinate_mediation.v1_0.manager import MediationManager +from ...coordinate_mediation.v1_0.models.mediation_record import MediationRecord from ...discovery.v2_0.manager import V20DiscoveryMgr from ...out_of_band.v1_0.messages.invitation import ( InvitationMessage as OOBInvitationMessage, ) from ...out_of_band.v1_0.messages.service import Service as OOBService -from .message_types import ARIES_PROTOCOL as DIDEX_1_1 +from .message_types import ARIES_PROTOCOL as DIDEX_1_1, DIDEX_1_0 from .messages.complete import DIDXComplete from .messages.problem_report import DIDXProblemReport, ProblemReportReason from .messages.request import DIDXRequest @@ -109,7 +110,9 @@ async def receive_invitation( ) else ConnRecord.ACCEPT_MANUAL ) - protocol = protocol or "didexchange/1.0" + protocol = protocol or DIDEX_1_0 + if protocol not in ConnRecord.SUPPORTED_PROTOCOLS: + raise DIDXManagerError(f"Unexpected protocol: {protocol}") service_item = invitation.services[0] # Create connection record @@ -251,7 +254,6 @@ async def create_request_implicit( mediation_id=mediation_id, goal_code=goal_code, goal=goal, - use_public_did=bool(my_public_info), ) conn_rec.request_id = request._id conn_rec.state = ConnRecord.State.REQUEST.rfc160 @@ -271,7 +273,6 @@ async def create_request( mediation_id: Optional[str] = None, goal_code: Optional[str] = None, goal: Optional[str] = None, - use_public_did: bool = False, ) -> DIDXRequest: """Create a new connection request for a previously-received invitation. @@ -283,8 +284,6 @@ async def create_request( service endpoint goal_code: Optional self-attested code for sharing intent of connection goal: Optional self-attested string for sharing intent of connection - use_public_did: Flag whether to use public DID and omit DID Doc - attachment on request Returns: A new `DIDXRequest` message to send to the other agent @@ -297,9 +296,6 @@ async def create_request( or_default=True, ) - my_info = None - - # Create connection request message if my_endpoint: my_endpoints = [my_endpoint] else: @@ -309,6 +305,55 @@ async def create_request( my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + if not my_label: + my_label = self.profile.settings.get("default_label") + assert my_label + + did_url = None + if conn_rec.their_public_did is not None: + services = await self.resolve_didcomm_services(conn_rec.their_public_did) + if services: + did_url = services[0].id + + pthid = conn_rec.invitation_msg_id or did_url + + if conn_rec.connection_protocol == DIDEX_1_0: + request = await self._legacy_request_with_attached_doc( + conn_rec, my_label, my_endpoints, mediation_records, goal_code, goal + ) + else: + request = await self._qualified_did_request_with_fallback( + conn_rec, my_label, my_endpoints, mediation_records, goal_code, goal + ) + + request.assign_thread_id(thid=request._id, pthid=pthid) + + # Update connection state + conn_rec.request_id = request._id + conn_rec.state = ConnRecord.State.REQUEST.rfc160 + async with self.profile.session() as session: + await conn_rec.save(session, reason="Created connection request") + + # Idempotent; if routing has already been set up, no action taken + await self._route_manager.route_connection_as_invitee( + self.profile, conn_rec, mediation_records + ) + + return request + + async def _qualified_did_request_with_fallback( + self, + conn_rec: ConnRecord, + my_label: str, + my_endpoints: Sequence[str], + mediation_records: List[MediationRecord], + goal_code: Optional[str], + goal: Optional[str], + ) -> DIDXRequest: + """Create DID Exchange request using a qualified DID. + + Fall back to unqualified DID if settings don't cause did:peer emission. + """ emit_did_peer_4 = self.profile.settings.get("emit_did_peer_4") emit_did_peer_2 = self.profile.settings.get("emit_did_peer_2") if emit_did_peer_2 and emit_did_peer_4: @@ -317,10 +362,21 @@ async def create_request( using did:peer:4" ) - if conn_rec.my_did: + if conn_rec.my_did: # DID should be public or qualified async with self.profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.get_local_did(conn_rec.my_did) + + posture = DIDPosture.get(my_info.metadata) + if posture not in ( + DIDPosture.PUBLIC, + DIDPosture.POSTED, + ) and not my_info.did.startswith("did:"): + # The DID has been previously set and is not public or qualified... + # Must fallback + return await self._legacy_request_with_attached_doc( + conn_rec, my_label, my_endpoints, mediation_records, goal_code, goal + ) elif emit_did_peer_4: my_info = await self.create_did_peer_4(my_endpoints, mediation_records) conn_rec.my_did = my_info.did @@ -328,7 +384,38 @@ async def create_request( my_info = await self.create_did_peer_2(my_endpoints, mediation_records) conn_rec.my_did = my_info.did else: - # Create new DID for connection + return await self._legacy_request_with_attached_doc( + conn_rec, my_label, my_endpoints, mediation_records, goal_code, goal + ) + + did = conn_rec.my_did + assert did, "DID must be set on connection record" + if not did.startswith("did:"): + did = f"did:sov:{did}" + + request = DIDXRequest( + label=my_label, + did=did, + goal_code=goal_code, + goal=goal, + ) + return request + + async def _legacy_request_with_attached_doc( + self, + conn_rec: ConnRecord, + my_label: str, + my_endpoints: Sequence[str], + mediation_records: List[MediationRecord], + goal_code: Optional[str], + goal: Optional[str], + ) -> DIDXRequest: + """Create a DID Exchange request using an unqualified DID.""" + if conn_rec.my_did: + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + my_info = await wallet.get_local_did(conn_rec.my_did) + else: async with self.profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did( @@ -337,56 +424,23 @@ async def create_request( ) conn_rec.my_did = my_info.did - if use_public_did or emit_did_peer_2 or emit_did_peer_4: - # Omit DID Doc attachment if we're using a public DID - did_doc = None - attach = None - did = conn_rec.my_did - if not did.startswith("did:"): - did = f"did:sov:{did}" - else: - did_doc = await self.create_did_document( - my_info, - my_endpoints, - mediation_records=mediation_records, - ) - attach = AttachDecorator.data_base64(did_doc.serialize()) - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - await attach.data.sign(my_info.verkey, wallet) - did = conn_rec.my_did - - did_url = None - if conn_rec.their_public_did is not None: - services = await self.resolve_didcomm_services(conn_rec.their_public_did) - if services: - did_url = services[0].id - - pthid = conn_rec.invitation_msg_id or did_url - - if not my_label: - my_label = self.profile.settings.get("default_label") + did_doc = await self.create_did_document( + my_info, + my_endpoints, + mediation_records=mediation_records, + ) + attach = AttachDecorator.data_base64(did_doc.serialize()) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + await attach.data.sign(my_info.verkey, wallet) request = DIDXRequest( label=my_label, - did=did, + did=my_info.did, did_doc_attach=attach, goal_code=goal_code, goal=goal, ) - request.assign_thread_id(thid=request._id, pthid=pthid) - - # Update connection state - conn_rec.request_id = request._id - conn_rec.state = ConnRecord.State.REQUEST.rfc160 - async with self.profile.session() as session: - await conn_rec.save(session, reason="Created connection request") - - # Idempotent; if routing has already been set up, no action taken - await self._route_manager.route_connection_as_invitee( - self.profile, conn_rec, mediation_records - ) - return request async def receive_request(