From 6715a0b46c7747fe9c6963a24f38074709042ed7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 16 Nov 2023 23:21:45 -0500 Subject: [PATCH 1/6] feat: add did:peer:1 resolution support Resolves did:peer:1 received in did exchange Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 41 +++--- .../connections/models/conn_record.py | 15 +- .../protocols/didexchange/v1_0/manager.py | 8 +- aries_cloudagent/resolver/__init__.py | 6 + .../resolver/default/legacy_peer.py | 2 +- aries_cloudagent/resolver/default/peer1.py | 132 ++++++++++++++++++ 6 files changed, 173 insertions(+), 31 deletions(-) create mode 100644 aries_cloudagent/resolver/default/peer1.py diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index f40a670ae8..f07064b631 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -3,6 +3,7 @@ For Connection, DIDExchange and OutOfBand Manager. """ +import json import logging from typing import List, Optional, Sequence, Text, Tuple, Union @@ -132,35 +133,35 @@ async def create_did_document( return did_doc - async def store_did_document(self, did_doc: DIDDoc): + async def store_did_document(self, value: Union[DIDDoc, dict]): """Store a DID document. Args: - did_doc: The `DIDDoc` instance to persist + value: The `DIDDoc` instance to persist """ - assert did_doc.did + if isinstance(value, DIDDoc): + did = value.did + doc = value.to_json() + else: + did = value["id"] + doc = json.dumps(value) + + self._logger.debug("Storing DID document for %s: %s", did, doc) try: - stored_doc, record = await self.fetch_did_document(did_doc.did) + stored_doc, record = await self.fetch_did_document(did) except StorageNotFoundError: - record = StorageRecord( - self.RECORD_TYPE_DID_DOC, - did_doc.to_json(), - {"did": did_doc.did}, - ) + record = StorageRecord(self.RECORD_TYPE_DID_DOC, doc, {"did": did}) async with self._profile.session() as session: storage: BaseStorage = session.inject(BaseStorage) await storage.add_record(record) else: async with self._profile.session() as session: storage: BaseStorage = session.inject(BaseStorage) - await storage.update_record( - record, did_doc.to_json(), {"did": did_doc.did} - ) - await self.remove_keys_for_did(did_doc.did) - for key in did_doc.pubkey.values(): - if key.controller == did_doc.did: - await self.add_key_for_did(did_doc.did, key.value) + await storage.update_record(record, doc, {"did": did}) + + await self.remove_keys_for_did(did) + await self.record_did(did) async def add_key_for_did(self, did: str, key: str): """Store a verkey for lookup against a DID. @@ -219,12 +220,12 @@ async def resolve_didcomm_services( doc: ResolvedDocument = pydid.deserialize_document(doc_dict, strict=True) except ResolverError as error: raise BaseConnectionManagerError( - "Failed to resolve public DID in invitation" + "Failed to resolve DID services" ) from error if not doc.service: raise BaseConnectionManagerError( - "Cannot connect via public DID that has no associated services" + "Cannot connect via DID that has no associated services" ) didcomm_services = sorted( @@ -617,7 +618,7 @@ def diddoc_connection_targets( ) return targets - async def fetch_did_document(self, did: str) -> Tuple[DIDDoc, StorageRecord]: + async def fetch_did_document(self, did: str) -> Tuple[dict, StorageRecord]: """Retrieve a DID Document for a given DID. Args: @@ -627,7 +628,7 @@ async def fetch_did_document(self, did: str) -> Tuple[DIDDoc, StorageRecord]: async with self._profile.session() as session: storage = session.inject(BaseStorage) record = await storage.find_record(self.RECORD_TYPE_DID_DOC, {"did": did}) - return DIDDoc.from_json(record.value), record + return json.loads(record.value), record async def find_connection( self, diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index 7418fca11b..208c79b748 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -9,8 +9,8 @@ from ...core.profile import ProfileSession from ...messaging.models.base_record import BaseRecord, BaseRecordSchema from ...messaging.valid import ( - INDY_DID_EXAMPLE, - INDY_DID_VALIDATE, + GENERIC_DID_EXAMPLE, + GENERIC_DID_VALIDATE, INDY_RAW_PUBLIC_KEY_EXAMPLE, INDY_RAW_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, @@ -653,15 +653,18 @@ class Meta: ) my_did = fields.Str( required=False, - validate=INDY_DID_VALIDATE, - metadata={"description": "Our DID for connection", "example": INDY_DID_EXAMPLE}, + validate=GENERIC_DID_VALIDATE, + metadata={ + "description": "Our DID for connection", + "example": GENERIC_DID_EXAMPLE, + }, ) their_did = fields.Str( required=False, - validate=INDY_DID_VALIDATE, + validate=GENERIC_DID_VALIDATE, metadata={ "description": "Their DID for connection", - "example": INDY_DID_EXAMPLE, + "example": GENERIC_DID_EXAMPLE, }, ) their_label = fields.Str( diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 4477ceee8a..4e74de1913 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -494,11 +494,11 @@ async def receive_request( wallet = session.inject(BaseWallet) conn_did_doc = await self.verify_diddoc(wallet, request.did_doc_attach) await self.store_did_document(conn_did_doc) - if request.did != conn_did_doc.did: + if request.did != conn_did_doc["id"]: raise DIDXManagerError( ( f"Connection DID {request.did} does not match " - f"DID Doc id {conn_did_doc.did}" + f"DID Doc id {conn_did_doc['id']}" ), error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value, ) @@ -941,7 +941,7 @@ async def verify_diddoc( wallet: BaseWallet, attached: AttachDecorator, invi_key: str = None, - ) -> DIDDoc: + ) -> dict: """Verify DIDDoc attachment and return signed data.""" signed_diddoc_bytes = attached.data.signed if not signed_diddoc_bytes: @@ -949,7 +949,7 @@ async def verify_diddoc( if not await attached.data.verify(wallet, invi_key): raise DIDXManagerError("DID doc attachment signature failed verification") - return DIDDoc.deserialize(json.loads(signed_diddoc_bytes.decode())) + return json.loads(signed_diddoc_bytes.decode()) async def get_resolved_did_document(self, qualified_did: str) -> ResolvedDocument: """Return resolved DID document.""" diff --git a/aries_cloudagent/resolver/__init__.py b/aries_cloudagent/resolver/__init__.py index 82efd99987..bee29b84be 100644 --- a/aries_cloudagent/resolver/__init__.py +++ b/aries_cloudagent/resolver/__init__.py @@ -57,6 +57,12 @@ async def setup(context: InjectionContext): await universal_resolver.setup(context) registry.register_resolver(universal_resolver) + peer_did_1_resolver = ClassProvider( + "aries_cloudagent.resolver.default.peer1.PeerDID1Resolver" + ).provide(context.settings, context.injector) + await peer_did_1_resolver.setup(context) + registry.register_resolver(peer_did_1_resolver) + peer_did_2_resolver = ClassProvider( "aries_cloudagent.resolver.default.peer2.PeerDID2Resolver" ).provide(context.settings, context.injector) diff --git a/aries_cloudagent/resolver/default/legacy_peer.py b/aries_cloudagent/resolver/default/legacy_peer.py index 3b012adede..623afff3b2 100644 --- a/aries_cloudagent/resolver/default/legacy_peer.py +++ b/aries_cloudagent/resolver/default/legacy_peer.py @@ -282,7 +282,7 @@ async def _fetch_did_document(self, profile: Profile, did: str) -> RetrieveResul try: doc, _ = await conn_mgr.fetch_did_document(did) LOGGER.debug("Fetched doc %s", doc) - to_cache = RetrieveResult(True, doc=doc.serialize()) + to_cache = RetrieveResult(True, doc=doc) except StorageNotFoundError: LOGGER.debug("Failed to fetch doc for did %s", did) to_cache = RetrieveResult(False) diff --git a/aries_cloudagent/resolver/default/peer1.py b/aries_cloudagent/resolver/default/peer1.py new file mode 100644 index 0000000000..8f1c928759 --- /dev/null +++ b/aries_cloudagent/resolver/default/peer1.py @@ -0,0 +1,132 @@ +"""did:peer:1 resolver implementation.""" + +import logging +import re +from typing import Callable, Optional, Pattern, Sequence, Text, Union + +from aries_cloudagent.messaging.valid import B58 + +from ...config.injection_context import InjectionContext +from ...connections.base_manager import BaseConnectionManager +from ...core.profile import Profile +from ...resolver.base import BaseDIDResolver, DIDNotFound, ResolverType +from ...storage.error import StorageNotFoundError + + +LOGGER = logging.getLogger(__name__) + + +# TODO Copy pasted from did-peer-4, reuse when available +def _operate_on_embedded( + visitor: Callable[[dict], dict] +) -> Callable[[Union[dict, str]], Union[dict, str]]: + """Return an adapter function that turns a vm visitor into a vm | ref visitor. + + The adapter function calls a visitor on embedded vms but just returns on refs. + """ + + def _adapter(vm: Union[dict, str]) -> Union[dict, str]: + if isinstance(vm, dict): + return visitor(vm) + return vm + + return _adapter + + +def _visit_verification_methods(document: dict, visitor: Callable[[dict], dict]): + """Visit all verification methods in a document. + + This includes the main verificationMethod list as well as verification + methods embedded in relationships. + """ + verification_methods = document.get("verificationMethod") + if verification_methods: + document["verificationMethod"] = [visitor(vm) for vm in verification_methods] + + for relationship in ( + "authentication", + "assertionMethod", + "keyAgreement", + "capabilityInvocation", + "capabilityDelegation", + ): + vms_and_refs = document.get(relationship) + if vms_and_refs: + document[relationship] = [ + _operate_on_embedded(visitor)(vm) for vm in vms_and_refs + ] + + return document + + +def contextualize(did: str, document: dict): + """Contextualize a peer DID document.""" + + def _contextualize_verification_method(vm: dict): + """Contextualize a verification method.""" + if vm["controller"] == "#id": + vm["controller"] = did + if vm["id"].startswith("#"): + vm["id"] = f"{did}{vm['id']}" + return vm + + document = _visit_verification_methods(document, _contextualize_verification_method) + + for service in document.get("service", []): + if service["id"].startswith("#"): + service["id"] = f"{did}{service['id']}" + + return document + + +class PeerDID1Resolver(BaseDIDResolver): + """Resolve legacy peer DIDs.""" + + PEER1_PATTERN = re.compile(rf"^did:peer:1zQm[{B58}]{{44}}$") + + def __init__(self): + """Initialize the resolver instance.""" + super().__init__(ResolverType.NATIVE) + + async def setup(self, context: InjectionContext): + """Perform required setup for the resolver.""" + + @property + def supported_did_regex(self) -> Pattern: + """Return supported_did_regex of DID Peer 1 Resolver.""" + return self.PEER1_PATTERN + + async def _fetch_did_document(self, profile: Profile, did: str) -> Optional[dict]: + """Fetch DID from wallet if available. + + This is the method to be used with fetch_did_document to enable caching. + """ + conn_mgr = BaseConnectionManager(profile) + try: + doc, _ = await conn_mgr.fetch_did_document(did) + LOGGER.debug("Fetched doc %s", doc) + return doc + except StorageNotFoundError: + LOGGER.debug("Failed to fetch doc for did %s", did) + + return None + + async def _resolve( + self, + profile: Profile, + did: str, + service_accept: Optional[Sequence[Text]] = None, + ) -> dict: + """Resolve Legacy Peer DID to a DID document by fetching from the wallet. + + By the time this resolver is selected, it should be impossible for it + to raise a DIDNotFound. + """ + result = await self._fetch_did_document(profile, did) + if result: + # Apply corrections? + result = contextualize(did, result) + LOGGER.debug("Resolved %s to %s", did, result) + return result + else: + raise DIDNotFound(f"DID not found: {did}") From a2c6b4fe9e49ecb71a48060102f428598f58502c Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Dec 2023 14:38:24 -0500 Subject: [PATCH 2/6] fix: match legacy behavior better Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 6 +++++- .../protocols/didexchange/v1_0/manager.py | 17 ++++++++++++++--- aries_cloudagent/resolver/did_resolver.py | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index f07064b631..0ed7e48e42 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -146,6 +146,11 @@ async def store_did_document(self, value: Union[DIDDoc, dict]): did = value["id"] doc = json.dumps(value) + # Special case: we used to store did:sov dids as unqualified. + # For backwards compatibility, we'll strip off the prefix. + if did.startswith("did:sov:"): + did = did[8:] + self._logger.debug("Storing DID document for %s: %s", did, doc) try: @@ -624,7 +629,6 @@ async def fetch_did_document(self, did: str) -> Tuple[dict, StorageRecord]: Args: did: The DID to search for """ - # legacy documents for unqualified dids async with self._profile.session() as session: storage = session.inject(BaseStorage) record = await storage.find_record(self.RECORD_TYPE_DID_DOC, {"did": did}) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 4e74de1913..a7755baf58 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -494,7 +494,14 @@ async def receive_request( wallet = session.inject(BaseWallet) conn_did_doc = await self.verify_diddoc(wallet, request.did_doc_attach) await self.store_did_document(conn_did_doc) - if request.did != conn_did_doc["id"]: + + # Special case: legacy DIDs were unqualified in request, qualified in doc + if request.did and not request.did.startswith("did:"): + did_to_check = f"did:sov:{request.did}" + else: + did_to_check = request.did + + if did_to_check != conn_did_doc["id"]: raise DIDXManagerError( ( f"Connection DID {request.did} does not match " @@ -763,16 +770,20 @@ async def accept_response( ) their_did = response.did + # Special case: legacy DIDs were unqualified in response, qualified in doc + if their_did and not their_did.startswith("did:"): + their_did = f"did:sov:{their_did}" + if response.did_doc_attach: async with self.profile.session() as session: wallet = session.inject(BaseWallet) conn_did_doc = await self.verify_diddoc( wallet, response.did_doc_attach, conn_rec.invitation_key ) - if their_did != conn_did_doc.did: + if their_did != conn_did_doc["id"]: raise DIDXManagerError( f"Connection DID {their_did} " - f"does not match DID doc id {conn_did_doc.did}" + f"does not match DID doc id {conn_did_doc['id']}" ) await self.store_did_document(conn_did_doc) else: diff --git a/aries_cloudagent/resolver/did_resolver.py b/aries_cloudagent/resolver/did_resolver.py index 1186e2c6ce..4dcdfa03ef 100644 --- a/aries_cloudagent/resolver/did_resolver.py +++ b/aries_cloudagent/resolver/did_resolver.py @@ -68,6 +68,7 @@ async def _resolve( ), timeout if timeout is not None else self.DEFAULT_TIMEOUT, ) + LOGGER.debug("Resolved DID %s with %s: %s", did, resolver, document) return resolver, document except DIDNotFound: LOGGER.debug("DID %s not found by resolver %s", did, resolver) From f6896869d5d09c5cb73810c62263a91ffed32738 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Dec 2023 14:44:52 -0500 Subject: [PATCH 3/6] test: remove now invalid regression test Signed-off-by: Daniel Bluhm --- .../connections/tests/test_base_manager.py | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/aries_cloudagent/connections/tests/test_base_manager.py b/aries_cloudagent/connections/tests/test_base_manager.py index 341d27b2e6..39437d9a74 100644 --- a/aries_cloudagent/connections/tests/test_base_manager.py +++ b/aries_cloudagent/connections/tests/test_base_manager.py @@ -40,9 +40,7 @@ from ...resolver.default.key import KeyDIDResolver from ...resolver.default.legacy_peer import LegacyPeerDIDResolver from ...resolver.did_resolver import DIDResolver -from ...storage.base import BaseStorage from ...storage.error import StorageNotFoundError -from ...storage.record import StorageRecord from ...transport.inbound.receipt import MessageReceipt from ...utils.multiformats import multibase, multicodec from ...wallet.base import DIDInfo @@ -243,70 +241,6 @@ async def test_did_key_storage(self): assert did == self.test_target_did await self.manager.remove_keys_for_did(self.test_target_did) - async def test_store_did_document_with_routing_keys(self): - """Regression test for ensuring agents with the same mediator can connect.""" - - # Replicate old behavior where routing keys could be stored multiple times - routing_key = "cK7fwfjpakMuv8QKVv2y6qouZddVw4TxZNQPUs2fFTd" - async with self.profile.session() as session: - for _ in range(3): - record = StorageRecord( - self.manager.RECORD_TYPE_DID_KEY, - routing_key, - {"did": "bogus", "key": routing_key}, - ) - storage = session.inject(BaseStorage) - await storage.add_record(record) - - # The DIDDoc class will turn the routing key into a publicKey entry. - # This is NOT the correct behavior for normalizing DID Documents. - # Unforunately, it's been doing it for a long time; to accomodate - # stored records, we need to make sure we can handle duplicate records - # where they shouldn't actually be. - # These records were never used or else we would have seen errors raised - # by find_did_for_key compaining of duplicate records. - doc_with_routing_keys = DIDDoc.deserialize( - { - "@context": "https://w3id.org/did/v1", - "publicKey": [ - { - "id": "YQwDgq9vdAbB3fk1tkeXmg#1", - "controller": "YQwDgq9vdAbB3fk1tkeXmg", - "type": "Ed25519VerificationKey2018", - "publicKeyBase58": "J81x9zdJa8CGSbTYpoYQaNrV6yv13M1Lgz4tmkNPKwZn", - }, - { - "id": "YQwDgq9vdAbB3fk1tkeXmg#1", - "controller": "YQwDgq9vdAbB3fk1tkeXmg", - "type": "Ed25519VerificationKey2018", - "publicKeyBase58": routing_key, - }, - ], - "service": [ - { - "id": "YQwDgq9vdAbB3fk1tkeXmg#IndyAgentService", - "serviceEndpoint": "https://aries-mediator-agent.vonx.io", - "type": "IndyAgent", - "priority": 0, - "recipientKeys": [ - "J81x9zdJa8CGSbTYpoYQaNrV6yv13M1Lgz4tmkNPKwZn" - ], - "routingKeys": [routing_key], - } - ], - "authentication": [ - { - "publicKey": "YQwDgq9vdAbB3fk1tkeXmg#1", - "type": "Ed25519SignatureAuthentication2018", - } - ], - "id": "YQwDgq9vdAbB3fk1tkeXmg", - } - ) - with self.assertLogs(level="WARNING") as context: - await self.manager.store_did_document(doc_with_routing_keys) - assert context.output and "Key already associated with DID" in context.output[0] - async def test_fetch_connection_targets_no_my_did(self): mock_conn = mock.MagicMock() mock_conn.my_did = None From 0358e1f85def2baef4b3371888ed2803725806d1 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Dec 2023 15:05:56 -0500 Subject: [PATCH 4/6] fix: connection manager tests Signed-off-by: Daniel Bluhm --- .../connections/v1_0/tests/test_manager.py | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py index fe09ae8d8f..a8589e458b 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -560,7 +560,7 @@ async def test_receive_request_public_did_oob_invite(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt( @@ -586,7 +586,9 @@ async def test_receive_request_public_did_oob_invite(self): ConnRecord, "retrieve_request", autospec=True ), mock.patch.object( ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id: + ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_invitation_msg_id.return_value = ConnRecord() conn_rec = await self.manager.receive_request(mock_request, receipt) assert conn_rec @@ -600,7 +602,7 @@ async def test_receive_request_public_did_unsolicited_fails(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt( @@ -626,7 +628,9 @@ async def test_receive_request_public_did_unsolicited_fails(self): ConnRecord, "retrieve_request", autospec=True ), mock.patch.object( ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id: + ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_invitation_msg_id.return_value = None conn_rec = await self.manager.receive_request(mock_request, receipt) @@ -635,7 +639,7 @@ async def test_receive_request_public_did_conn_invite(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt( @@ -667,7 +671,9 @@ async def test_receive_request_public_did_conn_invite(self): ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock(return_value=mock_connection_record), - ) as mock_conn_retrieve_by_invitation_msg_id: + ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): conn_rec = await self.manager.receive_request(mock_request, receipt) assert conn_rec @@ -676,7 +682,7 @@ async def test_receive_request_public_did_unsolicited(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt( @@ -703,7 +709,9 @@ async def test_receive_request_public_did_unsolicited(self): ConnRecord, "retrieve_request", autospec=True ), mock.patch.object( ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id: + ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_invitation_msg_id.return_value = None conn_rec = await self.manager.receive_request(mock_request, receipt) assert conn_rec @@ -743,7 +751,7 @@ async def test_receive_request_public_did_wrong_did(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = "dummy" receipt = MessageReceipt( @@ -773,7 +781,7 @@ async def test_receive_request_public_did_no_public_invites(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) @@ -794,6 +802,8 @@ async def test_receive_request_public_did_no_public_invites(self): ConnRecord, "retrieve_by_id", autospec=True ) as mock_conn_retrieve_by_id, mock.patch.object( ConnRecord, "retrieve_request", autospec=True + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): with self.assertRaises(ConnectionManagerError): await self.manager.receive_request(mock_request, receipt) @@ -803,7 +813,7 @@ async def test_receive_request_public_did_no_auto_accept(self): mock_request = mock.MagicMock() mock_request.connection = mock.MagicMock() mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock() + mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_request.connection.did_doc.did = self.test_did receipt = MessageReceipt( @@ -829,7 +839,9 @@ async def test_receive_request_public_did_no_auto_accept(self): ConnRecord, "retrieve_request", autospec=True ), mock.patch.object( ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id: + ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_invitation_msg_id.return_value = ConnRecord() conn_rec = await self.manager.receive_request(mock_request, receipt) assert conn_rec @@ -1007,7 +1019,7 @@ async def test_accept_response_find_by_thread_id(self): mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did mock_response.verify_signed_field = mock.CoroutineMock( return_value="sig_verkey" @@ -1020,6 +1032,8 @@ async def test_accept_response_find_by_thread_id(self): ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_req_id, mock.patch.object( MediationManager, "get_default_mediator", mock.CoroutineMock() + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=self.test_target_did, @@ -1039,7 +1053,7 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did mock_response.verify_signed_field = mock.CoroutineMock( return_value="sig_verkey" @@ -1055,6 +1069,8 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel ConnRecord, "retrieve_by_did", mock.CoroutineMock() ) as mock_conn_retrieve_by_did, mock.patch.object( MediationManager, "get_default_mediator", mock.CoroutineMock() + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_retrieve_by_req_id.side_effect = StorageNotFoundError() mock_conn_retrieve_by_did.return_value = mock.MagicMock( @@ -1078,7 +1094,7 @@ async def test_accept_response_not_found_by_thread_id_nor_receipt_sender_did(sel mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did receipt = MessageReceipt(sender_did=self.test_target_did) @@ -1101,7 +1117,7 @@ async def test_accept_response_find_by_thread_id_bad_state(self): mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did receipt = MessageReceipt(sender_did=self.test_target_did) @@ -1146,7 +1162,7 @@ async def test_accept_response_find_by_thread_id_did_mismatch(self): mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_did receipt = MessageReceipt(sender_did=self.test_target_did) @@ -1170,7 +1186,7 @@ async def test_accept_response_verify_invitation_key_sign_failure(self): mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did mock_response.verify_signed_field = mock.CoroutineMock(side_effect=ValueError) receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) @@ -1199,7 +1215,7 @@ async def test_accept_response_auto_send_mediation_request(self): mock_response._thread = mock.MagicMock() mock_response.connection = mock.MagicMock() mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock() + mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) mock_response.connection.did_doc.did = self.test_target_did mock_response.verify_signed_field = mock.CoroutineMock( return_value="sig_verkey" @@ -1212,6 +1228,8 @@ async def test_accept_response_auto_send_mediation_request(self): ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_req_id, mock.patch.object( MediationManager, "get_default_mediator", mock.CoroutineMock() + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=self.test_target_did, From 783895bc26e94229a4132aae675434f84c90a2af Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Dec 2023 16:03:17 -0500 Subject: [PATCH 5/6] fix: didexchange manager tests Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 4 +- .../connections/tests/test_base_manager.py | 11 + .../protocols/didexchange/v1_0/manager.py | 72 +----- .../didexchange/v1_0/tests/test_manager.py | 230 +++++------------- 4 files changed, 93 insertions(+), 224 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 0ed7e48e42..433a8334b3 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -586,7 +586,7 @@ async def get_connection_targets( def diddoc_connection_targets( self, - doc: DIDDoc, + doc: Optional[Union[DIDDoc, dict]], sender_verkey: str, their_label: Optional[str] = None, ) -> Sequence[ConnectionTarget]: @@ -597,6 +597,8 @@ def diddoc_connection_targets( sender_verkey: The verkey we are using their_label: The connection label they are using """ + if isinstance(doc, dict): + doc = DIDDoc.deserialize(doc) if not doc: raise BaseConnectionManagerError("No DIDDoc provided for connection target") if not doc.did: diff --git a/aries_cloudagent/connections/tests/test_base_manager.py b/aries_cloudagent/connections/tests/test_base_manager.py index 39437d9a74..8ce3cad699 100644 --- a/aries_cloudagent/connections/tests/test_base_manager.py +++ b/aries_cloudagent/connections/tests/test_base_manager.py @@ -1727,3 +1727,14 @@ async def test_get_endpoints(self): "localhost:8020", "10.20.30.40:5060", ) + + async def test_diddoc_connection_targets_diddoc(self): + did_doc = self.make_did_doc( + self.test_target_did, + self.test_target_verkey, + ) + targets = self.manager.diddoc_connection_targets( + did_doc, + self.test_verkey, + ) + assert isinstance(targets[0], ConnectionTarget) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index a7755baf58..a6a37c1512 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -4,9 +4,6 @@ import logging from typing import Optional, Sequence, Union -import pydid -from pydid import BaseDIDDocument as ResolvedDocument -from pydid import DIDCommService from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord @@ -18,8 +15,6 @@ from ....did.did_key import DIDKey from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder -from ....resolver.base import ResolverError -from ....resolver.did_resolver import DIDResolver from ....storage.error import StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet @@ -144,12 +139,10 @@ async def receive_invitation( # Save the invitation for later processing await conn_rec.attach_invitation(session, invitation) if not conn_rec.invitation_key and conn_rec.their_public_did: - did_document = await self.get_resolved_did_document( + targets = await self.resolve_connection_targets( conn_rec.their_public_did ) - conn_rec.invitation_key = did_document.verification_method[ - 0 - ].public_key_base58 + conn_rec.invitation_key = targets[0].recipient_keys[0] await self._route_manager.save_mediator_for_connection( self.profile, conn_rec, mediation_id=mediation_id @@ -345,12 +338,11 @@ async def create_request( 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: - qualified_did = conn_rec.their_public_did - did_document = await self.get_resolved_did_document(qualified_did) - did_url = await self.get_first_applicable_didcomm_service(did_document) - else: - did_url = 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 @@ -770,17 +762,19 @@ async def accept_response( ) their_did = response.did - # Special case: legacy DIDs were unqualified in response, qualified in doc - if their_did and not their_did.startswith("did:"): - their_did = f"did:sov:{their_did}" - if response.did_doc_attach: async with self.profile.session() as session: wallet = session.inject(BaseWallet) conn_did_doc = await self.verify_diddoc( wallet, response.did_doc_attach, conn_rec.invitation_key ) - if their_did != conn_did_doc["id"]: + # Special case: legacy DIDs were unqualified in response, qualified in doc + if their_did and not their_did.startswith("did:"): + did_to_check = f"did:sov:{their_did}" + else: + did_to_check = their_did + + if did_to_check != conn_did_doc["id"]: raise DIDXManagerError( f"Connection DID {their_did} " f"does not match DID doc id {conn_did_doc['id']}" @@ -962,46 +956,6 @@ async def verify_diddoc( return json.loads(signed_diddoc_bytes.decode()) - async def get_resolved_did_document(self, qualified_did: str) -> ResolvedDocument: - """Return resolved DID document.""" - resolver = self._profile.inject(DIDResolver) - if not qualified_did.startswith("did:"): - qualified_did = f"did:sov:{qualified_did}" - try: - doc_dict: dict = await resolver.resolve(self._profile, qualified_did) - doc = pydid.deserialize_document(doc_dict, strict=True) - return doc - except ResolverError as error: - raise DIDXManagerError( - "Failed to resolve public DID in invitation" - ) from error - - async def get_first_applicable_didcomm_service( - self, did_doc: ResolvedDocument - ) -> str: - """Return first applicable DIDComm service url with highest priority.""" - if not did_doc.service: - raise DIDXManagerError( - "Cannot connect via public DID that has no associated services" - ) - - didcomm_services = sorted( - [ - service - for service in did_doc.service - if isinstance(service, DIDCommService) - ], - key=lambda service: service.priority, - ) - - if not didcomm_services: - raise DIDXManagerError( - "Cannot connect via public DID that has no associated DIDComm services" - ) - - first_didcomm_service, *_ = didcomm_services - return first_didcomm_service.id - async def manager_error_to_problem_report( self, e: DIDXManagerError, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 32b92585dd..ed5550020d 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -7,9 +7,7 @@ from .. import manager as test_module from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache -from .....connections.base_manager import BaseConnectionManagerError from .....connections.models.conn_record import ConnRecord -from .....connections.models.connection_target import ConnectionTarget from .....connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from .....core.in_memory import InMemoryProfile from .....core.oob_processor import OobMessageProcessor @@ -19,7 +17,6 @@ from .....messaging.responder import BaseResponder, MockResponder from .....multitenant.base import BaseMultitenantManager from .....multitenant.manager import MultitenantManager -from .....resolver.base import ResolverError from .....resolver.did_resolver import DIDResolver from .....resolver.tests import DOC from .....storage.error import StorageNotFoundError @@ -208,7 +205,12 @@ async def test_receive_invitation_oob_public_did(self): test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( self.multitenant_mgr, "get_default_mediator" - ) as mock_get_default_mediator: + ) as mock_get_default_mediator, mock.patch.object( + self.manager, "resolve_connection_targets", mock.CoroutineMock() + ) as mock_resolve_targets: + mock_resolve_targets.return_value = [ + mock.MagicMock(recipient_keys=["test"]) + ] mock_get_default_mediator.return_value = None invi_rec = await self.oob_manager.create_invitation( my_endpoint="testendpoint", @@ -573,8 +575,6 @@ async def test_receive_request_explicit_public_did(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( test_module, "AttachDecorator", autospec=True @@ -583,12 +583,16 @@ async def test_receive_request_explicit_public_did(self): ) as mock_response, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock( + return_value={"id": "did:sov:" + TestConfig.test_did} + ), ), mock.patch.object( self.manager, "create_did_document", mock.CoroutineMock() ) as mock_create_did_doc, mock.patch.object( MediationManager, "prepare_request", autospec=True - ) as mock_mediation_mgr_prep_req: + ) as mock_mediation_mgr_prep_req, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_create_did_doc.return_value = mock.MagicMock( serialize=mock.MagicMock(return_value={}) ) @@ -623,9 +627,6 @@ async def test_receive_request_explicit_public_did(self): return_value=test_module.DIDPosture.PUBLIC ) - mock_did_doc.from_json = mock.MagicMock( - return_value=mock.MagicMock(did=TestConfig.test_did) - ) mock_attach_deco.data_base64 = mock.MagicMock( return_value=mock.MagicMock( data=mock.MagicMock(sign=mock.CoroutineMock()) @@ -711,8 +712,6 @@ async def test_receive_request_public_did_no_did_doc_attachment(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( test_module, "AttachDecorator", autospec=True @@ -721,7 +720,7 @@ async def test_receive_request_public_did_no_did_doc_attachment(self): ) as mock_response, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock(return_value={"id": TestConfig.test_did}), ), mock.patch.object( self.manager, "create_did_document", mock.CoroutineMock() ) as mock_create_did_doc, mock.patch.object( @@ -763,9 +762,6 @@ async def test_receive_request_public_did_no_did_doc_attachment(self): return_value=test_module.DIDPosture.PUBLIC ) - mock_did_doc.from_json = mock.MagicMock( - return_value=mock.MagicMock(did=TestConfig.test_did) - ) mock_attach_deco.data_base64 = mock.MagicMock( return_value=mock.MagicMock( data=mock.MagicMock(sign=mock.CoroutineMock()) @@ -920,11 +916,11 @@ async def test_receive_request_public_did_x_wrong_did(self): ) as mock_conn_rec_cls, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( - test_module.DIDDoc, "from_json", mock.MagicMock() - ) as mock_did_doc_from_json, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc("LjgpST2rjsoxYegQDRm7EL")), + mock.CoroutineMock(return_value={"id": "LjgpST2rjsoxYegQDRm7EL"}), + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_record = mock.MagicMock( accept=ConnRecord.ACCEPT_MANUAL, @@ -939,7 +935,6 @@ async def test_receive_request_public_did_x_wrong_did(self): mock_conn_rec_cls.retrieve_by_invitation_msg_id = mock.CoroutineMock( return_value=mock_conn_record ) - mock_did_doc_from_json.return_value = mock.MagicMock(did="wrong-did") mock_did_posture.get = mock.MagicMock( return_value=test_module.DIDPosture.PUBLIC @@ -1048,12 +1043,7 @@ async def test_receive_request_public_did_no_public_invites(self): test_module, "DIDXResponse", autospec=True ) as mock_response, mock.patch.object( self.manager, "create_did_document", mock.CoroutineMock() - ) as mock_create_did_doc, mock.patch.object( - test_module.DIDDoc, "from_json", mock.MagicMock() - ) as mock_did_doc_from_json: - mock_did_doc_from_json.return_value = mock.MagicMock( - did=TestConfig.test_did - ) + ) as mock_create_did_doc: with self.assertRaises(DIDXManagerError) as context: await self.manager.receive_request( request=mock_request, @@ -1093,8 +1083,6 @@ async def test_receive_request_public_did_no_auto_accept(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( test_module, "AttachDecorator", autospec=True @@ -1105,7 +1093,11 @@ async def test_receive_request_public_did_no_auto_accept(self): ) as mock_create_did_doc, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock( + return_value={"id": "did:sov:" + TestConfig.test_did} + ), + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_record = mock.MagicMock( accept=ConnRecord.ACCEPT_MANUAL, @@ -1125,9 +1117,6 @@ async def test_receive_request_public_did_no_auto_accept(self): return_value=test_module.DIDPosture.PUBLIC ) - mock_did_doc.from_json = mock.MagicMock( - return_value=mock.MagicMock(did=TestConfig.test_did) - ) conn_rec = await self.manager.receive_request( request=mock_request, recipient_did=TestConfig.test_did, @@ -1176,13 +1165,15 @@ async def test_receive_request_implicit_public_did_not_enabled(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock( + return_value={"id": "did:sov:" + TestConfig.test_did} + ), + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_did_posture.get = mock.MagicMock( return_value=test_module.DIDPosture.PUBLIC @@ -1242,13 +1233,15 @@ async def test_receive_request_implicit_public_did(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "DIDPosture", autospec=True ) as mock_did_posture, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock( + return_value={"id": "did:sov:" + TestConfig.test_did} + ), + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_did_posture.get = mock.MagicMock( return_value=test_module.DIDPosture.PUBLIC @@ -1326,15 +1319,17 @@ async def test_receive_request_peer_did(self): with mock.patch.object( test_module, "ConnRecord", mock.MagicMock() ) as mock_conn_rec_cls, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( test_module, "DIDXResponse", autospec=True ) as mock_response, mock.patch.object( self.manager, "verify_diddoc", - mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + mock.CoroutineMock( + return_value={"id": "did:sov:" + TestConfig.test_did} + ), + ), mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() ): mock_conn_rec_cls.retrieve_by_invitation_key = mock.CoroutineMock( return_value=mock_conn @@ -1348,9 +1343,6 @@ async def test_receive_request_peer_did(self): save=mock.CoroutineMock(), metadata_set=mock.CoroutineMock(), ) - mock_did_doc.from_json = mock.MagicMock( - return_value=mock.MagicMock(did=TestConfig.test_did) - ) mock_attach_deco.data_base64 = mock.MagicMock( return_value=mock.MagicMock( data=mock.MagicMock(sign=mock.CoroutineMock()) @@ -1422,8 +1414,6 @@ async def test_create_response(self): ) as mock_retrieve_req, mock.patch.object( conn_rec, "save", mock.CoroutineMock() ) as mock_save, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( test_module, "DIDXResponse", autospec=True @@ -1591,8 +1581,6 @@ async def test_create_response_conn_rec_my_did(self): ) as mock_retrieve_req, mock.patch.object( conn_rec, "save", mock.CoroutineMock() ) as mock_save, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( test_module, "DIDXResponse", autospec=True @@ -1641,8 +1629,6 @@ async def test_create_response_use_public_did(self): ) as mock_retrieve_req, mock.patch.object( conn_rec, "save", mock.CoroutineMock() ) as mock_save, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( test_module, "DIDXResponse", autospec=True @@ -1672,8 +1658,6 @@ async def test_create_response_use_public_did_x_no_public_did(self): ) as mock_retrieve_req, mock.patch.object( conn_rec, "save", mock.CoroutineMock() ) as mock_save, mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, mock.patch.object( test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, mock.patch.object( test_module, "DIDXResponse", autospec=True @@ -1703,7 +1687,11 @@ async def test_accept_response_find_by_thread_id(self): data=mock.MagicMock( verify=mock.CoroutineMock(return_value=True), signed=mock.MagicMock( - decode=mock.MagicMock(return_value=json.dumps({"dummy": "did-doc"})) + decode=mock.MagicMock( + return_value=json.dumps( + {"id": "did:sov:" + TestConfig.test_target_did} + ) + ) ), ) ) @@ -1720,11 +1708,8 @@ async def test_accept_response_find_by_thread_id(self): ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_id, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser: - mock_did_doc_deser.return_value = mock.MagicMock( - did=TestConfig.test_target_did - ) + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=TestConfig.test_target_did, did_doc_attach=mock.MagicMock( @@ -1759,7 +1744,11 @@ async def test_accept_response_find_by_thread_id_auto_disclose_features(self): data=mock.MagicMock( verify=mock.CoroutineMock(return_value=True), signed=mock.MagicMock( - decode=mock.MagicMock(return_value=json.dumps({"dummy": "did-doc"})) + decode=mock.MagicMock( + return_value=json.dumps( + {"id": "did:sov:" + TestConfig.test_target_did} + ) + ) ), ) ) @@ -1777,13 +1766,10 @@ async def test_accept_response_find_by_thread_id_auto_disclose_features(self): ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_id, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser, mock.patch.object( V20DiscoveryMgr, "proactive_disclose_features", mock.CoroutineMock() - ) as mock_proactive_disclose_features: - mock_did_doc_deser.return_value = mock.MagicMock( - did=TestConfig.test_target_did - ) + ) as mock_proactive_disclose_features, mock.patch.object( + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=TestConfig.test_target_did, did_doc_attach=mock.MagicMock( @@ -1819,7 +1805,11 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel data=mock.MagicMock( verify=mock.CoroutineMock(return_value=True), signed=mock.MagicMock( - decode=mock.MagicMock(return_value=json.dumps({"dummy": "did-doc"})) + decode=mock.MagicMock( + return_value=json.dumps( + {"id": "did:sov:" + TestConfig.test_target_did} + ) + ) ), ) ) @@ -1833,11 +1823,8 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_did", mock.CoroutineMock() ) as mock_conn_retrieve_by_did, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser: - mock_did_doc_deser.return_value = mock.MagicMock( - did=TestConfig.test_target_did - ) + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_req_id.side_effect = StorageNotFoundError() mock_conn_retrieve_by_did.return_value = mock.MagicMock( did=TestConfig.test_target_did, @@ -1934,13 +1921,8 @@ async def test_accept_response_find_by_thread_id_no_did_doc_attached(self): ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_id, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser, mock.patch.object( self.manager, "record_did", mock.CoroutineMock() ): - mock_did_doc_deser.return_value = mock.MagicMock( - did=TestConfig.test_target_did - ) mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=TestConfig.test_target_did, state=ConnRecord.State.REQUEST.rfc23, @@ -1975,13 +1957,8 @@ async def test_accept_response_find_by_thread_id_no_did_doc_attached_no_did(self ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_id, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser, mock.patch.object( self.manager, "record_did", mock.CoroutineMock() ): - mock_did_doc_deser.return_value = mock.MagicMock( - did=TestConfig.test_target_did - ) mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=TestConfig.test_target_did, state=ConnRecord.State.REQUEST.rfc23, @@ -2006,7 +1983,11 @@ async def test_accept_response_find_by_thread_id_did_mismatch(self): data=mock.MagicMock( verify=mock.CoroutineMock(return_value=True), signed=mock.MagicMock( - decode=mock.MagicMock(return_value=json.dumps({"dummy": "did-doc"})) + decode=mock.MagicMock( + return_value=json.dumps( + {"id": "did:sov:" + TestConfig.test_did} + ) + ) ), ) ) @@ -2020,9 +2001,8 @@ async def test_accept_response_find_by_thread_id_did_mismatch(self): ) as mock_conn_retrieve_by_req_id, mock.patch.object( ConnRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_conn_retrieve_by_id, mock.patch.object( - DIDDoc, "deserialize", mock.MagicMock() - ) as mock_did_doc_deser: - mock_did_doc_deser.return_value = mock.MagicMock(did=TestConfig.test_did) + self.manager, "store_did_document", mock.CoroutineMock() + ): mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( did=TestConfig.test_target_did, did_doc_attach=mock.MagicMock( @@ -2163,81 +2143,3 @@ async def test_receive_problem_report_x_unrecognized_code(self): with self.assertRaises(DIDXManagerError) as context: await self.manager.receive_problem_report(mock_conn, report) assert "unrecognized problem report" in str(context.exception) - - async def test_create_did_document(self): - did_info = DIDInfo( - TestConfig.test_did, - TestConfig.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - - did_doc = await self.manager.create_did_document( - did_info=did_info, - svc_endpoints=[TestConfig.test_endpoint], - ) - - async def test_did_key_storage(self): - did_info = DIDInfo( - TestConfig.test_did, - TestConfig.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - - did_doc = self.make_did_doc( - did=TestConfig.test_target_did, verkey=TestConfig.test_target_verkey - ) - - await self.manager.add_key_for_did( - did=TestConfig.test_target_did, key=TestConfig.test_target_verkey - ) - - did = await self.manager.find_did_for_key(key=TestConfig.test_target_verkey) - assert did == TestConfig.test_target_did - await self.manager.remove_keys_for_did(TestConfig.test_target_did) - - async def test_diddoc_connection_targets_diddoc(self): - did_doc = self.make_did_doc( - TestConfig.test_target_did, - TestConfig.test_target_verkey, - ) - targets = self.manager.diddoc_connection_targets( - did_doc, - TestConfig.test_verkey, - ) - assert isinstance(targets[0], ConnectionTarget) - - async def test_diddoc_connection_targets_diddoc_underspecified(self): - with self.assertRaises(BaseConnectionManagerError): - self.manager.diddoc_connection_targets(None, TestConfig.test_verkey) - - x_did_doc = DIDDoc(did=None) - with self.assertRaises(BaseConnectionManagerError): - self.manager.diddoc_connection_targets(x_did_doc, TestConfig.test_verkey) - - x_did_doc = self.make_did_doc( - did=TestConfig.test_target_did, verkey=TestConfig.test_target_verkey - ) - x_did_doc._service = {} - with self.assertRaises(BaseConnectionManagerError): - self.manager.diddoc_connection_targets(x_did_doc, TestConfig.test_verkey) - - async def test_resolve_did_document_error(self): - public_did_info = None - async with self.profile.session() as session: - await session.wallet.create_public_did( - SOV, - ED25519, - ) - public_did_info = await session.wallet.get_public_did() - with mock.patch.object( - self.resolver, - "resolve", - mock.CoroutineMock(side_effect=ResolverError()), - ): - with self.assertRaises(DIDXManagerError) as ctx: - await self.manager.get_resolved_did_document(public_did_info.did) - assert "Failed to resolve public DID in invitation" in str(ctx.exception) From e498c2672d1bb682bf05254aabe84be1d59b1823 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Dec 2023 18:32:31 -0500 Subject: [PATCH 6/6] fix: unused import Signed-off-by: Daniel Bluhm --- aries_cloudagent/protocols/didexchange/v1_0/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index a6a37c1512..55d68ecb57 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -8,7 +8,6 @@ from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord from ....connections.models.connection_target import ConnectionTarget -from ....connections.models.diddoc import DIDDoc from ....core.error import BaseError from ....core.oob_processor import OobMessageProcessor from ....core.profile import Profile