From 88dda1e99f8974e28868846aba471cb08c8193c7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 25 May 2022 16:21:19 -0400 Subject: [PATCH 01/37] feat: add update keylist for connection method and route Signed-off-by: Daniel Bluhm --- .../coordinate_mediation/v1_0/manager.py | 50 +++++++++++++++++-- .../coordinate_mediation/v1_0/routes.py | 41 ++++++++++++++- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py index 8fb2d2a449..79c9f90d19 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py @@ -1,24 +1,23 @@ """Manager for Mediation coordination.""" import json import logging - from typing import Optional, Sequence, Tuple +from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile, ProfileSession +from ....messaging.responder import BaseResponder from ....storage.base import BaseStorage from ....storage.error import StorageNotFoundError from ....storage.record import StorageRecord -from ....wallet.key_type import KeyType -from ....wallet.did_method import DIDMethod from ....wallet.base import BaseWallet from ....wallet.did_info import DIDInfo - +from ....wallet.did_method import DIDMethod +from ....wallet.key_type import KeyType from ...routing.v1_0.manager import RoutingManager from ...routing.v1_0.models.route_record import RouteRecord from ...routing.v1_0.models.route_update import RouteUpdate from ...routing.v1_0.models.route_updated import RouteUpdated - from .messages.inner.keylist_key import KeylistKey from .messages.inner.keylist_query_paginate import KeylistQueryPaginate from .messages.inner.keylist_update_rule import KeylistUpdateRule @@ -485,6 +484,47 @@ async def prepare_keylist_query( ) return message + async def update_keylist_for_connection( + self, conn_record: ConnRecord, mediation_record: MediationRecord + ) -> Optional[KeylistUpdate]: + """Update the mediator with keys created for the connection. + + If the connection is in invitation received state, create the + connection keys and update. + """ + if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( + ConnRecord.Role.REQUESTER + ) or conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( + ConnRecord.Role.RESPONDER + ): + if not conn_record.my_did: + async with self._profile.session() as session: + wallet = session.inject(BaseWallet) + # Create new DID for connection + my_info = await wallet.create_local_did( + DIDMethod.SOV, KeyType.ED25519 + ) + conn_record.my_did = my_info.did + await conn_record.save(session, reason="Connection my did created") + else: + async with self._profile.session() as session: + wallet = session.inject(BaseWallet) + my_info = await wallet.get_local_did(conn_record.my_did) + + keylist_update = await self.add_key(my_info.verkey) + if conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( + ConnRecord.Role.RESPONDER + ): + keylist_update = await self.remove_key( + conn_record.invitation_key, keylist_update + ) + responder = self._profile.inject(BaseResponder) + await responder.send( + keylist_update, connection_id=mediation_record.connection_id + ) + return keylist_update + return None + async def add_key( self, recipient_key: str, message: Optional[KeylistUpdate] = None ) -> KeylistUpdate: diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 3435174712..aed2df63ec 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -495,7 +495,7 @@ async def set_default_mediator(request: web.BaseRequest): mediator_mgr = MediationManager(context.profile) await mediator_mgr.set_default_mediator_by_id(mediation_id=mediation_id) default_mediator = await mediator_mgr.get_default_mediator() - results = default_mediator.serialize() + results = default_mediator.serialize() if default_mediator else {} except (StorageError, BaseModelError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response(results, status=201) @@ -510,12 +510,49 @@ async def clear_default_mediator(request: web.BaseRequest): mediator_mgr = MediationManager(context.profile) default_mediator = await mediator_mgr.get_default_mediator() await mediator_mgr.clear_default_mediator() - results = default_mediator.serialize() + results = default_mediator.serialize() if default_mediator else {} except (StorageError, BaseModelError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response(results, status=201) +@docs(tags=["mediation"], summary="Update keylist for a connection") +@match_info_schema(ConnectionsConnIdMatchInfoSchema()) +@request_schema(MediationIdMatchInfoSchema(), 200) +@response_schema(KeylistUpdateSchema()) +async def update_keylist_for_connection(request: web.BaseRequest): + """Update keylist for a connection.""" + context: AdminRequestContext = request["context"] + body = await request.json() + mediation_id = body.get("mediation_id") + connection_id = request.match_info["conn_id"] + try: + mediation_mgr = MediationManager(context.profile) + mediation_id = mediation_id or await mediation_mgr.get_default_mediator() + if not mediation_id: + raise web.HTTPBadRequest( + reason="No mediation_id specified and no default mediator" + ) + + async with context.session() as session: + mediation_record = await MediationRecord.retrieve_by_id( + session, mediation_id + ) + connection_record = await ConnRecord.retrieve_by_id(session, connection_id) + + keylist_update = await mediation_mgr.update_keylist_for_connection( + connection_record, mediation_record + ) + + results = keylist_update.serialize() if keylist_update else {} + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except (StorageError, BaseModelError) as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + return web.json_response(results, status=201) + + async def register(app: web.Application): """Register routes.""" From 8a349205e2bc483d8f0e029000bf3fea15283b21 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 09:16:56 -0400 Subject: [PATCH 02/37] refactor: move mediation record retrieval util Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/util.py | 36 ------------------- .../protocols/connections/v1_0/manager.py | 31 ++++++++-------- .../coordinate_mediation/v1_0/manager.py | 26 ++++++++++++++ 3 files changed, 42 insertions(+), 51 deletions(-) delete mode 100644 aries_cloudagent/connections/util.py diff --git a/aries_cloudagent/connections/util.py b/aries_cloudagent/connections/util.py deleted file mode 100644 index e688643f9d..0000000000 --- a/aries_cloudagent/connections/util.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Class for providing base utilities for Mediator support.""" - -from ..protocols.coordinate_mediation.v1_0.manager import MediationManager -from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( - MediationRecord, -) -from ..core.profile import Profile - -from .base_manager import BaseConnectionManagerError - - -async def mediation_record_if_id( - profile: Profile, mediation_id: str = None, or_default: bool = False -): - """Validate mediation and return record. - - If mediation_id is not None, - validate mediation record state and return record - else, return None - """ - mediation_record = None - if mediation_id: - async with profile.session() as session: - mediation_record = await MediationRecord.retrieve_by_id( - session, mediation_id - ) - elif or_default: - mediation_record = await MediationManager(profile).get_default_mediator() - - if mediation_record: - if mediation_record.state != MediationRecord.STATE_GRANTED: - raise BaseConnectionManagerError( - "Mediation is not granted for mediation identified by " - f"{mediation_record.mediation_id}" - ) - return mediation_record diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index e833327f56..6405682aad 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -285,7 +285,6 @@ async def receive_invitation( auto_accept: bool = None, alias: str = None, mediation_id: str = None, - mediation_record: MediationRecord = None, ) -> ConnRecord: """ Create a new connection record to track a received invitation. @@ -379,22 +378,9 @@ async def create_request( """ keylist_updates = None - - # Mediation Record can still be None after this operation if no - # mediation id passed and no default - mediation_record = await mediation_record_if_id( - self.profile, - mediation_id, - or_default=True, - ) - + my_info = None multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) wallet_id = self.profile.settings.get("wallet.id") - base_mediation_record = None - - if multitenant_mgr and wallet_id: - base_mediation_record = await multitenant_mgr.get_default_mediator() - my_info = None if connection.my_did: async with self.profile.session() as session: @@ -406,6 +392,7 @@ async def create_request( # Create new DID for connection my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) connection.my_did = my_info.did + mediation_mgr = MediationManager(self.profile) keylist_updates = await mediation_mgr.add_key( my_info.verkey, keylist_updates @@ -425,6 +412,20 @@ async def create_request( my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + # Retrieve MediationRecords for constructing DID Document + mediation_mgr = MediationManager(self.profile) + + # Mediation Record can still be None after this operation if no + # mediation id passed and no default + mediation_record = await mediation_mgr.mediation_record_if_id( + mediation_id, + or_default=True, + ) + + base_mediation_record = None + if multitenant_mgr and wallet_id: + base_mediation_record = await multitenant_mgr.get_default_mediator() + did_doc = await self.create_did_document( my_info, connection.inbound_connection_id, diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py index 79c9f90d19..7d310667ce 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py @@ -650,4 +650,30 @@ async def get_my_keylist( async with self._profile.session() as session: return await RouteRecord.query(session, tag_filter) + async def mediation_record_if_id( + self, mediation_id: Optional[str] = None, or_default: bool = False + ): + """Validate mediation and return record. + + If mediation_id is not None, + validate mediation record state and return record + else, return None + """ + mediation_record = None + if mediation_id: + async with self._profile.session() as session: + mediation_record = await MediationRecord.retrieve_by_id( + session, mediation_id + ) + elif or_default: + mediation_record = await MediationManager(profile).get_default_mediator() + + if mediation_record: + if mediation_record.state != MediationRecord.STATE_GRANTED: + raise BaseConnectionManagerError( + "Mediation is not granted for mediation identified by " + f"{mediation_record.mediation_id}" + ) + return mediation_record + # }}} From 36e38372516c109b46c730a4e162e2cb7fa9c0cd Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 21:57:11 -0400 Subject: [PATCH 03/37] feat: add route managers Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/route_manager.py | 100 +++++++++ .../v1_0/route_manager.py | 199 ++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 aries_cloudagent/multitenant/route_manager.py create mode 100644 aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py new file mode 100644 index 0000000000..25af722ad5 --- /dev/null +++ b/aries_cloudagent/multitenant/route_manager.py @@ -0,0 +1,100 @@ +"""Multitenancy route manager.""" + + +import logging +from typing import List, Optional, Tuple +from aries_cloudagent.core.profile import Profile +from aries_cloudagent.messaging.responder import BaseResponder + +from aries_cloudagent.protocols.coordinate_mediation.v1_0.manager import ( + MediationManager, +) +from aries_cloudagent.protocols.routing.v1_0.manager import RoutingManager +from aries_cloudagent.protocols.routing.v1_0.models.route_record import RouteRecord +from aries_cloudagent.storage.error import StorageNotFoundError + +from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( + MediationRecord, +) +from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager + + +LOGGER = logging.getLogger(__name__) + + +class MultitenantRouteManager(RouteManager): + """Multitenancy route manager.""" + + def __init__(self, root_profile: Profile, sub_profile: Profile, wallet_id: str): + self.root_profile = root_profile + self.wallet_id = wallet_id + super().__init__(sub_profile) + + @property + def sub_profile(self) -> Profile: + return self.profile + + async def get_base_wallet_mediator(self) -> Optional[MediationRecord]: + return await MediationManager(self.root_profile).get_default_mediator() + + async def _route_for_key( + self, + recipient_key: str, + mediation_record: Optional[MediationRecord] = None, + *, + skip_if_exists: bool = False, + replace_key: Optional[str] = None, + ): + LOGGER.info( + f"Add route record for recipient {recipient_key} to wallet {self.wallet_id}" + ) + routing_mgr = RoutingManager(self.root_profile) + mediation_mgr = MediationManager(self.root_profile) + # Passed in mediation_record is ignored altogether. + # Only the base mediator needs updates. + mediation_record = await self.get_base_wallet_mediator() + + if skip_if_exists: + try: + async with self.root_profile.session() as session: + await RouteRecord.retrieve_by_recipient_key(session, recipient_key) + + # If no error is thrown, it means there is already a record + return None + except (StorageNotFoundError): + pass + + await routing_mgr.create_route_record( + recipient_key=recipient_key, internal_wallet_id=self.wallet_id + ) + + # External mediation + keylist_updates = None + if mediation_record: + keylist_updates = await mediation_mgr.add_key(recipient_key) + if replace_key: + keylist_updates = await mediation_mgr.remove_key(replace_key) + + responder = self.root_profile.inject(BaseResponder) + await responder.send( + keylist_updates, connection_id=mediation_record.connection_id + ) + + return keylist_updates + + async def routing_info( + self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None + ) -> Tuple[List[str], str]: + routing_keys = [] + + base_mediation_record = await self.get_base_wallet_mediator() + + if base_mediation_record: + routing_keys = base_mediation_record.routing_keys + my_endpoint = base_mediation_record.endpoint + + if mediation_record: + routing_keys = [*routing_keys, *mediation_record.routing_keys] + my_endpoint = mediation_record.endpoint + + return routing_keys, my_endpoint diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py new file mode 100644 index 0000000000..480401e7f1 --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -0,0 +1,199 @@ +"""Route manager. + +Set up routing for newly formed connections. +""" + + +from abc import ABC, abstractmethod +from typing import List, Optional, Tuple + +from aries_cloudagent.protocols.routing.v1_0.models.route_record import RouteRecord +from aries_cloudagent.storage.error import StorageNotFoundError + +from ....connections.models.conn_record import ConnRecord +from ....core.profile import Profile +from ....messaging.responder import BaseResponder +from ....wallet.base import BaseWallet +from ....wallet.did_info import DIDInfo +from ....wallet.did_method import DIDMethod +from ....wallet.key_type import KeyType +from .manager import MediationManager +from .models.mediation_record import MediationRecord + + +class RouteManagerError(Exception): + """Raised on error from route manager.""" + + +class RouteManager(ABC): + """Base Route Manager.""" + + def __init__(self, profile: Profile): + self.profile = profile + + async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: + if not conn_record.my_did: + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + # Create new DID for connection + my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) + conn_record.my_did = my_info.did + await conn_record.save(session, reason="Connection my did created") + else: + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + my_info = await wallet.get_local_did(conn_record.my_did) + + return my_info + + async def mediation_record_if_id( + self, mediation_id: Optional[str] = None, or_default: bool = False + ): + """Validate mediation and return record. + + If mediation_id is not None, + validate mediation record state and return record + else, return None + """ + mediation_record = None + if mediation_id: + async with self.profile.session() as session: + mediation_record = await MediationRecord.retrieve_by_id( + session, mediation_id + ) + elif or_default: + mediation_record = await MediationManager( + self.profile + ).get_default_mediator() + + if mediation_record: + if mediation_record.state != MediationRecord.STATE_GRANTED: + raise RouteManagerError( + "Mediation is not granted for mediation identified by " + f"{mediation_record.mediation_id}" + ) + return mediation_record + + @abstractmethod + async def _route_for_key( + self, + recipient_key: str, + mediation_record: Optional[MediationRecord] = None, + *, + skip_if_exists: bool = False, + replace_key: Optional[str] = None, + ): + """Route a key.""" + + async def route_connection_as_invitee( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + ): + """Set up routing for a new connection when we are the invitee.""" + my_info = await self.get_or_create_my_did(conn_record) + return await self._route_for_key( + my_info.verkey, mediation_record, skip_if_exists=True + ) + + async def route_connection_as_inviter( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + ): + """Set up routing for a new connection when we are the inviter.""" + my_info = await self.get_or_create_my_did(conn_record) + return await self._route_for_key( + my_info.verkey, + mediation_record, + replace_key=conn_record.invitation_key, + skip_if_exists=True, + ) + + async def route_invitation( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + ): + """Set up routing for receiving a response to an invitation.""" + if mediation_record: + # Save that this invitation was created with mediation + async with self.profile.session() as session: + await conn_record.metadata_set( + session, + MediationManager.METADATA_KEY, + {MediationManager.METADATA_ID: mediation_record.mediation_id}, + ) + + if conn_record.invitation_key: + return await self._route_for_key( + conn_record.invitation_key, mediation_record, skip_if_exists=True + ) + + raise ValueError("Expected connection to have invitation_key") + + async def route_public_did(self, verkey: str): + """Establish routing for a public DID.""" + return await self._route_for_key(verkey, skip_if_exists=True) + + async def route_static( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + ): + my_info = await self.get_or_create_my_did(conn_record) + return await self._route_for_key( + my_info.verkey, mediation_record, skip_if_exists=True + ) + + @abstractmethod + async def routing_info( + self, + my_endpoint: str, + mediation_record: Optional[MediationRecord] = None, + ) -> Tuple[List[str], str]: + """Retrieve routing keys.""" + + +class CoordinateMediationV1RouteManager(RouteManager): + """Manage routes using Coordinate Mediation protocol.""" + + async def _route_for_key( + self, + recipient_key: str, + mediation_record: Optional[MediationRecord] = None, + *, + skip_if_exists: bool = False, + replace_key: Optional[str] = None, + ): + if not mediation_record: + return None + + if skip_if_exists: + try: + async with self.profile.session() as session: + await RouteRecord.retrieve_by_recipient_key(session, recipient_key) + + return None + except StorageNotFoundError: + pass + + # Keylist update is idempotent, skip_if_exists ignored + mediation_mgr = MediationManager(self.profile) + keylist_update = await mediation_mgr.add_key(recipient_key) + if replace_key: + keylist_update = await mediation_mgr.remove_key(replace_key) + + responder = self.profile.inject(BaseResponder) + await responder.send( + keylist_update, connection_id=mediation_record.connection_id + ) + return keylist_update + + async def routing_info( + self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None + ) -> Tuple[List[str], str]: + if mediation_record: + return mediation_record.routing_keys, mediation_record.endpoint + + return [], my_endpoint From 3d56a0eca5f17cfa4ccc33263a83cc744adf52d6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:01:22 -0400 Subject: [PATCH 04/37] feat: integrate route manager in base classes Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 16 +++++++++ aries_cloudagent/multitenant/base.py | 36 ++++++++++---------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index bd5e281694..9a1df8e393 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -18,12 +18,17 @@ from ..core.error import BaseError from ..core.profile import Profile from ..did.did_key import DIDKey +from ..multitenant.base import BaseMultitenantManager from ..protocols.connections.v1_0.messages.connection_invitation import ( ConnectionInvitation, ) from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) +from ..protocols.coordinate_mediation.v1_0.route_manager import ( + CoordinateMediationV1RouteManager, + RouteManager, +) from ..resolver.base import ResolverError from ..resolver.did_resolver import DIDResolver from ..storage.base import BaseStorage @@ -57,6 +62,17 @@ def __init__(self, profile: Profile): self._logger = logging.getLogger(__name__) self._profile = profile + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + wallet_id = profile.settings.get_str("wallet.id") + if multitenant_mgr and wallet_id: + self._route_manager: RouteManager = multitenant_mgr.get_route_manager( + profile, wallet_id + ) + else: + self._route_manager: RouteManager = CoordinateMediationV1RouteManager( + profile + ) + async def create_did_document( self, did_info: DIDInfo, diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index 18fe63a9c5..a500c88007 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -1,31 +1,28 @@ """Manager for multitenancy.""" +from abc import abstractmethod from datetime import datetime import logging -from abc import abstractmethod +from typing import List, Optional, cast import jwt -from typing import List, Optional, cast -from ..core.profile import ( - Profile, - ProfileSession, -) -from ..messaging.responder import BaseResponder from ..config.injection_context import InjectionContext -from ..wallet.models.wallet_record import WalletRecord -from ..wallet.base import BaseWallet from ..core.error import BaseError -from ..protocols.routing.v1_0.manager import RouteNotFoundError, RoutingManager -from ..protocols.routing.v1_0.models.route_record import RouteRecord -from ..transport.wire_format import BaseWireFormat -from ..storage.base import BaseStorage -from ..storage.error import StorageNotFoundError +from ..core.profile import Profile, ProfileSession +from ..messaging.responder import BaseResponder +from ..multitenant.route_manager import MultitenantRouteManager from ..protocols.coordinate_mediation.v1_0.manager import ( MediationManager, MediationRecord, ) - +from ..protocols.routing.v1_0.manager import RouteNotFoundError, RoutingManager +from ..protocols.routing.v1_0.models.route_record import RouteRecord +from ..storage.base import BaseStorage +from ..storage.error import StorageNotFoundError +from ..transport.wire_format import BaseWireFormat +from ..wallet.base import BaseWallet +from ..wallet.models.wallet_record import WalletRecord from .error import WalletKeyMissingError LOGGER = logging.getLogger(__name__) @@ -201,9 +198,9 @@ async def create_wallet( public_did_info = await wallet.get_public_did() if public_did_info: - await self.add_key( - wallet_record.wallet_id, public_did_info.verkey, skip_if_exists=True - ) + await self.get_route_manager( + profile, wallet_record.wallet_id + ).route_public_did(public_did_info.verkey) except Exception: await wallet_record.delete_record(session) raise @@ -294,6 +291,9 @@ async def remove_wallet_profile(self, profile: Profile): """ + def get_route_manager(self, sub_profile: Profile, wallet_id: str): + return MultitenantRouteManager(self._profile, sub_profile, wallet_id) + async def add_key( self, wallet_id: str, recipient_key: str, *, skip_if_exists: bool = False ): From efa8b1f932d541d5a4458e85ba8002c5c4cc9e59 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:06:36 -0400 Subject: [PATCH 05/37] refactor: connection manager uses routing manager Signed-off-by: Daniel Bluhm --- .../handlers/connection_request_handler.py | 1 - .../protocols/connections/v1_0/manager.py | 190 +++++------------- 2 files changed, 47 insertions(+), 144 deletions(-) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py index 662e0bfbee..82eb79cf81 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py @@ -38,7 +38,6 @@ async def handle(self, context: RequestContext, responder: BaseResponder): connection = await mgr.receive_request( context.message, context.message_receipt, - mediation_id=mediation_id, ) if connection.accept == ConnRecord.ACCEPT_AUTO: diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index 6405682aad..cf6301101f 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -2,14 +2,13 @@ import logging -from typing import Coroutine, Sequence, Tuple +from typing import Coroutine, Sequence, Tuple, cast from ....cache.base import BaseCache from ....config.base import InjectionError from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord from ....connections.models.connection_target import ConnectionTarget -from ....connections.util import mediation_record_if_id from ....core.error import BaseError from ....core.profile import Profile from ....messaging.responder import BaseResponder @@ -26,7 +25,6 @@ from ...routing.v1_0.manager import RoutingManager 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 .message_types import ARIES_PROTOCOL as CONN_PROTO @@ -124,18 +122,12 @@ async def create_invitation( """ # Mediation Record can still be None after this operation if no # mediation id passed and no default - mediation_record = await mediation_record_if_id( - self.profile, + mediation_record = await self._route_manager.mediation_record_if_id( mediation_id, or_default=True, ) - keylist_updates = None image_url = self.profile.context.settings.get("image_url") - # Multitenancy setup - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - wallet_id = self.profile.settings.get("wallet.id") - if not my_label: my_label = self.profile.settings.get("default_label") @@ -168,10 +160,7 @@ async def create_invitation( # Add mapping for multitenant relaying. # Mediation of public keys is not supported yet - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key( - wallet_id, public_did.verkey, skip_if_exists=True - ) + await self._route_manager.route_public_did(public_did.verkey) return None, invitation @@ -192,13 +181,6 @@ async def create_invitation( ) invitation_key = invitation_signing_key.verkey recipient_keys = [invitation_key] - mediation_mgr = MediationManager(self.profile) - keylist_updates = await mediation_mgr.add_key( - invitation_key, keylist_updates - ) - - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, invitation_key) accept = ( ConnRecord.ACCEPT_AUTO @@ -225,39 +207,11 @@ async def create_invitation( async with self.profile.session() as session: await connection.save(session, reason="Created new invitation") - routing_keys = [] - my_endpoint = my_endpoint or self.profile.settings.get("default_endpoint") - - # The base wallet can act as a mediator for all tenants - if multitenant_mgr and wallet_id: - base_mediation_record = await multitenant_mgr.get_default_mediator() - - if base_mediation_record: - routing_keys = base_mediation_record.routing_keys - my_endpoint = base_mediation_record.endpoint - - # If we use a mediator for the base wallet we don't - # need to register the key at the subwallet mediator - # because it only needs to know the key of the base mediator - # sub wallet mediator -> base wallet mediator -> agent - keylist_updates = None - if mediation_record: - routing_keys = [*routing_keys, *mediation_record.routing_keys] - my_endpoint = mediation_record.endpoint - - # Save that this invitation was created with mediation - async with self.profile.session() as session: - await connection.metadata_set( - session, - MediationManager.METADATA_KEY, - {MediationManager.METADATA_ID: mediation_record.mediation_id}, - ) - - if keylist_updates: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) + await self._route_manager.route_invitation(connection, mediation_record) + routing_keys, my_endpoint = await self._route_manager.routing_info( + my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), + mediation_record, + ) # Create connection invitation message # Note: Need to split this into two stages to support inbound routing of invites @@ -377,11 +331,18 @@ async def create_request( """ - keylist_updates = None - my_info = None + mediation_record = await self._route_manager.mediation_record_if_id( + mediation_id, + or_default=True, + ) + multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) wallet_id = self.profile.settings.get("wallet.id") + base_mediation_record = None + if multitenant_mgr and wallet_id: + base_mediation_record = await multitenant_mgr.get_default_mediator() + if connection.my_did: async with self.profile.session() as session: wallet = session.inject(BaseWallet) @@ -393,14 +354,10 @@ async def create_request( my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) connection.my_did = my_info.did - mediation_mgr = MediationManager(self.profile) - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) + # Idempotent; if routing has already been set up, no action taken + await self._route_manager.route_connection_as_invitee( + connection, mediation_record + ) # Create connection request message if my_endpoint: @@ -412,19 +369,8 @@ async def create_request( my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) - # Retrieve MediationRecords for constructing DID Document - mediation_mgr = MediationManager(self.profile) - # Mediation Record can still be None after this operation if no # mediation id passed and no default - mediation_record = await mediation_mgr.mediation_record_if_id( - mediation_id, - or_default=True, - ) - - base_mediation_record = None - if multitenant_mgr and wallet_id: - base_mediation_record = await multitenant_mgr.get_default_mediator() did_doc = await self.create_did_document( my_info, @@ -451,21 +397,12 @@ async def create_request( async with self.profile.session() as session: await connection.save(session, reason="Created connection request") - # Notify mediator of keylist changes - if keylist_updates and mediation_record: - # send a update keylist message with new recipient keys. - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - return request async def receive_request( self, request: ConnectionRequest, receipt: MessageReceipt, - mediation_id: str = None, ) -> ConnRecord: """ Receive and store a connection request. @@ -484,16 +421,10 @@ async def receive_request( settings=self.profile.settings, ) - mediation_mgr = MediationManager(self.profile) - keylist_updates = None connection = None connection_key = None my_info = None - # Multitenancy setup - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - wallet_id = self.profile.settings.get("wallet.id") - # Determine what key will need to sign the response if receipt.recipient_did_public: async with self.profile.session() as session: @@ -533,9 +464,6 @@ async def receive_request( my_info = await wallet.create_local_did( DIDMethod.SOV, KeyType.ED25519 ) - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) new_connection = ConnRecord( invitation_key=connection_key, @@ -563,14 +491,6 @@ async def receive_request( connection = new_connection - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) - else: - # remove key from mediator keylist - keylist_updates = await mediation_mgr.remove_key( - connection_key, keylist_updates - ) conn_did_doc = request.connection.did_doc if not conn_did_doc: raise ConnectionManagerError( @@ -597,14 +517,7 @@ async def receive_request( async with self.profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) - # send update-keylist message with new recipient keys - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) async with self.profile.session() as session: connection = await ConnRecord.retrieve_by_invitation_msg_id( session=session, @@ -634,14 +547,6 @@ async def receive_request( # Attach the connection request so it can be found and responded to await connection.attach_request(session, request) - # Send keylist updates to mediator - mediation_record = await mediation_record_if_id(self.profile, mediation_id) - if keylist_updates and mediation_record: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - return connection async def create_response( @@ -668,14 +573,15 @@ async def create_response( settings=self.profile.settings, ) - keylist_updates = None - mediation_record = await mediation_record_if_id(self.profile, mediation_id) + mediation_record = await self._route_manager.mediation_record_if_id( + mediation_id + ) # Multitenancy setup multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) wallet_id = self.profile.settings.get("wallet.id") - base_mediation_record = None + base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator() @@ -689,6 +595,7 @@ async def create_response( async with self.profile.session() as session: request = await connection.retrieve_request(session) + if connection.my_did: async with self.profile.session() as session: wallet = session.inject(BaseWallet) @@ -698,13 +605,11 @@ async def create_response( wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) connection.my_did = my_info.did - mediation_mgr = MediationManager(self.profile) - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) + + # Idempotent; if routing has already been set up, no action taken + await self._route_manager.route_connection_as_inviter( + connection, mediation_record + ) # Create connection response message if my_endpoint: @@ -746,13 +651,6 @@ async def create_response( log_params={"response": response}, ) - # Update mediator if necessary - if keylist_updates and mediation_record: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - # TODO It's possible the mediation request sent here might arrive # before the connection response. This would result in an error condition # difficult to accomodate for without modifying handlers for trust ping @@ -901,6 +799,7 @@ async def create_static_connection( their_endpoint: str = None, their_label: str = None, alias: str = None, + mediation_id: str = None, ) -> Tuple[DIDInfo, DIDInfo, ConnRecord]: """ Register a new static connection (for use by the test suite). @@ -918,11 +817,6 @@ async def create_static_connection( Tuple: my DIDInfo, their DIDInfo, new `ConnRecord` instance """ - # Multitenancy setup - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - wallet_id = self.profile.settings.get("wallet.id") - base_mediation_record = None - async with self.profile.session() as session: wallet = session.inject(BaseWallet) # seed and DID optional @@ -962,20 +856,30 @@ async def create_static_connection( connection_id=connection.connection_id ) - # Add mapping for multitenant relaying / mediation + # Routing + mediation_record = await self._route_manager.mediation_record_if_id( + mediation_id, or_default=True + ) + + multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) + wallet_id = self.profile.settings.get("wallet.id") + + base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator() - await multitenant_mgr.add_key(wallet_id, my_info.verkey) + + await self._route_manager.route_static(connection, mediation_record) # Synthesize their DID doc did_doc = await self.create_did_document( their_info, None, [their_endpoint or ""], - mediation_records=[base_mediation_record] - if base_mediation_record - else None, + mediation_records=list( + filter(None, [base_mediation_record, mediation_record]) + ), ) + await self.store_did_document(did_doc) return my_info, their_info, connection From 2d3dfbc183e2e3c0c7c2789b3483016070706182 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:15:54 -0400 Subject: [PATCH 06/37] refactor: route_connection, utils on route manager Signed-off-by: Daniel Bluhm --- .../coordinate_mediation/v1_0/manager.py | 70 +------------------ .../v1_0/route_manager.py | 17 +++++ 2 files changed, 18 insertions(+), 69 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py index 7d310667ce..31b9921644 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py @@ -3,10 +3,9 @@ import logging from typing import Optional, Sequence, Tuple -from ....connections.models.conn_record import ConnRecord + from ....core.error import BaseError from ....core.profile import Profile, ProfileSession -from ....messaging.responder import BaseResponder from ....storage.base import BaseStorage from ....storage.error import StorageNotFoundError from ....storage.record import StorageRecord @@ -484,47 +483,6 @@ async def prepare_keylist_query( ) return message - async def update_keylist_for_connection( - self, conn_record: ConnRecord, mediation_record: MediationRecord - ) -> Optional[KeylistUpdate]: - """Update the mediator with keys created for the connection. - - If the connection is in invitation received state, create the - connection keys and update. - """ - if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( - ConnRecord.Role.REQUESTER - ) or conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( - ConnRecord.Role.RESPONDER - ): - if not conn_record.my_did: - async with self._profile.session() as session: - wallet = session.inject(BaseWallet) - # Create new DID for connection - my_info = await wallet.create_local_did( - DIDMethod.SOV, KeyType.ED25519 - ) - conn_record.my_did = my_info.did - await conn_record.save(session, reason="Connection my did created") - else: - async with self._profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.get_local_did(conn_record.my_did) - - keylist_update = await self.add_key(my_info.verkey) - if conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( - ConnRecord.Role.RESPONDER - ): - keylist_update = await self.remove_key( - conn_record.invitation_key, keylist_update - ) - responder = self._profile.inject(BaseResponder) - await responder.send( - keylist_update, connection_id=mediation_record.connection_id - ) - return keylist_update - return None - async def add_key( self, recipient_key: str, message: Optional[KeylistUpdate] = None ) -> KeylistUpdate: @@ -650,30 +608,4 @@ async def get_my_keylist( async with self._profile.session() as session: return await RouteRecord.query(session, tag_filter) - async def mediation_record_if_id( - self, mediation_id: Optional[str] = None, or_default: bool = False - ): - """Validate mediation and return record. - - If mediation_id is not None, - validate mediation record state and return record - else, return None - """ - mediation_record = None - if mediation_id: - async with self._profile.session() as session: - mediation_record = await MediationRecord.retrieve_by_id( - session, mediation_id - ) - elif or_default: - mediation_record = await MediationManager(profile).get_default_mediator() - - if mediation_record: - if mediation_record.state != MediationRecord.STATE_GRANTED: - raise BaseConnectionManagerError( - "Mediation is not granted for mediation identified by " - f"{mediation_record.mediation_id}" - ) - return mediation_record - # }}} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 480401e7f1..e26405f1fc 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -110,6 +110,23 @@ async def route_connection_as_inviter( skip_if_exists=True, ) + async def route_connection( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + ): + if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( + ConnRecord.Role.REQUESTER + ): + return await self.route_connection_as_invitee(conn_record, mediation_record) + + if conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( + ConnRecord.Role.RESPONDER + ): + return await self.route_connection_as_inviter(conn_record, mediation_record) + + return None + async def route_invitation( self, conn_record: ConnRecord, From 2ca17d150ec997beec89e75fc36a4cbe9521ab15 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:41:36 -0400 Subject: [PATCH 07/37] fix: retrieve previously used mediator when possible Signed-off-by: Daniel Bluhm --- .../protocols/connections/v1_0/manager.py | 19 ++++----- .../v1_0/route_manager.py | 40 ++++++++++++++++--- .../coordinate_mediation/v1_0/routes.py | 31 ++++++++------ 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index cf6301101f..3f4e71c0b7 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -1,7 +1,6 @@ """Classes to manage connections.""" import logging - from typing import Coroutine, Sequence, Tuple, cast from ....cache.base import BaseCache @@ -16,17 +15,15 @@ from ....storage.error import StorageError, StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet -from ....wallet.did_info import DIDInfo from ....wallet.crypto import create_keypair, seed_to_did -from ....wallet.key_type import KeyType +from ....wallet.did_info import DIDInfo from ....wallet.did_method import DIDMethod from ....wallet.error import WalletNotFoundError +from ....wallet.key_type import KeyType from ....wallet.util import bytes_to_b58 -from ...routing.v1_0.manager import RoutingManager from ...coordinate_mediation.v1_0.manager import MediationManager - from ...discovery.v2_0.manager import V20DiscoveryMgr - +from ...routing.v1_0.manager import RoutingManager from .message_types import ARIES_PROTOCOL as CONN_PROTO from .messages.connection_invitation import ConnectionInvitation from .messages.connection_request import ConnectionRequest @@ -331,7 +328,8 @@ async def create_request( """ - mediation_record = await self._route_manager.mediation_record_if_id( + mediation_record = await self._route_manager.mediation_record_for_connection( + connection, mediation_id, or_default=True, ) @@ -369,9 +367,6 @@ async def create_request( my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) - # Mediation Record can still be None after this operation if no - # mediation id passed and no default - did_doc = await self.create_did_document( my_info, connection.inbound_connection_id, @@ -573,8 +568,8 @@ async def create_response( settings=self.profile.settings, ) - mediation_record = await self._route_manager.mediation_record_if_id( - mediation_id + mediation_record = await self._route_manager.mediation_record_for_connection( + connection, mediation_id ) # Multitenancy setup diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index e26405f1fc..f189a2a6f6 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -46,6 +46,40 @@ async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: return my_info + def _validate_mediation_state(self, mediation_record: MediationRecord): + if mediation_record.state != MediationRecord.STATE_GRANTED: + raise RouteManagerError( + "Mediation is not granted for mediation identified by " + f"{mediation_record.mediation_id}" + ) + + async def mediation_record_for_connection( + self, + conn_record: ConnRecord, + mediation_id: Optional[str] = None, + or_default: bool = False, + ): + """Validate mediation and return record. + + If mediation_id is not None, + validate mediation record state and return record + else, return None + """ + mediation_record = None + async with self.profile.session() as session: + try: + mediation_record = await MediationRecord.retrieve_by_connection_id( + session, conn_record.connection_id + ) + except StorageNotFoundError: + pass + + if mediation_record: + self._validate_mediation_state(mediation_record) + return mediation_record + + return await self.mediation_record_if_id(mediation_id, or_default) + async def mediation_record_if_id( self, mediation_id: Optional[str] = None, or_default: bool = False ): @@ -67,11 +101,7 @@ async def mediation_record_if_id( ).get_default_mediator() if mediation_record: - if mediation_record.state != MediationRecord.STATE_GRANTED: - raise RouteManagerError( - "Mediation is not granted for mediation identified by " - f"{mediation_record.mediation_id}" - ) + self._validate_mediation_state(mediation_record) return mediation_record @abstractmethod diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index aed2df63ec..55a19f07c5 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -15,11 +15,10 @@ from ....messaging.models.base import BaseModelError from ....messaging.models.openapi import OpenAPISchema from ....messaging.valid import UUIDFour +from ....multitenant.base import BaseMultitenantManager from ....storage.error import StorageError, StorageNotFoundError - from ...connections.v1_0.routes import ConnectionsConnIdMatchInfoSchema from ...routing.v1_0.models.route_record import RouteRecord, RouteRecordSchema - from .manager import MediationManager, MediationManagerError from .message_types import SPEC_URI from .messages.inner.keylist_update_rule import ( @@ -31,6 +30,7 @@ from .messages.mediate_deny import MediationDenySchema from .messages.mediate_grant import MediationGrantSchema from .models.mediation_record import MediationRecord, MediationRecordSchema +from .route_manager import CoordinateMediationV1RouteManager CONNECTION_ID_SCHEMA = fields.UUID( @@ -527,20 +527,27 @@ async def update_keylist_for_connection(request: web.BaseRequest): mediation_id = body.get("mediation_id") connection_id = request.match_info["conn_id"] try: - mediation_mgr = MediationManager(context.profile) - mediation_id = mediation_id or await mediation_mgr.get_default_mediator() - if not mediation_id: - raise web.HTTPBadRequest( - reason="No mediation_id specified and no default mediator" + multitenant_mgr = context.inject_or(BaseMultitenantManager) + wallet_id = context.settings.get_str("wallet.id") + if multitenant_mgr and wallet_id: + routing_manager = multitenant_mgr.get_route_manager( + context.profile, wallet_id ) + else: + routing_manager = CoordinateMediationV1RouteManager(context.profile) async with context.session() as session: - mediation_record = await MediationRecord.retrieve_by_id( - session, mediation_id - ) connection_record = await ConnRecord.retrieve_by_id(session, connection_id) + mediation_record = await routing_manager.mediation_record_for_connection( + connection_record, mediation_id, or_default=True + ) + + if not mediation_record: + raise web.HTTPBadRequest( + reason="No mediation_id specified and no default mediator" + ) - keylist_update = await mediation_mgr.update_keylist_for_connection( + keylist_update = await routing_manager.route_connection( connection_record, mediation_record ) @@ -550,7 +557,7 @@ async def update_keylist_for_connection(request: web.BaseRequest): except (StorageError, BaseModelError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err - return web.json_response(results, status=201) + return web.json_response(results, status=200) async def register(app: web.Application): From b116c4ae76139551a6f938e92da9b4f93ad174b2 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:49:27 -0400 Subject: [PATCH 08/37] fix: broken imports Signed-off-by: Daniel Bluhm --- .../protocols/coordinate_mediation/v1_0/routes.py | 1 + .../protocols/didexchange/v1_0/manager.py | 12 +++++++----- .../protocols/out_of_band/v1_0/manager.py | 6 ++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 55a19f07c5..1c84e74ec3 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -519,6 +519,7 @@ async def clear_default_mediator(request: web.BaseRequest): @docs(tags=["mediation"], summary="Update keylist for a connection") @match_info_schema(ConnectionsConnIdMatchInfoSchema()) @request_schema(MediationIdMatchInfoSchema(), 200) +# TODO Fix this response so that it adequately represents Optionals @response_schema(KeylistUpdateSchema()) async def update_keylist_for_connection(request: web.BaseRequest): """Update keylist for a connection.""" diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index fc13a67c6e..158fb748e2 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -9,7 +9,6 @@ from ....connections.models.conn_record import ConnRecord from ....connections.models.diddoc import DIDDoc from ....connections.base_manager import BaseConnectionManager -from ....connections.util import mediation_record_if_id from ....core.error import BaseError from ....core.profile import Profile from ....messaging.decorators.attach_decorator import AttachDecorator @@ -255,8 +254,7 @@ async def create_request( # Mediation Support mediation_mgr = MediationManager(self.profile) keylist_updates = None - mediation_record = await mediation_record_if_id( - self.profile, + mediation_record = await self._route_manager.mediation_record_if_id( mediation_id, or_default=True, ) @@ -552,7 +550,9 @@ async def receive_request( await conn_rec.attach_request(session, request) # Send keylist updates to mediator - mediation_record = await mediation_record_if_id(self.profile, mediation_id) + mediation_record = await self._route_manager.mediation_record_if_id( + mediation_id + ) if keylist_updates and mediation_record: responder = self.profile.inject(BaseResponder) await responder.send( @@ -588,7 +588,9 @@ async def create_response( mediation_mgr = MediationManager(self.profile) keylist_updates = None - mediation_record = await mediation_record_if_id(self.profile, mediation_id) + mediation_record = await self._route_manager.mediation_record_if_id( + mediation_id + ) base_mediation_record = None # Multitenancy setup diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index ec157bf1b6..332473df78 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -8,7 +8,6 @@ from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord -from ....connections.util import mediation_record_if_id from ....core.error import BaseError from ....core.profile import Profile from ....did.did_key import DIDKey @@ -125,8 +124,7 @@ async def create_invitation( """ mediation_mgr = MediationManager(self.profile) - mediation_record = await mediation_record_if_id( - self.profile, + mediation_record = await self._route_manager.mediation_record_if_id( mediation_id, or_default=True, ) @@ -404,7 +402,7 @@ async def receive_invitation( """ if mediation_id: try: - await mediation_record_if_id(self.profile, mediation_id) + await self._route_manager.mediation_record_if_id(mediation_id) except StorageNotFoundError: mediation_id = None From f2bd49ece96d3a63c4249c0c31ca369e310a02c9 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 22:53:20 -0400 Subject: [PATCH 09/37] fix: status code on response Signed-off-by: Daniel Bluhm --- .../protocols/coordinate_mediation/v1_0/routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 1c84e74ec3..06056a126b 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -518,9 +518,9 @@ async def clear_default_mediator(request: web.BaseRequest): @docs(tags=["mediation"], summary="Update keylist for a connection") @match_info_schema(ConnectionsConnIdMatchInfoSchema()) -@request_schema(MediationIdMatchInfoSchema(), 200) +@request_schema(MediationIdMatchInfoSchema()) # TODO Fix this response so that it adequately represents Optionals -@response_schema(KeylistUpdateSchema()) +@response_schema(KeylistUpdateSchema(), 200) async def update_keylist_for_connection(request: web.BaseRequest): """Update keylist for a connection.""" context: AdminRequestContext = request["context"] From b6b67845e28ba54204a73d0e201eae2712ac0708 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 23:07:09 -0400 Subject: [PATCH 10/37] fix: mediation id from metadata Signed-off-by: Daniel Bluhm --- .../coordinate_mediation/v1_0/route_manager.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index f189a2a6f6..47f9075724 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -65,18 +65,13 @@ async def mediation_record_for_connection( validate mediation record state and return record else, return None """ - mediation_record = None async with self.profile.session() as session: - try: - mediation_record = await MediationRecord.retrieve_by_connection_id( - session, conn_record.connection_id - ) - except StorageNotFoundError: - pass - - if mediation_record: - self._validate_mediation_state(mediation_record) - return mediation_record + mediation_metadata = await conn_record.metadata_get( + session, MediationManager.METADATA_KEY, {} + ) + mediation_id = ( + mediation_metadata.get(MediationManager.METADATA_ID) or mediation_id + ) return await self.mediation_record_if_id(mediation_id, or_default) From 82893f60e5f712a2ea0c065c9942bb5b5bc5246d Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 8 Jun 2022 23:28:11 -0400 Subject: [PATCH 11/37] feat: register update-keylist route Signed-off-by: Daniel Bluhm --- aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 06056a126b..3ebfc87f1c 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -593,6 +593,9 @@ async def register(app: web.Application): ), web.put("/mediation/{mediation_id}/default-mediator", set_default_mediator), web.delete("/mediation/default-mediator", clear_default_mediator), + web.post( + "/mediation/update-keylist/{conn_id}", update_keylist_for_connection + ), ] ) From fcb70c694c0a82cc1779863461ba4ee25828d35c Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 9 Jun 2022 11:12:10 -0400 Subject: [PATCH 12/37] fix: state mismatches, overwriting updates Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/route_manager.py | 4 +- .../protocols/connections/v1_0/manager.py | 4 ++ .../v1_0/route_manager.py | 45 ++++++++++++++----- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index 25af722ad5..fb9d96de36 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -73,7 +73,9 @@ async def _route_for_key( if mediation_record: keylist_updates = await mediation_mgr.add_key(recipient_key) if replace_key: - keylist_updates = await mediation_mgr.remove_key(replace_key) + keylist_updates = await mediation_mgr.remove_key( + replace_key, keylist_updates + ) responder = self.root_profile.inject(BaseResponder) await responder.send( diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index 3f4e71c0b7..c8bc663b9d 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -294,6 +294,10 @@ async def receive_invitation( # Save the invitation for later processing await connection.attach_invitation(session, invitation) + await self._route_manager.save_mediator_for_connection( + connection, mediation_id=mediation_id + ) + if connection.accept == ConnRecord.ACCEPT_AUTO: request = await self.create_request(connection, mediation_id=mediation_id) responder = self.profile.inject_or(BaseResponder) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 47f9075724..adbcd9c18d 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -5,6 +5,7 @@ from abc import ABC, abstractmethod +import logging from typing import List, Optional, Tuple from aries_cloudagent.protocols.routing.v1_0.models.route_record import RouteRecord @@ -21,6 +22,9 @@ from .models.mediation_record import MediationRecord +LOGGER = logging.getLogger(__name__) + + class RouteManagerError(Exception): """Raised on error from route manager.""" @@ -73,7 +77,10 @@ async def mediation_record_for_connection( mediation_metadata.get(MediationManager.METADATA_ID) or mediation_id ) - return await self.mediation_record_if_id(mediation_id, or_default) + mediation_record = await self.mediation_record_if_id(mediation_id, or_default) + if mediation_record: + await self.save_mediator_for_connection(conn_record, mediation_record) + return mediation_record async def mediation_record_if_id( self, mediation_id: Optional[str] = None, or_default: bool = False @@ -116,6 +123,7 @@ async def route_connection_as_invitee( mediation_record: Optional[MediationRecord] = None, ): """Set up routing for a new connection when we are the invitee.""" + LOGGER.debug("Routing connection as invitee") my_info = await self.get_or_create_my_did(conn_record) return await self._route_for_key( my_info.verkey, mediation_record, skip_if_exists=True @@ -127,6 +135,7 @@ async def route_connection_as_inviter( mediation_record: Optional[MediationRecord] = None, ): """Set up routing for a new connection when we are the inviter.""" + LOGGER.debug("Routing connection as inviter") my_info = await self.get_or_create_my_did(conn_record) return await self._route_for_key( my_info.verkey, @@ -141,12 +150,12 @@ async def route_connection( mediation_record: Optional[MediationRecord] = None, ): if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( - ConnRecord.Role.REQUESTER + ConnRecord.Role.RESPONDER ): return await self.route_connection_as_invitee(conn_record, mediation_record) if conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( - ConnRecord.Role.RESPONDER + ConnRecord.Role.REQUESTER ): return await self.route_connection_as_inviter(conn_record, mediation_record) @@ -158,14 +167,7 @@ async def route_invitation( mediation_record: Optional[MediationRecord] = None, ): """Set up routing for receiving a response to an invitation.""" - if mediation_record: - # Save that this invitation was created with mediation - async with self.profile.session() as session: - await conn_record.metadata_set( - session, - MediationManager.METADATA_KEY, - {MediationManager.METADATA_ID: mediation_record.mediation_id}, - ) + await self.save_mediator_for_connection(conn_record, mediation_record) if conn_record.invitation_key: return await self._route_for_key( @@ -188,6 +190,25 @@ async def route_static( my_info.verkey, mediation_record, skip_if_exists=True ) + async def save_mediator_for_connection( + self, + conn_record: ConnRecord, + mediation_record: Optional[MediationRecord] = None, + mediation_id: Optional[str] = None, + ): + async with self.profile.session() as session: + if mediation_id: + mediation_record = await MediationRecord.retrieve_by_id( + session, mediation_id + ) + + if mediation_record: + await conn_record.metadata_set( + session, + MediationManager.METADATA_KEY, + {MediationManager.METADATA_ID: mediation_record.mediation_id}, + ) + @abstractmethod async def routing_info( self, @@ -224,7 +245,7 @@ async def _route_for_key( mediation_mgr = MediationManager(self.profile) keylist_update = await mediation_mgr.add_key(recipient_key) if replace_key: - keylist_update = await mediation_mgr.remove_key(replace_key) + keylist_update = await mediation_mgr.remove_key(replace_key, keylist_update) responder = self.profile.inject(BaseResponder) await responder.send( From 998cb4910bb7fa835158fd2458a6b3947c2f9ea6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 9 Jun 2022 11:49:53 -0400 Subject: [PATCH 13/37] feat: out of band manager use route manager Signed-off-by: Daniel Bluhm --- .../protocols/out_of_band/v1_0/manager.py | 60 ++----------------- 1 file changed, 6 insertions(+), 54 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 332473df78..dea80bf851 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -15,14 +15,12 @@ from ....indy.models.xform import indy_proof_req_preview2indy_requested_creds from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder -from ....multitenant.base import BaseMultitenantManager from ....storage.error import StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet from ....wallet.util import b64_to_bytes from ....wallet.key_type import KeyType -from ...coordinate_mediation.v1_0.manager import MediationManager from ...connections.v1_0.manager import ConnectionManager from ...connections.v1_0.messages.connection_invitation import ConnectionInvitation from ...didcomm_prefix import DIDCommPrefix @@ -123,12 +121,10 @@ async def create_invitation( Invitation record """ - mediation_mgr = MediationManager(self.profile) mediation_record = await self._route_manager.mediation_record_if_id( mediation_id, or_default=True, ) - keylist_updates = None if not (hs_protos or attachments): raise OutOfBandManagerError( @@ -136,10 +132,6 @@ async def create_invitation( "request attachments, or both" ) - # Multitenancy setup - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - wallet_id = self.profile.settings.get("wallet.id") - accept = bool( auto_accept or ( @@ -236,9 +228,6 @@ async def create_invitation( requests_attach=message_attachments, services=[f"did:sov:{public_did.did}"], ) - keylist_updates = await mediation_mgr.add_key( - public_did.verkey, keylist_updates - ) endpoint, *_ = await self.resolve_invitation(public_did.did) invi_url = invi_msg.to_url(endpoint) @@ -257,11 +246,6 @@ async def create_invitation( await conn_rec.save(session, reason="Created new invitation") await conn_rec.attach_invitation(session, invi_msg) - if multitenant_mgr and wallet_id: # add mapping for multitenant relay - await multitenant_mgr.add_key( - wallet_id, public_did.verkey, skip_if_exists=True - ) - else: invitation_mode = ( ConnRecord.INVITATION_MODE_MULTI @@ -277,12 +261,7 @@ async def create_invitation( async with self.profile.session() as session: wallet = session.inject(BaseWallet) connection_key = await wallet.create_signing_key(KeyType.ED25519) - keylist_updates = await mediation_mgr.add_key( - connection_key.verkey, keylist_updates - ) - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, connection_key.verkey) + # Initializing InvitationMessage here to include # invitation_msg_id in webhook poyload invi_msg = InvitationMessage() @@ -301,38 +280,9 @@ async def create_invitation( async with self.profile.session() as session: await conn_rec.save(session, reason="Created new connection") - routing_keys = [] - # The base wallet can act as a mediator for all tenants - if multitenant_mgr and wallet_id: - base_mediation_record = await multitenant_mgr.get_default_mediator() - - if base_mediation_record: - routing_keys = base_mediation_record.routing_keys - my_endpoint = base_mediation_record.endpoint - - # If we use a mediator for the base wallet we don't - # need to register the key at the subwallet mediator - # because it only needs to know the key of the base mediator - # sub wallet mediator -> base wallet mediator -> agent - keylist_updates = None - if mediation_record: - routing_keys = [*routing_keys, *mediation_record.routing_keys] - my_endpoint = mediation_record.endpoint - - # Save that this invitation was created with mediation - - async with self.profile.session() as session: - await conn_rec.metadata_set( - session, - MediationManager.METADATA_KEY, - {MediationManager.METADATA_ID: mediation_record.mediation_id}, - ) - - if keylist_updates: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) + routing_keys, my_endpoint = await self._route_manager.routing_info( + my_endpoint, mediation_record + ) routing_keys = [ key if len(key.split(":")) == 3 @@ -371,6 +321,8 @@ async def create_invitation( for key, value in metadata.items(): await conn_rec.metadata_set(session, key, value) + await self._route_manager.route_invitation(conn_rec, mediation_record) + return InvitationRecord( # for return via admin API, not storage state=InvitationRecord.STATE_INITIAL, invi_msg_id=invi_msg._id, From 4a3313e4ea1948894084e94e208eb099bc29c6eb Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 9 Jun 2022 11:59:23 -0400 Subject: [PATCH 14/37] feat: didexchange manager use route manager Signed-off-by: Daniel Bluhm --- .../protocols/didexchange/v1_0/manager.py | 97 +++++-------------- 1 file changed, 25 insertions(+), 72 deletions(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 158fb748e2..d36472c0dd 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -152,6 +152,11 @@ async def receive_invitation( conn_rec.invitation_key = did_document.verification_method[ 0 ].public_key_base58 + + await self._route_manager.save_mediator_for_connection( + conn_rec, mediation_id=mediation_id + ) + if conn_rec.accept == ConnRecord.ACCEPT_AUTO: request = await self.create_request(conn_rec, mediation_id=mediation_id) responder = self.profile.inject_or(BaseResponder) @@ -252,17 +257,17 @@ async def create_request( """ # Mediation Support - mediation_mgr = MediationManager(self.profile) - keylist_updates = None - mediation_record = await self._route_manager.mediation_record_if_id( + mediation_record = await self._route_manager.mediation_record_for_connection( + conn_rec, mediation_id, or_default=True, ) - base_mediation_record = None # Multitenancy setup multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) wallet_id = self.profile.settings.get("wallet.id") + + base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator() @@ -281,12 +286,11 @@ async def create_request( key_type=KeyType.ED25519, ) conn_rec.my_did = my_info.did - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) + + # Idempotent; if routing has already been set up, no action taken + await self._route_manager.route_connection_as_invitee( + conn_rec, mediation_record + ) # Create connection request message if my_endpoint: @@ -297,6 +301,7 @@ async def create_request( if default_endpoint: my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + did_doc = await self.create_did_document( my_info, conn_rec.inbound_connection_id, @@ -329,12 +334,6 @@ async def create_request( async with self.profile.session() as session: await conn_rec.save(session, reason="Created connection request") - # Notify Mediator - if keylist_updates and mediation_record: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) return request async def receive_request( @@ -345,7 +344,6 @@ async def receive_request( my_endpoint: str = None, alias: str = None, auto_accept_implicit: bool = None, - mediation_id: str = None, ) -> ConnRecord: """ Receive and store a connection request. @@ -357,8 +355,6 @@ async def receive_request( my_endpoint: My endpoint alias: Alias for the connection auto_accept: Auto-accept request against implicit invitation - mediation_id: The record id for mediation that contains routing_keys and - service endpoint Returns: The new or updated `ConnRecord` instance @@ -369,16 +365,10 @@ async def receive_request( settings=self.profile.settings, ) - mediation_mgr = MediationManager(self.profile) - keylist_updates = None conn_rec = None connection_key = None my_info = None - # Multitenancy setup - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - wallet_id = self.profile.settings.get("wallet.id") - # Determine what key will need to sign the response if recipient_verkey: # peer DID connection_key = recipient_verkey @@ -428,9 +418,6 @@ async def receive_request( method=DIDMethod.SOV, key_type=KeyType.ED25519, ) - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) new_conn_rec = ConnRecord( invitation_key=connection_key, @@ -458,14 +445,6 @@ async def receive_request( conn_rec = new_conn_rec - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) - else: - keylist_updates = await mediation_mgr.remove_key( - connection_key, keylist_updates - ) - # request DID doc describes requester DID if not (request.did_doc_attach and request.did_doc_attach.data): raise DIDXManagerError( @@ -509,14 +488,6 @@ async def receive_request( key_type=KeyType.ED25519, ) - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) - auto_accept = bool( auto_accept_implicit or ( @@ -549,16 +520,6 @@ async def receive_request( # Attach the connection request so it can be found and responded to await conn_rec.attach_request(session, request) - # Send keylist updates to mediator - mediation_record = await self._route_manager.mediation_record_if_id( - mediation_id - ) - if keylist_updates and mediation_record: - responder = self.profile.inject(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - return conn_rec async def create_response( @@ -586,16 +547,15 @@ async def create_response( settings=self.profile.settings, ) - mediation_mgr = MediationManager(self.profile) - keylist_updates = None - mediation_record = await self._route_manager.mediation_record_if_id( - mediation_id + mediation_record = await self._route_manager.mediation_record_for_connection( + conn_rec, mediation_id ) - base_mediation_record = None # Multitenancy setup multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) wallet_id = self.profile.settings.get("wallet.id") + + base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator() @@ -618,12 +578,11 @@ async def create_response( key_type=KeyType.ED25519, ) conn_rec.my_did = my_info.did - keylist_updates = await mediation_mgr.add_key( - my_info.verkey, keylist_updates - ) - # Add mapping for multitenant relay - if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, my_info.verkey) + + # Idempotent; if routing has already been set up, no action taken + await self._route_manager.route_connection_as_inviter( + conn_rec, mediation_record + ) # Create connection response message if my_endpoint: @@ -634,6 +593,7 @@ async def create_response( if default_endpoint: my_endpoints.append(default_endpoint) my_endpoints.extend(self.profile.settings.get("additional_endpoints", [])) + did_doc = await self.create_did_document( my_info, conn_rec.inbound_connection_id, @@ -660,13 +620,6 @@ async def create_response( log_params={"response": response}, ) - # Update Mediator if necessary - if keylist_updates and mediation_record: - responder = self.profile.inject_or(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - async with self.profile.session() as session: send_mediation_request = await conn_rec.metadata_get( session, MediationManager.SEND_REQ_AFTER_CONNECTION From aad950564161a668d8971b22e6d979cb9b1765e1 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 20:47:27 -0400 Subject: [PATCH 15/37] test: out of band manager with route manager Signed-off-by: Daniel Bluhm --- .../out_of_band/v1_0/tests/test_manager.py | 55 ++++--------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py index f7ff19144a..cb515bf729 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py @@ -22,6 +22,7 @@ from .....multitenant.base import BaseMultitenantManager from .....multitenant.manager import MultitenantManager from .....protocols.coordinate_mediation.v1_0.manager import MediationManager +from .....protocols.coordinate_mediation.v1_0.route_manager import RouteManager from .....protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) @@ -359,6 +360,12 @@ def setUp(self): self.test_mediator_conn_id = "mediator-conn-id" self.test_mediator_endpoint = "http://mediator.example.com" + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=(self.test_mediator_routing_keys, self.test_mediator_endpoint) + ) + self.manager._route_manager = self.route_manager + async def test_create_invitation_handshake_succeeds(self): self.profile.context.update_settings({"public_invites": True}) @@ -388,37 +395,6 @@ async def test_create_invitation_handshake_succeeds(self): ) assert invi_rec.invitation.services == [f"did:sov:{TestConfig.test_did}"] - async def test_create_invitation_mediation_overwrites_routing_and_endpoint(self): - async with self.profile.session() as session: - mock_conn_rec = async_mock.MagicMock() - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - with async_mock.patch.object( - MediationManager, - "get_default_mediator_id", - ) as mock_get_default_mediator, async_mock.patch.object( - mock_conn_rec, "metadata_set", async_mock.CoroutineMock() - ) as mock_metadata_set: - invite = await self.manager.create_invitation( - my_endpoint=TestConfig.test_endpoint, - my_label="test123", - hs_protos=[HSProto.RFC23], - mediation_id=mediation_record.mediation_id, - ) - assert isinstance(invite, InvitationRecord) - assert invite.invitation._type == DIDCommPrefix.qualify_current( - INVITATION - ) - assert invite.invitation.label == "test123" - mock_get_default_mediator.assert_not_called() - async def test_create_invitation_multitenant_local(self): self.profile.context.update_settings( { @@ -427,8 +403,6 @@ async def test_create_invitation_multitenant_local(self): } ) - self.multitenant_mgr.add_key = async_mock.CoroutineMock() - with async_mock.patch.object( InMemoryWallet, "create_signing_key", autospec=True ) as mock_wallet_create_signing_key, async_mock.patch.object( @@ -444,9 +418,7 @@ async def test_create_invitation_multitenant_local(self): multi_use=False, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", TestConfig.test_verkey - ) + self.route_manager.route_invitation.assert_called_once() async def test_create_invitation_multitenant_public(self): self.profile.context.update_settings( @@ -457,8 +429,6 @@ async def test_create_invitation_multitenant_public(self): } ) - self.multitenant_mgr.add_key = async_mock.CoroutineMock() - with async_mock.patch.object( InMemoryWallet, "get_public_did", autospec=True ) as mock_wallet_get_public_did: @@ -475,9 +445,7 @@ async def test_create_invitation_multitenant_public(self): multi_use=False, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", TestConfig.test_verkey, skip_if_exists=True - ) + self.route_manager.route_invitation.assert_called_once() async def test_create_invitation_no_handshake_no_attachments_x(self): with self.assertRaises(OutOfBandManagerError) as context: @@ -1214,6 +1182,9 @@ async def test_receive_invitation_with_invalid_mediation(self): mock_didx_recv_invi.return_value = mock_conn mock_retrieve_conn_by_id.return_value = mock_conn invi_msg = invite.invitation + self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( + side_effect=StorageNotFoundError + ) await self.manager.receive_invitation( invi_msg, mediation_id="test-mediation-id", @@ -1457,8 +1428,6 @@ async def test_receive_invitation_handshake_reuse_failed(self): ConnRecord, "retrieve_by_id", async_mock.CoroutineMock(return_value=test_exist_conn), - ), async_mock.patch.object( - test_module, "mediation_record_if_id", async_mock.CoroutineMock() ): oob_invitation = InvitationMessage( handshake_protocols=[ From 16d987a31c16d984554c5b52b8eaf6e1d6307f87 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 20:57:48 -0400 Subject: [PATCH 16/37] style: flake8 fixes for route manager Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/base.py | 1 + aries_cloudagent/multitenant/route_manager.py | 4 +++ .../v1_0/route_manager.py | 29 +++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index b6e2c3cdd1..f316f7eadb 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -292,6 +292,7 @@ async def remove_wallet_profile(self, profile: Profile): """ def get_route_manager(self, sub_profile: Profile, wallet_id: str): + """Return a route manager for handling multitenant routing.""" return MultitenantRouteManager(self._profile, sub_profile, wallet_id) async def add_key( diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index fb9d96de36..c79cdd6faa 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -26,15 +26,18 @@ class MultitenantRouteManager(RouteManager): """Multitenancy route manager.""" def __init__(self, root_profile: Profile, sub_profile: Profile, wallet_id: str): + """Initialize multitenant route manager.""" self.root_profile = root_profile self.wallet_id = wallet_id super().__init__(sub_profile) @property def sub_profile(self) -> Profile: + """Return reference to sub wallet profile.""" return self.profile async def get_base_wallet_mediator(self) -> Optional[MediationRecord]: + """Get base wallet's default mediator.""" return await MediationManager(self.root_profile).get_default_mediator() async def _route_for_key( @@ -87,6 +90,7 @@ async def _route_for_key( async def routing_info( self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None ) -> Tuple[List[str], str]: + """Return routing info.""" routing_keys = [] base_mediation_record = await self.get_base_wallet_mediator() diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index adbcd9c18d..6dca92c02c 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -8,16 +8,19 @@ import logging from typing import List, Optional, Tuple -from aries_cloudagent.protocols.routing.v1_0.models.route_record import RouteRecord -from aries_cloudagent.storage.error import StorageNotFoundError +from aries_cloudagent.protocols.coordinate_mediation.v1_0.messages.keylist_update import ( + KeylistUpdate, +) from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile from ....messaging.responder import BaseResponder +from ....storage.error import StorageNotFoundError from ....wallet.base import BaseWallet from ....wallet.did_info import DIDInfo from ....wallet.did_method import DIDMethod from ....wallet.key_type import KeyType +from ...routing.v1_0.models.route_record import RouteRecord from .manager import MediationManager from .models.mediation_record import MediationRecord @@ -36,6 +39,7 @@ def __init__(self, profile: Profile): self.profile = profile async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: + """Create or retrieve DID info for a conneciton.""" if not conn_record.my_did: async with self.profile.session() as session: wallet = session.inject(BaseWallet) @@ -51,6 +55,7 @@ async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: return my_info def _validate_mediation_state(self, mediation_record: MediationRecord): + """Perform mediation state validation""" if mediation_record.state != MediationRecord.STATE_GRANTED: raise RouteManagerError( "Mediation is not granted for mediation identified by " @@ -114,14 +119,14 @@ async def _route_for_key( *, skip_if_exists: bool = False, replace_key: Optional[str] = None, - ): + ) -> Optional[KeylistUpdate]: """Route a key.""" async def route_connection_as_invitee( self, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, - ): + ) -> Optional[KeylistUpdate]: """Set up routing for a new connection when we are the invitee.""" LOGGER.debug("Routing connection as invitee") my_info = await self.get_or_create_my_did(conn_record) @@ -133,7 +138,7 @@ async def route_connection_as_inviter( self, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, - ): + ) -> Optional[KeylistUpdate]: """Set up routing for a new connection when we are the inviter.""" LOGGER.debug("Routing connection as inviter") my_info = await self.get_or_create_my_did(conn_record) @@ -148,7 +153,11 @@ async def route_connection( self, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, - ): + ) -> Optional[KeylistUpdate]: + """Setup routing for a connection. + + This method will evaluate connection state and call the appropriate methods. + """ if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( ConnRecord.Role.RESPONDER ): @@ -165,7 +174,7 @@ async def route_invitation( self, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, - ): + ) -> Optional[KeylistUpdate]: """Set up routing for receiving a response to an invitation.""" await self.save_mediator_for_connection(conn_record, mediation_record) @@ -184,7 +193,8 @@ async def route_static( self, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, - ): + ) -> Optional[KeylistUpdate]: + """Establish routing for a static connection.""" my_info = await self.get_or_create_my_did(conn_record) return await self._route_for_key( my_info.verkey, mediation_record, skip_if_exists=True @@ -196,6 +206,7 @@ async def save_mediator_for_connection( mediation_record: Optional[MediationRecord] = None, mediation_id: Optional[str] = None, ): + """Save mediator info to connection metadata.""" async with self.profile.session() as session: if mediation_id: mediation_record = await MediationRecord.retrieve_by_id( @@ -228,7 +239,7 @@ async def _route_for_key( *, skip_if_exists: bool = False, replace_key: Optional[str] = None, - ): + ) -> Optional[KeylistUpdate]: if not mediation_record: return None From f19e16019656ee0f9321e6f91eebd11f2a075372 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 21:29:05 -0400 Subject: [PATCH 17/37] refactor: remove add_key from base multitenant mgr Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/base.py | 45 --------- .../multitenant/tests/test_base.py | 91 ++++--------------- 2 files changed, 16 insertions(+), 120 deletions(-) diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index f316f7eadb..78e9b184b3 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -10,7 +10,6 @@ from ..config.injection_context import InjectionContext from ..core.error import BaseError from ..core.profile import Profile, ProfileSession -from ..messaging.responder import BaseResponder from ..multitenant.route_manager import MultitenantRouteManager from ..protocols.coordinate_mediation.v1_0.manager import ( MediationManager, @@ -19,7 +18,6 @@ from ..protocols.routing.v1_0.manager import RouteNotFoundError, RoutingManager from ..protocols.routing.v1_0.models.route_record import RouteRecord from ..storage.base import BaseStorage -from ..storage.error import StorageNotFoundError from ..transport.wire_format import BaseWireFormat from ..wallet.base import BaseWallet from ..wallet.models.wallet_record import WalletRecord @@ -295,49 +293,6 @@ def get_route_manager(self, sub_profile: Profile, wallet_id: str): """Return a route manager for handling multitenant routing.""" return MultitenantRouteManager(self._profile, sub_profile, wallet_id) - async def add_key( - self, wallet_id: str, recipient_key: str, *, skip_if_exists: bool = False - ): - """ - Add a wallet key to map incoming messages to specific subwallets. - - Args: - wallet_id: The wallet id the key corresponds to - recipient_key: The recipient key belonging to the wallet - skip_if_exists: Whether to skip the action if the key is already registered - for relaying / mediation - """ - - LOGGER.info( - f"Add route record for recipient {recipient_key} to wallet {wallet_id}" - ) - routing_mgr = RoutingManager(self._profile) - mediation_mgr = MediationManager(self._profile) - mediation_record = await mediation_mgr.get_default_mediator() - - if skip_if_exists: - try: - async with self._profile.session() as session: - await RouteRecord.retrieve_by_recipient_key(session, recipient_key) - - # If no error is thrown, it means there is already a record - return - except (StorageNotFoundError): - pass - - await routing_mgr.create_route_record( - recipient_key=recipient_key, internal_wallet_id=wallet_id - ) - - # External mediation - if mediation_record: - keylist_updates = await mediation_mgr.add_key(recipient_key) - - responder = self._profile.inject(BaseResponder) - await responder.send( - keylist_updates, connection_id=mediation_record.connection_id - ) - async def create_auth_token( self, wallet_record: WalletRecord, wallet_key: str = None ) -> str: diff --git a/aries_cloudagent/multitenant/tests/test_base.py b/aries_cloudagent/multitenant/tests/test_base.py index f20605e9cb..121cf6af08 100644 --- a/aries_cloudagent/multitenant/tests/test_base.py +++ b/aries_cloudagent/multitenant/tests/test_base.py @@ -191,13 +191,18 @@ async def test_create_wallet_fails_if_wallet_name_exists(self): async def test_create_wallet_saves_wallet_record_creates_profile(self): + mock_route_manager = async_mock.MagicMock() + mock_route_manager.route_public_did = async_mock.CoroutineMock() + with async_mock.patch.object( WalletRecord, "save" ) as wallet_record_save, async_mock.patch.object( BaseMultitenantManager, "get_wallet_profile" ) as get_wallet_profile, async_mock.patch.object( - BaseMultitenantManager, "add_key" - ) as add_key: + BaseMultitenantManager, + "get_route_manager", + async_mock.MagicMock(return_value=mock_route_manager), + ): get_wallet_profile.return_value = InMemoryProfile.test_profile() wallet_record = await self.manager.create_wallet( @@ -212,7 +217,7 @@ async def test_create_wallet_saves_wallet_record_creates_profile(self): {"wallet.key": "test_key"}, provision=True, ) - add_key.assert_not_called() + mock_route_manager.route_public_did.assert_not_called() assert isinstance(wallet_record, WalletRecord) assert wallet_record.wallet_name == "test_wallet" assert wallet_record.key_management_mode == WalletRecord.MODE_MANAGED @@ -227,13 +232,18 @@ async def test_create_wallet_adds_wallet_route(self): key_type=KeyType.ED25519, ) + mock_route_manager = async_mock.MagicMock() + mock_route_manager.route_public_did = async_mock.CoroutineMock() + with async_mock.patch.object( WalletRecord, "save" ) as wallet_record_save, async_mock.patch.object( BaseMultitenantManager, "get_wallet_profile" ) as get_wallet_profile, async_mock.patch.object( - BaseMultitenantManager, "add_key" - ) as add_key, async_mock.patch.object( + BaseMultitenantManager, + "get_route_manager", + async_mock.MagicMock(return_value=mock_route_manager), + ), async_mock.patch.object( InMemoryWallet, "get_public_did" ) as get_public_did: get_wallet_profile.return_value = InMemoryProfile.test_profile() @@ -244,9 +254,7 @@ async def test_create_wallet_adds_wallet_route(self): WalletRecord.MODE_MANAGED, ) - add_key.assert_called_once_with( - wallet_record.wallet_id, did_info.verkey, skip_if_exists=True - ) + mock_route_manager.route_public_did.assert_called_once_with(did_info.verkey) wallet_record_save.assert_called_once() get_wallet_profile.assert_called_once_with( @@ -337,73 +345,6 @@ async def test_remove_wallet_removes_profile_wallet_storage_records(self): RouteRecord.RECORD_TYPE, {"wallet_id": "test"} ) - async def test_add_key_no_mediation(self): - with async_mock.patch.object( - RoutingManager, "create_route_record" - ) as create_route_record, async_mock.patch.object( - MediationManager, "add_key" - ) as mediation_add_key: - await self.manager.add_key("wallet_id", "recipient_key") - - create_route_record.assert_called_once_with( - recipient_key="recipient_key", internal_wallet_id="wallet_id" - ) - mediation_add_key.assert_not_called() - - async def test_add_key_skip_if_exists_does_not_exist(self): - with async_mock.patch.object( - RoutingManager, "create_route_record" - ) as create_route_record, async_mock.patch.object( - RouteRecord, "retrieve_by_recipient_key" - ) as retrieve_by_recipient_key: - retrieve_by_recipient_key.side_effect = StorageNotFoundError() - - await self.manager.add_key( - "wallet_id", "recipient_key", skip_if_exists=True - ) - - create_route_record.assert_called_once_with( - recipient_key="recipient_key", internal_wallet_id="wallet_id" - ) - - async def test_add_key_skip_if_exists_does_exist(self): - with async_mock.patch.object( - RoutingManager, "create_route_record" - ) as create_route_record, async_mock.patch.object( - RouteRecord, "retrieve_by_recipient_key" - ) as retrieve_by_recipient_key: - await self.manager.add_key( - "wallet_id", "recipient_key", skip_if_exists=True - ) - - create_route_record.assert_not_called() - - async def test_add_key_mediation(self): - with async_mock.patch.object( - RoutingManager, "create_route_record" - ) as create_route_record, async_mock.patch.object( - MediationManager, "get_default_mediator" - ) as get_default_mediator, async_mock.patch.object( - MediationManager, "add_key" - ) as mediation_add_key: - default_mediator = async_mock.CoroutineMock() - keylist_updates = async_mock.CoroutineMock() - - get_default_mediator.return_value = default_mediator - mediation_add_key.return_value = keylist_updates - - await self.manager.add_key("wallet_id", "recipient_key") - - create_route_record.assert_called_once_with( - recipient_key="recipient_key", internal_wallet_id="wallet_id" - ) - - get_default_mediator.assert_called_once() - mediation_add_key.assert_called_once_with("recipient_key") - self.responder.send.assert_called_once_with( - keylist_updates, connection_id=default_mediator.connection_id - ) - async def test_create_auth_token_fails_no_wallet_key_but_required(self): self.profile.settings["multitenant.jwt_secret"] = "very_secret_jwt" wallet_record = WalletRecord( From a99a95222cccd00ff0c258ffc1ac0cb34c89de10 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 21:36:51 -0400 Subject: [PATCH 18/37] test: conn rec handler mediation id Signed-off-by: Daniel Bluhm --- .../handlers/tests/test_request_handler.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py index ec16a72cef..7fa1d3ab31 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py @@ -83,7 +83,7 @@ async def test_called(self, mock_conn_mgr, request_context): responder = MockResponder() await handler_inst.handle(request_context, responder) mock_conn_mgr.return_value.receive_request.assert_called_once_with( - request_context.message, request_context.message_receipt, mediation_id=None + request_context.message, request_context.message_receipt ) assert not responder.messages @@ -101,31 +101,38 @@ async def test_called_with_auto_response(self, mock_conn_mgr, request_context): responder = MockResponder() await handler_inst.handle(request_context, responder) mock_conn_mgr.return_value.receive_request.assert_called_once_with( - request_context.message, request_context.message_receipt, mediation_id=None + request_context.message, request_context.message_receipt + ) + mock_conn_mgr.return_value.create_response.assert_called_once_with( + mock_conn_rec, mediation_id=None ) assert responder.messages @pytest.mark.asyncio @async_mock.patch.object(handler, "ConnectionManager") - async def test_connection_record_with_mediation_metadata( + async def test_connection_record_with_mediation_metadata_auto_response( self, mock_conn_mgr, request_context, connection_record ): - mock_conn_mgr.return_value.receive_request = async_mock.CoroutineMock() + mock_conn_rec = async_mock.MagicMock() + mock_conn_rec.accept = ConnRecord.ACCEPT_AUTO + mock_conn_mgr.return_value.receive_request = async_mock.CoroutineMock( + return_value=mock_conn_rec + ) + mock_conn_mgr.return_value.create_response = async_mock.CoroutineMock() request_context.message = ConnectionRequest() with async_mock.patch.object( connection_record, "metadata_get", async_mock.CoroutineMock(return_value={"id": "test-mediation-id"}), - ) as mock_metadata_get: + ): handler_inst = handler.ConnectionRequestHandler() responder = MockResponder() await handler_inst.handle(request_context, responder) - mock_conn_mgr.return_value.receive_request.assert_called_once_with( - request_context.message, - request_context.message_receipt, - mediation_id="test-mediation-id", + mock_conn_mgr.return_value.receive_request.assert_called_once() + mock_conn_mgr.return_value.create_response.assert_called_once_with( + mock_conn_rec, mediation_id="test-mediation-id" ) - assert not responder.messages + assert responder.messages @pytest.mark.asyncio @async_mock.patch.object(handler, "ConnectionManager") @@ -146,7 +153,6 @@ async def test_connection_record_without_mediation_metadata( mock_conn_mgr.return_value.receive_request.assert_called_once_with( request_context.message, request_context.message_receipt, - mediation_id=None, ) assert not responder.messages From 835e9ead18203dba9b51acf1561182be86bd1c64 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 23:03:26 -0400 Subject: [PATCH 19/37] test: connection manager with route manager Signed-off-by: Daniel Bluhm --- .../connections/v1_0/tests/test_manager.py | 694 +++++------------- .../out_of_band/v1_0/tests/test_manager.py | 41 ++ 2 files changed, 238 insertions(+), 497 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 c1b1bb0ba1..bbb681645e 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -24,12 +24,12 @@ from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.base import DIDInfo -from .....wallet.did_info import KeyInfo from .....wallet.did_method import DIDMethod from .....wallet.error import WalletNotFoundError from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import KeyType from ....coordinate_mediation.v1_0.manager import MediationManager +from ....coordinate_mediation.v1_0.route_manager import RouteManager from ....coordinate_mediation.v1_0.messages.inner.keylist_update_rule import ( KeylistUpdateRule, ) @@ -106,6 +106,14 @@ async def setUp(self): self.test_mediator_endpoint = "http://mediator.example.com" self.manager = ConnectionManager(self.profile) + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=([], self.test_endpoint) + ) + self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( + return_value=None + ) + self.manager._route_manager = self.route_manager assert self.manager.profile async def test_create_invitation_public_and_multi_use_fails(self): @@ -155,6 +163,7 @@ async def test_create_invitation_non_multi_use_invitation_fails_on_reuse(self): async def test_create_invitation_public(self): self.context.update_settings({"public_invites": True}) + self.route_manager.route_public_did = async_mock.CoroutineMock() with async_mock.patch.object( InMemoryWallet, "get_public_did", autospec=True ) as mock_wallet_get_public_did: @@ -171,45 +180,8 @@ async def test_create_invitation_public(self): assert connect_record is None assert connect_invite.did.endswith(self.test_did) - - async def test_create_invitation_multitenant(self): - self.context.update_settings( - {"wallet.id": "test_wallet", "multitenant.enabled": True} - ) - - with async_mock.patch.object( - InMemoryWallet, "create_signing_key", autospec=True - ) as mock_wallet_create_signing_key: - mock_wallet_create_signing_key.return_value = KeyInfo( - self.test_verkey, None, KeyType.ED25519 - ) - await self.manager.create_invitation() - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey - ) - - async def test_create_invitation_public_multitenant(self): - self.context.update_settings( - { - "public_invites": True, - "wallet.id": "test_wallet", - "multitenant.enabled": True, - } - ) - - with async_mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - await self.manager.create_invitation(public=True) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey, skip_if_exists=True + self.route_manager.route_public_did.assert_called_once_with( + self.test_verkey ) async def test_create_invitation_public_no_public_invites(self): @@ -338,6 +310,9 @@ async def test_create_invitation_multi_use_metadata_transfers_to_connection(self assert await new_conn_rec.metadata_get_all(session) == {"test": "value"} async def test_create_invitation_mediation_overwrites_routing_and_endpoint(self): + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=(self.test_mediator_routing_keys, self.test_mediator_endpoint) + ) async with self.profile.session() as session: mediation_record = MediationRecord( role=MediationRecord.ROLE_CLIENT, @@ -361,6 +336,9 @@ async def test_create_invitation_mediation_overwrites_routing_and_endpoint(self) mock_get_default_mediator.assert_not_called() async def test_create_invitation_mediation_using_default(self): + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=(self.test_mediator_routing_keys, self.test_mediator_endpoint) + ) async with self.profile.session() as session: mediation_record = MediationRecord( role=MediationRecord.ROLE_CLIENT, @@ -371,17 +349,19 @@ async def test_create_invitation_mediation_using_default(self): ) await mediation_record.save(session) with async_mock.patch.object( - MediationManager, - "get_default_mediator", + self.route_manager, + "mediation_record_if_id", async_mock.CoroutineMock(return_value=mediation_record), - ) as mock_get_default_mediator: + ): _, invite = await self.manager.create_invitation( routing_keys=[self.test_verkey], my_endpoint=self.test_endpoint, ) assert invite.routing_keys == self.test_mediator_routing_keys assert invite.endpoint == self.test_mediator_endpoint - mock_get_default_mediator.assert_called_once() + self.route_manager.routing_info.assert_awaited_once_with( + self.test_endpoint, mediation_record + ) async def test_receive_invitation(self): (_, connect_invite) = await self.manager.create_invitation( @@ -434,41 +414,6 @@ async def test_receive_invitation_mediation_passes_id_when_auto_accept(self): invitee_record, mediation_id="test-mediation-id" ) - async def test_receive_invitation_bad_mediation(self): - _, connect_invite = await self.manager.create_invitation( - my_endpoint="testendpoint" - ) - with self.assertRaises(StorageNotFoundError): - await self.manager.receive_invitation( - connect_invite, mediation_id="not-a-mediation-id" - ) - - async def test_receive_invitation_mediation_not_granted(self): - async with self.profile.session() as session: - _, connect_invite = await self.manager.create_invitation( - my_endpoint="testendpoint" - ) - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_DENIED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.receive_invitation( - connect_invite, mediation_id=mediation_record.mediation_id - ) - - mediation_record.state = MediationRecord.STATE_REQUEST - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.receive_invitation( - connect_invite, mediation_id=mediation_record.mediation_id - ) - async def test_create_request(self): conn_req = await self.manager.create_request( ConnRecord( @@ -515,10 +460,27 @@ async def test_create_request_multitenant(self): self.context.update_settings( {"wallet.id": "test_wallet", "multitenant.enabled": True} ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) with async_mock.patch.object( InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did: + ) as mock_wallet_create_local_did, async_mock.patch.object( + self.multitenant_mgr, + "get_default_mediator", + async_mock.CoroutineMock(return_value=mediation_record), + ), async_mock.patch.object( + ConnectionManager, "create_did_document", autospec=True + ) as create_did_document, async_mock.patch.object( + self.route_manager, + "mediation_record_for_connection", + async_mock.CoroutineMock(return_value=None), + ): mock_wallet_create_local_did.return_value = DIDInfo( self.test_did, self.test_verkey, @@ -532,69 +494,66 @@ async def test_create_request_multitenant(self): their_label="Hello", their_role=ConnRecord.Role.RESPONDER.rfc160, alias="Bob", - ) + ), + my_endpoint=self.test_endpoint, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey + create_did_document.assert_called_once_with( + self.manager, + mock_wallet_create_local_did.return_value, + None, + [self.test_endpoint], + mediation_records=[mediation_record], ) + self.route_manager.route_connection_as_invitee.assert_called_once() async def test_create_request_mediation_id(self): - async with self.profile.session() as session: - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - - record = ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) - # Ensure the path with new did creation is hit - record.my_did = None + record = ConnRecord( + invitation_key=self.test_verkey, + their_label="Hello", + their_role=ConnRecord.Role.RESPONDER.rfc160, + alias="Bob", + ) - with async_mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, async_mock.patch.object( - InMemoryWallet, "create_local_did" - ) as create_local_did, async_mock.patch.object( - MediationManager, "get_default_mediator" - ) as mock_get_default_mediator: - did_info = DIDInfo( - did=self.test_did, - verkey=self.test_verkey, - metadata={}, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - create_local_did.return_value = did_info - await self.manager.create_request( - record, - mediation_id=mediation_record.mediation_id, - my_endpoint=self.test_endpoint, - ) - create_local_did.assert_called_once_with(DIDMethod.SOV, KeyType.ED25519) - create_did_document.assert_called_once_with( - self.manager, - did_info, - None, - [self.test_endpoint], - mediation_records=[mediation_record], - ) - mock_get_default_mediator.assert_not_called() + # Ensure the path with new did creation is hit + record.my_did = None - assert len(self.responder.messages) == 1 - message, used_kwargs = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert ( - "connection_id" in used_kwargs - and used_kwargs["connection_id"] == self.test_mediator_conn_id + with async_mock.patch.object( + ConnectionManager, "create_did_document", autospec=True + ) as create_did_document, async_mock.patch.object( + InMemoryWallet, "create_local_did" + ) as create_local_did, async_mock.patch.object( + self.route_manager, + "mediation_record_for_connection", + async_mock.CoroutineMock(return_value=mediation_record), + ): + did_info = DIDInfo( + did=self.test_did, + verkey=self.test_verkey, + metadata={}, + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + ) + create_local_did.return_value = did_info + await self.manager.create_request( + record, + mediation_id=mediation_record.mediation_id, + my_endpoint=self.test_endpoint, + ) + create_local_did.assert_called_once_with(DIDMethod.SOV, KeyType.ED25519) + create_did_document.assert_called_once_with( + self.manager, + did_info, + None, + [self.test_endpoint], + mediation_records=[mediation_record], ) async def test_create_request_default_mediator(self): @@ -623,10 +582,10 @@ async def test_create_request_default_mediator(self): ) as create_did_document, async_mock.patch.object( InMemoryWallet, "create_local_did" ) as create_local_did, async_mock.patch.object( - MediationManager, - "get_default_mediator", + self.route_manager, + "mediation_record_for_connection", async_mock.CoroutineMock(return_value=mediation_record), - ) as mock_get_default_mediator: + ): did_info = DIDInfo( did=self.test_did, verkey=self.test_verkey, @@ -647,44 +606,6 @@ async def test_create_request_default_mediator(self): [self.test_endpoint], mediation_records=[mediation_record], ) - mock_get_default_mediator.assert_called_once() - - assert len(self.responder.messages) == 1 - message, used_kwargs = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert ( - "connection_id" in used_kwargs - and used_kwargs["connection_id"] == self.test_mediator_conn_id - ) - - async def test_create_request_bad_mediation(self): - record, _ = await self.manager.create_invitation(my_endpoint="testendpoint") - with self.assertRaises(StorageNotFoundError): - await self.manager.create_request(record, mediation_id="not-a-mediation-id") - - async def test_create_request_mediation_not_granted(self): - async with self.profile.session() as session: - record, _ = await self.manager.create_invitation(my_endpoint="testendpoint") - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_DENIED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.create_request( - record, mediation_id=mediation_record.mediation_id - ) - - mediation_record.state = MediationRecord.STATE_REQUEST - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.create_request( - record, mediation_id=mediation_record.mediation_id - ) async def test_receive_request_public_did_oob_invite(self): async with self.profile.session() as session: @@ -762,114 +683,6 @@ async def test_receive_request_public_did_conn_invite(self): conn_rec = await self.manager.receive_request(mock_request, receipt) assert conn_rec - async def test_receive_request_multi_use_multitenant(self): - async with self.profile.session() as session: - multiuse_info = await session.wallet.create_local_did( - DIDMethod.SOV, KeyType.ED25519 - ) - new_info = await session.wallet.create_local_did( - DIDMethod.SOV, KeyType.ED25519 - ) - - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock( - is_multiuse_invitation=True, invitation_key=multiuse_info.verkey - ) - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = self.test_did - receipt = MessageReceipt(recipient_verkey=multiuse_info.verkey) - - self.context.update_settings( - {"wallet.id": "test_wallet", "multitenant.enabled": True} - ) - with async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ), async_mock.patch.object( - ConnRecord, "save", autospec=True - ), async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did: - mock_wallet_create_local_did.return_value = DIDInfo( - new_info.did, - new_info.verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - mock_conn_retrieve_by_invitation_key.return_value = ( - async_mock.MagicMock( - connection_id="dummy", - retrieve_invitation=async_mock.CoroutineMock(return_value={}), - metadata_get_all=async_mock.CoroutineMock(return_value={}), - ) - ) - await self.manager.receive_request(mock_request, receipt) - - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", new_info.verkey - ) - - async def test_receive_request_public_multitenant(self): - async with self.profile.session() as session: - new_info = await session.wallet.create_local_did( - DIDMethod.SOV, KeyType.ED25519 - ) - - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock( - accept=ConnRecord.ACCEPT_MANUAL - ) - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = self.test_did - receipt = MessageReceipt(recipient_did_public=True) - - self.context.update_settings( - { - "wallet.id": "test_wallet", - "multitenant.enabled": True, - "public_invites": True, - "debug.auto_accept_requests": False, - } - ) - - with async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ), async_mock.patch.object( - ConnRecord, "save", autospec=True - ), async_mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did, async_mock.patch.object( - InMemoryWallet, "get_local_did", autospec=True - ) as mock_wallet_get_local_did, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_msg_id", async_mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id: - mock_conn_retrieve_by_invitation_msg_id.return_value = ConnRecord() - mock_wallet_create_local_did.return_value = DIDInfo( - new_info.did, - new_info.verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - mock_wallet_get_local_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - await self.manager.receive_request(mock_request, receipt) - - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", new_info.verkey - ) - async def test_receive_request_public_did_no_did_doc(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock() @@ -999,136 +812,6 @@ async def test_receive_request_public_did_no_auto_accept(self): messages = self.responder.messages assert not messages - async def test_receive_request_mediation_id(self): - async with self.profile.session() as session: - - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=False - ) - await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - seed=None, - did=self.test_did, - ) - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - - record, invite = await self.manager.create_invitation() - record.accept = ConnRecord.ACCEPT_MANUAL - - await record.save(session) - - with async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - mock_conn_retrieve_by_invitation_key.return_value = record - conn_rec = await self.manager.receive_request( - mock_request, receipt, mediation_id=mediation_record.mediation_id - ) - - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert len(message.updates) == 1 - (remove,) = message.updates - assert remove.action == KeylistUpdateRule.RULE_REMOVE - assert remove.recipient_key == record.invitation_key - - async def test_receive_request_bad_mediation(self): - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = self.test_did - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=False - ) - record, invite = await self.manager.create_invitation() - with async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - mock_conn_retrieve_by_invitation_key.return_value = record - with self.assertRaises(StorageNotFoundError): - await self.manager.receive_request( - mock_request, receipt, mediation_id="not-a-mediation-id" - ) - - async def test_receive_request_mediation_not_granted(self): - async with self.profile.session() as session: - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = self.make_did_doc( - self.test_target_did, self.test_target_verkey - ) - mock_request.connection.did_doc.did = self.test_did - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=False - ) - record, invite = await self.manager.create_invitation() - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_DENIED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - - await mediation_record.save(session) - - with async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - mock_conn_retrieve_by_invitation_key.return_value = record - with self.assertRaises(BaseConnectionManagerError): - await self.manager.receive_request( - mock_request, - receipt, - mediation_id=mediation_record.mediation_id, - ) - - mediation_record.state = MediationRecord.STATE_REQUEST - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.receive_request( - mock_request, - receipt, - mediation_id=mediation_record.mediation_id, - ) - async def test_create_response(self): conn_rec = ConnRecord(state=ConnRecord.State.REQUEST.rfc160) @@ -1150,13 +833,41 @@ async def test_create_response_multitenant(self): {"wallet.id": "test_wallet", "multitenant.enabled": True} ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + with async_mock.patch.object( - ConnectionResponse, "sign_field", autospec=True + ConnRecord, "log_state", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ), async_mock.patch.object( + ConnRecord, "metadata_get", async_mock.CoroutineMock(return_value=False) + ), async_mock.patch.object( + self.route_manager, + "mediation_record_for_connection", + async_mock.CoroutineMock(return_value=mediation_record), ), async_mock.patch.object( ConnRecord, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnectionResponse, "sign_field", autospec=True ), async_mock.patch.object( InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did: + ) as mock_wallet_create_local_did, async_mock.patch.object( + self.multitenant_mgr, + "get_default_mediator", + async_mock.CoroutineMock(return_value=mediation_record), + ), async_mock.patch.object( + ConnectionManager, "create_did_document", autospec=True + ) as create_did_document, async_mock.patch.object( + self.route_manager, + "mediation_record_for_connection", + async_mock.CoroutineMock(return_value=None), + ): mock_wallet_create_local_did.return_value = DIDInfo( self.test_did, self.test_verkey, @@ -1167,11 +878,17 @@ async def test_create_response_multitenant(self): await self.manager.create_response( ConnRecord( state=ConnRecord.State.REQUEST, - ) + ), + my_endpoint=self.test_endpoint, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey + create_did_document.assert_called_once_with( + self.manager, + mock_wallet_create_local_did.return_value, + None, + [self.test_endpoint], + mediation_records=[mediation_record], ) + self.route_manager.route_connection_as_inviter.assert_called_once() async def test_create_response_bad_state(self): with self.assertRaises(ConnectionManagerError): @@ -1186,78 +903,67 @@ async def test_create_response_bad_state(self): ) async def test_create_response_mediation(self): - async with self.profile.session() as session: - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) - conn_rec = ConnRecord( - state=ConnRecord.State.REQUEST.rfc160, - ) - conn_rec.my_did = None + record = ConnRecord( + connection_id="test-conn-id", + invitation_key=self.test_verkey, + their_label="Hello", + their_role=ConnRecord.Role.RESPONDER.rfc160, + alias="Bob", + state=ConnRecord.State.REQUEST.rfc160, + ) - with async_mock.patch.object( - ConnRecord, "log_state", autospec=True - ) as mock_conn_log_state, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ) as mock_conn_retrieve_request, async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_save, async_mock.patch.object( - ConnectionResponse, "sign_field", autospec=True - ) as mock_sign, async_mock.patch.object( - conn_rec, "metadata_get", async_mock.CoroutineMock(return_value=False) - ): - await self.manager.create_response( - conn_rec, mediation_id=mediation_record.mediation_id - ) + # Ensure the path with new did creation is hit + record.my_did = None - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert len(message.updates) == 1 - (add,) = message.updates - assert add.action == KeylistUpdateRule.RULE_ADD - assert add.recipient_key - - async def test_create_response_bad_mediation(self): - record = async_mock.MagicMock() - with self.assertRaises(StorageNotFoundError): + with async_mock.patch.object( + ConnRecord, "log_state", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ), async_mock.patch.object( + record, "metadata_get", async_mock.CoroutineMock(return_value=False) + ), async_mock.patch.object( + ConnectionManager, "create_did_document", autospec=True + ) as create_did_document, async_mock.patch.object( + InMemoryWallet, "create_local_did" + ) as create_local_did, async_mock.patch.object( + self.route_manager, + "mediation_record_for_connection", + async_mock.CoroutineMock(return_value=mediation_record), + ), async_mock.patch.object( + record, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnectionResponse, "sign_field", autospec=True + ): + did_info = DIDInfo( + did=self.test_did, + verkey=self.test_verkey, + metadata={}, + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + ) + create_local_did.return_value = did_info await self.manager.create_response( - record, mediation_id="not-a-mediation-id" + record, + mediation_id=mediation_record.mediation_id, + my_endpoint=self.test_endpoint, ) - - async def test_create_response_mediation_not_granted(self): - async with self.profile.session() as session: - record = ConnRecord(state=ConnRecord.State.REQUEST) - with async_mock.patch.object( - ConnRecord, "retrieve_request" - ) as retrieve_request, async_mock.patch.object( - ConnectionResponse, "sign_field", autospec=True - ) as mock_sign: - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_DENIED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.create_response( - record, mediation_id=mediation_record.mediation_id - ) - - mediation_record.state = MediationRecord.STATE_REQUEST - await mediation_record.save(session) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.create_response( - record, mediation_id=mediation_record.mediation_id - ) + create_local_did.assert_called_once_with(DIDMethod.SOV, KeyType.ED25519) + create_did_document.assert_called_once_with( + self.manager, + did_info, + None, + [self.test_endpoint], + mediation_records=[mediation_record], + ) + self.route_manager.route_connection_as_inviter.assert_called_once() async def test_create_response_auto_send_mediation_request(self): conn_rec = ConnRecord( @@ -1576,9 +1282,7 @@ async def test_create_static_connection_multitenant(self): their_endpoint=self.test_endpoint, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey - ) + self.route_manager.route_static.assert_called_once() async def test_create_static_connection_multitenant_auto_disclose_features(self): self.context.update_settings( @@ -1609,9 +1313,7 @@ async def test_create_static_connection_multitenant_auto_disclose_features(self) their_verkey=self.test_target_verkey, their_endpoint=self.test_endpoint, ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey - ) + self.route_manager.route_static.assert_called_once() mock_proactive_disclose_features.assert_called_once() async def test_create_static_connection_multitenant_mediator(self): @@ -1656,7 +1358,7 @@ async def test_create_static_connection_multitenant_mediator(self): their_endpoint=self.test_endpoint, ) - assert self.multitenant_mgr.add_key.call_count is 2 + assert self.route_manager.route_static.call_count == 2 their_info = DIDInfo( self.test_target_did, @@ -1673,9 +1375,7 @@ async def test_create_static_connection_multitenant_mediator(self): [self.test_endpoint], mediation_records=[default_mediator], ), - call( - their_info, None, [self.test_endpoint], mediation_records=None - ), + call(their_info, None, [self.test_endpoint], mediation_records=[]), ] ) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py index cb515bf729..df6a3a9ca4 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py @@ -447,6 +447,47 @@ async def test_create_invitation_multitenant_public(self): self.route_manager.route_invitation.assert_called_once() + async def test_create_invitation_mediation_overwrites_routing_and_endpoint(self): + async with self.profile.session() as session: + mock_conn_rec = async_mock.MagicMock() + + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + await mediation_record.save(session) + with async_mock.patch.object( + MediationManager, + "get_default_mediator_id", + ) as mock_get_default_mediator, async_mock.patch.object( + mock_conn_rec, "metadata_set", async_mock.CoroutineMock() + ) as mock_metadata_set: + invite = await self.manager.create_invitation( + my_endpoint=TestConfig.test_endpoint, + my_label="test123", + hs_protos=[HSProto.RFC23], + mediation_id=mediation_record.mediation_id, + ) + assert isinstance(invite, InvitationRecord) + assert invite.invitation._type == DIDCommPrefix.qualify_current( + INVITATION + ) + assert invite.invitation.label == "test123" + assert ( + DIDKey.from_did( + invite.invitation.services[0].routing_keys[0] + ).public_key_b58 + == self.test_mediator_routing_keys[0] + ) + assert ( + invite.invitation.services[0].service_endpoint + == self.test_mediator_endpoint + ) + mock_get_default_mediator.assert_not_called() + async def test_create_invitation_no_handshake_no_attachments_x(self): with self.assertRaises(OutOfBandManagerError) as context: await self.manager.create_invitation( From 5d4a3edb9db4e09020d3776433946171d229c9db Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 23:25:22 -0400 Subject: [PATCH 20/37] test: didex request handler with route mgr Signed-off-by: Daniel Bluhm --- .../v1_0/handlers/request_handler.py | 1 - .../handlers/tests/test_request_handler.py | 81 ++++++++----------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py index efcc02c64d..766ecb8571 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py @@ -43,7 +43,6 @@ async def handle(self, context: RequestContext, responder: BaseResponder): if context.message_receipt.recipient_did_public else context.message_receipt.recipient_verkey ), - mediation_id=mediation_id, ) # Auto respond diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py index 21eb4c2688..ff76d6a9c6 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py @@ -8,15 +8,12 @@ PublicKeyType, Service, ) -from ......core.profile import ProfileSession from ......core.in_memory import InMemoryProfile from ......wallet.key_type import KeyType from ......wallet.did_method import DIDMethod from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder -from ......storage.base import BaseStorage -from ......storage.error import StorageNotFoundError from ......transport.inbound.receipt import MessageReceipt from .....problem_report.v1_0.message import ProblemReport @@ -103,47 +100,6 @@ async def setUp(self): did_doc_attach=self.did_doc_attach, ) - async def test_connection_record_with_mediation_metadata(self): - test_exist_conn = conn_record.ConnRecord( - my_did="did:sov:LjgpST2rjsoxYegQDRm7EL", - their_did="did:sov:LjgpST2rjsoxYegQDRm7EL", - their_public_did="did:sov:LjgpST2rjsoxYegQDRm7EL", - invitation_msg_id="12345678-1234-5678-1234-567812345678", - their_role=conn_record.ConnRecord.Role.REQUESTER, - ) - await test_exist_conn.save(self.session) - await test_exist_conn.metadata_set( - self.session, "mediation", {"id": "mediation-test-id"} - ) - test_ctx = RequestContext.test_context() - test_ctx.message = DIDXRequest() - test_ctx.message_receipt = MessageReceipt() - test_ctx.connection_record = test_exist_conn - responder = MockResponder() - handler_inst = test_module.DIDXRequestHandler() - await handler_inst.handle(test_ctx, responder) - mediation_metadata = await test_ctx.connection_record.metadata_get( - self.session, "mediation", {} - ) - assert mediation_metadata.get("id") == "mediation-test-id" - assert not responder.messages - - @async_mock.patch.object(test_module, "DIDXManager") - async def test_connection_record_without_mediation_metadata(self, mock_didx_mgr): - mock_didx_mgr.return_value.receive_request = async_mock.CoroutineMock() - self.ctx.message = DIDXRequest() - self.ctx.connection_record = None - handler_inst = test_module.DIDXRequestHandler() - responder = MockResponder() - await handler_inst.handle(self.ctx, responder) - mock_didx_mgr.return_value.receive_request.assert_called_once_with( - request=self.ctx.message, - recipient_did=self.ctx.message_receipt.recipient_did, - recipient_verkey=None, - mediation_id=None, - ) - assert not responder.messages - @async_mock.patch.object(test_module, "DIDXManager") async def test_called(self, mock_didx_mgr): mock_didx_mgr.return_value.receive_request = async_mock.CoroutineMock() @@ -156,7 +112,6 @@ async def test_called(self, mock_didx_mgr): request=self.ctx.message, recipient_did=self.ctx.message_receipt.recipient_did, recipient_verkey=None, - mediation_id=None, ) assert not responder.messages @@ -178,7 +133,41 @@ async def test_called_with_auto_response(self, mock_didx_mgr): request=self.ctx.message, recipient_did=self.ctx.message_receipt.recipient_did, recipient_verkey=None, - mediation_id=None, + ) + mock_didx_mgr.return_value.create_response.assert_called_once_with( + mock_conn_rec, mediation_id=None + ) + assert responder.messages + + @async_mock.patch.object(test_module, "DIDXManager") + async def test_connection_record_with_mediation_metadata_auto_response( + self, mock_didx_mgr + ): + test_exist_conn = conn_record.ConnRecord( + my_did="did:sov:LjgpST2rjsoxYegQDRm7EL", + their_did="did:sov:LjgpST2rjsoxYegQDRm7EL", + their_public_did="did:sov:LjgpST2rjsoxYegQDRm7EL", + invitation_msg_id="12345678-1234-5678-1234-567812345678", + their_role=conn_record.ConnRecord.Role.REQUESTER, + ) + test_exist_conn.metadata_get = async_mock.CoroutineMock( + return_value={"id": "mediation-test-id"} + ) + test_exist_conn.accept = conn_record.ConnRecord.ACCEPT_AUTO + test_exist_conn.save = async_mock.CoroutineMock() + mock_didx_mgr.return_value.receive_request = async_mock.CoroutineMock( + return_value=test_exist_conn + ) + mock_didx_mgr.return_value.create_response = async_mock.CoroutineMock() + test_ctx = RequestContext.test_context() + test_ctx.message = DIDXRequest() + test_ctx.message_receipt = MessageReceipt() + test_ctx.connection_record = test_exist_conn + responder = MockResponder() + handler_inst = test_module.DIDXRequestHandler() + await handler_inst.handle(test_ctx, responder) + mock_didx_mgr.return_value.create_response.assert_called_once_with( + test_exist_conn, mediation_id="mediation-test-id" ) assert responder.messages From 23a2981b028f89b49696258546a94f9c540abb16 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 23:50:07 -0400 Subject: [PATCH 21/37] test: didexchange manager with route manager Signed-off-by: Daniel Bluhm --- .../didexchange/v1_0/tests/test_manager.py | 406 +----------------- 1 file changed, 23 insertions(+), 383 deletions(-) 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 3ffd26a181..0cbec5deef 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -35,6 +35,7 @@ from .....connections.base_manager import BaseConnectionManagerError from ....coordinate_mediation.v1_0.manager import MediationManager +from ....coordinate_mediation.v1_0.route_manager import RouteManager from ....coordinate_mediation.v1_0.messages.keylist_update import ( KeylistUpdate, KeylistUpdateRule, @@ -124,6 +125,9 @@ async def setUp(self): self.context.injector.bind_instance( BaseMultitenantManager, self.multitenant_mgr ) + self.multitenant_mgr.get_default_mediator = async_mock.CoroutineMock( + return_value=None + ) self.manager = DIDXManager(self.profile) assert self.manager.profile @@ -136,6 +140,19 @@ async def setUp(self): self.test_mediator_conn_id = "mediator-conn-id" self.test_mediator_endpoint = "http://mediator.example.com" + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=([], self.test_endpoint) + ) + self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( + return_value=None + ) + self.route_manager.mediation_record_for_connection = async_mock.CoroutineMock( + return_value=None + ) + self.manager._route_manager = self.route_manager + self.oob_manager._route_manager = self.route_manager + async def test_verify_diddoc(self): async with self.profile.session() as session: did_doc = self.make_did_doc( @@ -290,15 +307,6 @@ async def test_create_request_implicit(self): async def test_create_request_implicit_use_public_did(self): async with self.profile.session() as session: - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - info_public = await session.wallet.create_public_did( DIDMethod.SOV, KeyType.ED25519, @@ -307,7 +315,7 @@ async def test_create_request_implicit_use_public_did(self): their_public_did=TestConfig.test_target_did, my_label=None, my_endpoint=None, - mediation_id=mediation_record._id, + mediation_id=None, use_public_did=True, alias="Tester", ) @@ -396,9 +404,7 @@ async def test_create_request_multitenant(self): ) ) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", TestConfig.test_verkey - ) + self.route_manager.route_connection_as_invitee.assert_called_once() async def test_create_request_mediation_id(self): async with self.profile.session() as session: @@ -443,13 +449,8 @@ async def test_create_request_mediation_id(self): mediation_id=mediation_record._id, ) assert didx_req - assert len(self.responder.messages) == 1 - message, used_kwargs = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert ( - "connection_id" in used_kwargs - and used_kwargs["connection_id"] == self.test_mediator_conn_id - ) + + self.route_manager.route_connection_as_invitee.assert_called_once() async def test_create_request_my_endpoint(self): mock_conn_rec = async_mock.MagicMock( @@ -590,7 +591,6 @@ async def test_receive_request_explicit_public_did(self): my_endpoint=None, alias=None, auto_accept_implicit=None, - mediation_id=None, ) assert conn_rec self.oob_mock.clean_finished_oob_record.assert_called_once_with( @@ -626,183 +626,9 @@ async def test_receive_request_invi_not_found(self): my_endpoint=None, alias=None, auto_accept_implicit=None, - mediation_id=None, ) assert "No explicit invitation found" in str(context.exception) - async def test_receive_request_with_mediator_without_multi_use_multitenant(self): - async with self.profile.session() as session: - multiuse_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - did_doc_dict = self.make_did_doc( - did=TestConfig.test_target_did, - verkey=TestConfig.test_target_verkey, - ).serialize() - del did_doc_dict["authentication"] - del did_doc_dict["service"] - new_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock( - is_multiuse_invitation=False, invitation_key=multiuse_info.verkey - ) - mock_request.connection.did = TestConfig.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = TestConfig.test_did - mock_request.did = self.test_target_did - mock_request.did_doc_attach = async_mock.MagicMock( - data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), - signed=async_mock.MagicMock( - decode=async_mock.MagicMock( - return_value=json.dumps(did_doc_dict) - ) - ), - ) - ) - - await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - seed=None, - did=TestConfig.test_did, - ) - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - - record = ConnRecord( - invitation_key=TestConfig.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) - record.accept = ConnRecord.ACCEPT_MANUAL - await record.save(session) - - with async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - mock_conn_retrieve_by_invitation_key.return_value = record - conn_rec = await self.manager.receive_request( - request=mock_request, - recipient_did=TestConfig.test_did, - recipient_verkey=TestConfig.test_verkey, - my_endpoint=None, - alias=None, - auto_accept_implicit=None, - mediation_id=mediation_record.mediation_id, - ) - - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert len(message.updates) == 1 - (remove,) = message.updates - assert remove.action == KeylistUpdateRule.RULE_REMOVE - assert remove.recipient_key == record.invitation_key - - async def test_receive_request_with_mediator_without_multi_use_multitenant_mismatch( - self, - ): - async with self.profile.session() as session: - multiuse_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - did_doc_dict = self.make_did_doc( - did=TestConfig.test_target_did, - verkey=TestConfig.test_target_verkey, - ).serialize() - del did_doc_dict["authentication"] - del did_doc_dict["service"] - new_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - - mock_request = async_mock.MagicMock() - mock_request.connection = async_mock.MagicMock( - is_multiuse_invitation=False, invitation_key=multiuse_info.verkey - ) - mock_request.connection.did = TestConfig.test_did - mock_request.connection.did_doc = async_mock.MagicMock() - mock_request.connection.did_doc.did = TestConfig.test_did - mock_request.did_doc_attach = async_mock.MagicMock( - data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), - signed=async_mock.MagicMock( - decode=async_mock.MagicMock( - return_value=json.dumps(did_doc_dict) - ) - ), - ) - ) - - await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - seed=None, - did=TestConfig.test_did, - ) - - mediation_record = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id=self.test_mediator_conn_id, - routing_keys=self.test_mediator_routing_keys, - endpoint=self.test_mediator_endpoint, - ) - await mediation_record.save(session) - - record = ConnRecord( - invitation_key=TestConfig.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) - record.accept = ConnRecord.ACCEPT_MANUAL - await record.save(session) - - with async_mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, async_mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, async_mock.patch.object( - ConnRecord, "retrieve_by_invitation_key" - ) as mock_conn_retrieve_by_invitation_key, async_mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - mock_conn_retrieve_by_invitation_key.return_value = record - with self.assertRaises(DIDXManagerError) as context: - conn_rec = await self.manager.receive_request( - request=mock_request, - recipient_did=TestConfig.test_did, - recipient_verkey=TestConfig.test_verkey, - my_endpoint=None, - alias=None, - auto_accept_implicit=None, - mediation_id=mediation_record.mediation_id, - ) - assert "does not match" in str(context.exception) - async def test_receive_request_public_did_no_did_doc_attachment(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock( @@ -851,7 +677,6 @@ async def test_receive_request_public_did_no_did_doc_attachment(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=None, - mediation_id=None, ) assert "DID Doc attachment missing or has no data" in str( context.exception @@ -896,7 +721,6 @@ async def test_receive_request_public_did_x_not_public(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) assert "is not public" in str(context.exception) @@ -964,7 +788,6 @@ async def test_receive_request_public_did_x_wrong_did(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) assert "does not match" in str(context.exception) @@ -1027,7 +850,6 @@ async def test_receive_request_public_did_x_did_doc_attach_bad_sig(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) async def test_receive_request_public_did_no_public_invites(self): @@ -1075,7 +897,6 @@ async def test_receive_request_public_did_no_public_invites(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) assert "Public invitations are not enabled" in str(context.exception) @@ -1149,7 +970,6 @@ async def test_receive_request_public_did_no_auto_accept(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) assert conn_rec @@ -1240,183 +1060,12 @@ async def test_receive_request_peer_did(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) assert conn_rec mock_conn_rec_cls.return_value.metadata_set.assert_called() assert not self.responder.messages - async def test_receive_request_multiuse_multitenant(self): - async with self.profile.session() as session: - multiuse_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - new_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - - mock_request = async_mock.MagicMock( - did=TestConfig.test_did, - did_doc_attach=async_mock.MagicMock( - data=async_mock.MagicMock( - signed=async_mock.MagicMock( - decode=async_mock.MagicMock(return_value="dummy-did-doc") - ), - ) - ), - _thread=async_mock.MagicMock(pthid="dummy-pthid"), - ) - - self.context.update_settings( - {"wallet.id": "test_wallet", "multitenant.enabled": True} - ) - ACCEPT_MANUAL = ConnRecord.ACCEPT_MANUAL - with async_mock.patch.object( - test_module, "ConnRecord", autospec=True - ) as mock_conn_rec_cls, async_mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did, async_mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, async_mock.patch.object( - self.manager, - "verify_diddoc", - async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), - ): - mock_conn_rec = async_mock.CoroutineMock( - connection_id="dummy", - accept=ACCEPT_MANUAL, - is_multiuse_invitation=True, - attach_request=async_mock.CoroutineMock(), - save=async_mock.CoroutineMock(), - retrieve_invitation=async_mock.CoroutineMock(return_value={}), - metadata_get_all=async_mock.CoroutineMock(return_value={}), - retrieve_request=async_mock.CoroutineMock(), - ) - mock_conn_rec_cls.return_value = mock_conn_rec - mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( - return_value=mock_conn_rec - ) - mock_wallet_create_local_did.return_value = DIDInfo( - new_info.did, - new_info.verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - mock_did_doc.from_json = async_mock.MagicMock( - return_value=async_mock.MagicMock(did=TestConfig.test_did) - ) - await self.manager.receive_request( - request=mock_request, - recipient_did=TestConfig.test_did, - recipient_verkey=TestConfig.test_verkey, - my_endpoint=TestConfig.test_endpoint, - alias="Alias", - auto_accept_implicit=False, - mediation_id=None, - ) - - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", new_info.verkey - ) - - async def test_receive_request_implicit_multitenant(self): - async with self.profile.session() as session: - new_info = await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - - mock_request = async_mock.MagicMock( - did=TestConfig.test_did, - did_doc_attach=async_mock.MagicMock( - data=async_mock.MagicMock( - signed=async_mock.MagicMock( - decode=async_mock.MagicMock(return_value="dummy-did-doc") - ), - ) - ), - _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), - ) - - self.context.update_settings( - { - "wallet.id": "test_wallet", - "multitenant.enabled": True, - "public_invites": True, - "debug.auto_accept_requests": False, - } - ) - - ACCEPT_MANUAL = ConnRecord.ACCEPT_MANUAL - with async_mock.patch.object( - test_module, "ConnRecord", autospec=True - ) as mock_conn_rec_cls, async_mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did, async_mock.patch.object( - InMemoryWallet, "get_local_did", autospec=True - ) as mock_wallet_get_local_did, async_mock.patch.object( - test_module, "DIDPosture", autospec=True - ) as mock_did_posture, async_mock.patch.object( - test_module, "DIDDoc", autospec=True - ) as mock_did_doc, async_mock.patch.object( - self.manager, - "verify_diddoc", - async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), - ): - mock_conn_rec = async_mock.CoroutineMock( - connection_id="dummy", - accept=ACCEPT_MANUAL, - is_multiuse_invitation=False, - attach_request=async_mock.CoroutineMock(), - save=async_mock.CoroutineMock(), - retrieve_invitation=async_mock.CoroutineMock(return_value={}), - metadata_get_all=async_mock.CoroutineMock(return_value={}), - retrieve_request=async_mock.CoroutineMock(), - ) - mock_conn_rec_cls.return_value = mock_conn_rec - mock_conn_rec_cls.retrieve_by_invitation_msg_id = ( - async_mock.CoroutineMock(return_value=[]) - ) - - mock_did_posture.get = async_mock.MagicMock( - return_value=test_module.DIDPosture.PUBLIC - ) - - mock_wallet_create_local_did.return_value = DIDInfo( - new_info.did, - new_info.verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - mock_did_doc.from_json = async_mock.MagicMock( - return_value=async_mock.MagicMock(did=TestConfig.test_did) - ) - mock_wallet_get_local_did.return_value = DIDInfo( - TestConfig.test_did, - TestConfig.test_verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - await self.manager.receive_request( - request=mock_request, - recipient_did=TestConfig.test_did, - recipient_verkey=None, - my_endpoint=TestConfig.test_endpoint, - alias="Alias", - auto_accept_implicit=False, - mediation_id=None, - ) - - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", new_info.verkey - ) - async def test_receive_request_peer_did_not_found_x(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock( @@ -1453,7 +1102,6 @@ async def test_receive_request_peer_did_not_found_x(self): my_endpoint=TestConfig.test_endpoint, alias="Alias", auto_accept_implicit=False, - mediation_id=None, ) async def test_create_response(self): @@ -1536,13 +1184,7 @@ async def test_create_response_mediation_id(self): record, mediation_id=mediation_record.mediation_id ) - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, KeylistUpdate) - assert len(message.updates) == 1 - (add,) = message.updates - assert add.action == KeylistUpdateRule.RULE_ADD - assert add.recipient_key + self.route_manager.route_connection_as_inviter.assert_called_once() async def test_create_response_mediation_id_invalid_conn_state(self): async with self.profile.session() as session: @@ -1629,9 +1271,7 @@ async def test_create_response_multitenant(self): ) await self.manager.create_response(conn_rec) - self.multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", TestConfig.test_verkey - ) + self.route_manager.route_connection_as_inviter.assert_called_once() async def test_create_response_conn_rec_my_did(self): conn_rec = ConnRecord( From 58faf2015c9c2633648480cfb799681e6ac7bda4 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Jun 2022 23:53:28 -0400 Subject: [PATCH 22/37] style: flake8 fixes for route manager Signed-off-by: Daniel Bluhm --- .../protocols/coordinate_mediation/v1_0/route_manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 6dca92c02c..32f42805da 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -36,6 +36,7 @@ class RouteManager(ABC): """Base Route Manager.""" def __init__(self, profile: Profile): + """Initialize route manager.""" self.profile = profile async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: @@ -55,7 +56,7 @@ async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: return my_info def _validate_mediation_state(self, mediation_record: MediationRecord): - """Perform mediation state validation""" + """Perform mediation state validation.""" if mediation_record.state != MediationRecord.STATE_GRANTED: raise RouteManagerError( "Mediation is not granted for mediation identified by " @@ -154,7 +155,7 @@ async def route_connection( conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: - """Setup routing for a connection. + """Set up routing for a connection. This method will evaluate connection state and call the appropriate methods. """ @@ -267,6 +268,7 @@ async def _route_for_key( async def routing_info( self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None ) -> Tuple[List[str], str]: + """Return routing info for mediator.""" if mediation_record: return mediation_record.routing_keys, mediation_record.endpoint From 0e3e03084bd57d51b9feb6eb4915ff9a4216de37 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 1 Jul 2022 11:58:17 -0400 Subject: [PATCH 23/37] test: route manager and mediation route manager Signed-off-by: Daniel Bluhm --- .../v1_0/route_manager.py | 7 +- .../v1_0/tests/test_route_manager.py | 590 ++++++++++++++++++ 2 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 32f42805da..be67b3c9e6 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -69,12 +69,7 @@ async def mediation_record_for_connection( mediation_id: Optional[str] = None, or_default: bool = False, ): - """Validate mediation and return record. - - If mediation_id is not None, - validate mediation record state and return record - else, return None - """ + """Return relevant mediator for connection.""" async with self.profile.session() as session: mediation_metadata = await conn_record.metadata_get( session, MediationManager.METADATA_KEY, {} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py new file mode 100644 index 0000000000..3bd3d9e7ff --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py @@ -0,0 +1,590 @@ +from asynctest import mock +import pytest + +from .....connections.models.conn_record import ConnRecord +from .....core.in_memory import InMemoryProfile +from .....core.profile import Profile +from .....messaging.responder import BaseResponder, MockResponder +from .....storage.error import StorageNotFoundError +from .....wallet.did_info import DIDInfo +from .....wallet.in_memory import InMemoryWallet +from ....routing.v1_0.models.route_record import RouteRecord +from ..manager import MediationManager +from ..messages.keylist_update import KeylistUpdate +from ..models.mediation_record import MediationRecord +from ..route_manager import ( + CoordinateMediationV1RouteManager, + RouteManager, + RouteManagerError, +) + + +class TestRouteManager(RouteManager): + """Concretion of RouteManager for testing.""" + + _route_for_key = mock.CoroutineMock() + routing_info = mock.CoroutineMock() + + +@pytest.fixture +def mock_responder(): + yield MockResponder() + + +@pytest.fixture +def profile(mock_responder: MockResponder): + yield InMemoryProfile.test_profile(bind={BaseResponder: mock_responder}) + + +@pytest.fixture +def route_manager(profile: Profile): + manager = TestRouteManager(profile) + manager._route_for_key = mock.CoroutineMock( + return_value=mock.MagicMock(KeylistUpdate) + ) + manager.routing_info = mock.CoroutineMock(return_value=([], "http://example.com")) + yield manager + + +@pytest.fixture +def mediation_route_manager(profile: Profile): + yield CoordinateMediationV1RouteManager(profile) + + +@pytest.fixture +def conn_record(): + record = ConnRecord() + record.metadata_get = mock.CoroutineMock(return_value={}) + record.metadata_set = mock.CoroutineMock() + yield record + + +@pytest.mark.asyncio +async def test_get_or_create_my_did_no_did( + route_manager: RouteManager, conn_record: ConnRecord +): + conn_record.my_did = None + mock_did_info = mock.MagicMock() + with mock.patch.object( + InMemoryWallet, + "create_local_did", + mock.CoroutineMock(return_value=mock_did_info), + ) as mock_create_local_did, mock.patch.object( + conn_record, "save", mock.CoroutineMock() + ) as mock_save: + info = await route_manager.get_or_create_my_did(conn_record) + assert mock_did_info == info + mock_create_local_did.assert_called_once() + mock_save.assert_called_once() + + +@pytest.mark.asyncio +async def test_get_or_create_my_did_existing_did( + route_manager: RouteManager, conn_record: ConnRecord +): + conn_record.my_did = "test-did" + mock_did_info = mock.MagicMock(DIDInfo) + with mock.patch.object( + InMemoryWallet, "get_local_did", mock.CoroutineMock(return_value=mock_did_info) + ) as mock_get_local_did: + info = await route_manager.get_or_create_my_did(conn_record) + assert mock_did_info == info + mock_get_local_did.assert_called_once() + + +@pytest.mark.asyncio +async def test_mediation_record_for_connection_mediation_id( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + with mock.patch.object( + route_manager, + "mediation_record_if_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_mediation_record_if_id, mock.patch.object( + route_manager, "save_mediator_for_connection", mock.CoroutineMock() + ): + assert ( + await route_manager.mediation_record_for_connection( + conn_record, mediation_record.mediation_id + ) + == mediation_record + ) + mock_mediation_record_if_id.assert_called_once_with( + mediation_record.mediation_id, False + ) + + +@pytest.mark.asyncio +async def test_mediation_record_for_connection_mediation_metadata( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + conn_record.metadata_get.return_value = { + MediationManager.METADATA_ID: mediation_record.mediation_id + } + with mock.patch.object( + route_manager, + "mediation_record_if_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_mediation_record_if_id, mock.patch.object( + route_manager, "save_mediator_for_connection", mock.CoroutineMock() + ): + assert ( + await route_manager.mediation_record_for_connection( + conn_record, "another-mediation-id" + ) + == mediation_record + ) + mock_mediation_record_if_id.assert_called_once_with( + mediation_record.mediation_id, False + ) + + +@pytest.mark.asyncio +async def test_mediation_record_for_connection_default( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + with mock.patch.object( + route_manager, + "mediation_record_if_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_mediation_record_if_id, mock.patch.object( + route_manager, "save_mediator_for_connection", mock.CoroutineMock() + ): + assert ( + await route_manager.mediation_record_for_connection( + conn_record, None, or_default=True + ) + == mediation_record + ) + mock_mediation_record_if_id.assert_called_once_with(None, True) + + +@pytest.mark.asyncio +async def test_mediation_record_if_id_with_id(route_manager: RouteManager): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", state=MediationRecord.STATE_GRANTED + ) + with mock.patch.object( + MediationRecord, + "retrieve_by_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_retrieve_by_id: + actual = await route_manager.mediation_record_if_id( + mediation_id=mediation_record.mediation_id + ) + assert mediation_record == actual + mock_retrieve_by_id.assert_called_once() + + +@pytest.mark.asyncio +async def test_mediation_record_if_id_with_id_bad_state(route_manager: RouteManager): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", state=MediationRecord.STATE_DENIED + ) + with mock.patch.object( + MediationRecord, + "retrieve_by_id", + mock.CoroutineMock(return_value=mediation_record), + ): + with pytest.raises(RouteManagerError): + await route_manager.mediation_record_if_id( + mediation_id=mediation_record.mediation_id + ) + + +@pytest.mark.asyncio +async def test_mediation_record_if_id_with_id_and_default(route_manager: RouteManager): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", state=MediationRecord.STATE_GRANTED + ) + with mock.patch.object( + MediationRecord, + "retrieve_by_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_retrieve_by_id, mock.patch.object( + MediationManager, "get_default_mediator", mock.CoroutineMock() + ) as mock_get_default_mediator: + actual = await route_manager.mediation_record_if_id( + mediation_id=mediation_record.mediation_id, or_default=True + ) + assert mediation_record == actual + mock_retrieve_by_id.assert_called_once() + mock_get_default_mediator.assert_not_called() + + +@pytest.mark.asyncio +async def test_mediation_record_if_id_without_id_and_default( + route_manager: RouteManager, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", state=MediationRecord.STATE_GRANTED + ) + with mock.patch.object( + MediationRecord, "retrieve_by_id", mock.CoroutineMock() + ) as mock_retrieve_by_id, mock.patch.object( + MediationManager, + "get_default_mediator", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_get_default_mediator: + actual = await route_manager.mediation_record_if_id( + mediation_id=None, or_default=True + ) + assert mediation_record == actual + mock_retrieve_by_id.assert_not_called() + mock_get_default_mediator.assert_called_once() + + +@pytest.mark.asyncio +async def test_mediation_record_if_id_without_id_and_no_default( + route_manager: RouteManager, +): + with mock.patch.object( + MediationRecord, "retrieve_by_id", mock.CoroutineMock(return_value=None) + ) as mock_retrieve_by_id, mock.patch.object( + MediationManager, "get_default_mediator", mock.CoroutineMock(return_value=None) + ) as mock_get_default_mediator: + assert ( + await route_manager.mediation_record_if_id( + mediation_id=None, or_default=True + ) + is None + ) + mock_retrieve_by_id.assert_not_called() + mock_get_default_mediator.assert_called_once() + + +@pytest.mark.asyncio +async def test_route_connection_as_invitee( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + mock_did_info = mock.MagicMock(DIDInfo) + with mock.patch.object( + route_manager, + "get_or_create_my_did", + mock.CoroutineMock(return_value=mock_did_info), + ): + await route_manager.route_connection_as_invitee(conn_record, mediation_record) + route_manager._route_for_key.assert_called_once_with( + mock_did_info.verkey, mediation_record, skip_if_exists=True + ) + + +@pytest.mark.asyncio +async def test_route_connection_as_inviter( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + mock_did_info = mock.MagicMock(DIDInfo) + conn_record.invitation_key = "test-invitation-key" + with mock.patch.object( + route_manager, + "get_or_create_my_did", + mock.CoroutineMock(return_value=mock_did_info), + ): + await route_manager.route_connection_as_inviter(conn_record, mediation_record) + route_manager._route_for_key.assert_called_once_with( + mock_did_info.verkey, + mediation_record, + replace_key="test-invitation-key", + skip_if_exists=True, + ) + + +@pytest.mark.asyncio +async def test_route_connection_state_invitee( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + conn_record.state = "invitation" + conn_record.their_role = "responder" + with mock.patch.object( + route_manager, "route_connection_as_invitee", mock.CoroutineMock() + ) as mock_route_connection_as_invitee, mock.patch.object( + route_manager, "route_connection_as_inviter", mock.CoroutineMock() + ) as mock_route_connection_as_inviter: + await route_manager.route_connection(conn_record, mediation_record) + mock_route_connection_as_invitee.assert_called_once() + mock_route_connection_as_inviter.assert_not_called() + + +@pytest.mark.asyncio +async def test_route_connection_state_inviter( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + conn_record.state = "request" + conn_record.their_role = "requester" + with mock.patch.object( + route_manager, "route_connection_as_invitee", mock.CoroutineMock() + ) as mock_route_connection_as_invitee, mock.patch.object( + route_manager, "route_connection_as_inviter", mock.CoroutineMock() + ) as mock_route_connection_as_inviter: + await route_manager.route_connection(conn_record, mediation_record) + mock_route_connection_as_inviter.assert_called_once() + mock_route_connection_as_invitee.assert_not_called() + + +@pytest.mark.asyncio +async def test_route_connection_state_other( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + conn_record.state = "response" + conn_record.their_role = "requester" + assert await route_manager.route_connection(conn_record, mediation_record) is None + + +@pytest.mark.asyncio +async def test_route_invitation_with_key( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + conn_record.invitation_key = "test-invitation-key" + with mock.patch.object( + route_manager, "save_mediator_for_connection", mock.CoroutineMock() + ): + await route_manager.route_invitation(conn_record, mediation_record) + route_manager._route_for_key.assert_called_once() + + +@pytest.mark.asyncio +async def test_route_invitation_without_key( + route_manager: RouteManager, conn_record: ConnRecord +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + with mock.patch.object( + route_manager, "save_mediator_for_connection", mock.CoroutineMock() + ): + with pytest.raises(ValueError): + await route_manager.route_invitation(conn_record, mediation_record) + route_manager._route_for_key.assert_not_called() + + +@pytest.mark.asyncio +async def test_route_public_did(route_manager: RouteManager): + await route_manager.route_public_did("test-verkey") + route_manager._route_for_key.assert_called_once_with( + "test-verkey", skip_if_exists=True + ) + + +@pytest.mark.asyncio +async def test_route_static(route_manager: RouteManager, conn_record: ConnRecord): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + mock_did_info = mock.MagicMock(DIDInfo) + conn_record.invitation_key = "test-invitation-key" + with mock.patch.object( + route_manager, + "get_or_create_my_did", + mock.CoroutineMock(return_value=mock_did_info), + ): + await route_manager.route_static(conn_record, mediation_record) + route_manager._route_for_key.assert_called_once_with( + mock_did_info.verkey, + mediation_record, + skip_if_exists=True, + ) + + +@pytest.mark.asyncio +async def test_save_mediator_for_connection_record( + route_manager: RouteManager, conn_record: ConnRecord, profile: Profile +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + session = mock.MagicMock() + profile.session = mock.MagicMock(return_value=session) + session.__aenter__ = mock.CoroutineMock(return_value=session) + session.__aexit__ = mock.CoroutineMock() + with mock.patch.object( + MediationRecord, "retrieve_by_id", mock.CoroutineMock() + ) as mock_retrieve_by_id: + await route_manager.save_mediator_for_connection(conn_record, mediation_record) + mock_retrieve_by_id.assert_not_called() + conn_record.metadata_set.assert_called_once_with( + session, + MediationManager.METADATA_KEY, + {MediationManager.METADATA_ID: mediation_record.mediation_id}, + ) + + +@pytest.mark.asyncio +async def test_save_mediator_for_connection_id( + route_manager: RouteManager, conn_record: ConnRecord, profile: Profile +): + mediation_record = MediationRecord(mediation_id="test-mediation-id") + session = mock.MagicMock() + profile.session = mock.MagicMock(return_value=session) + session.__aenter__ = mock.CoroutineMock(return_value=session) + session.__aexit__ = mock.CoroutineMock() + with mock.patch.object( + MediationRecord, + "retrieve_by_id", + mock.CoroutineMock(return_value=mediation_record), + ) as mock_retrieve_by_id: + await route_manager.save_mediator_for_connection( + conn_record, mediation_id=mediation_record.mediation_id + ) + mock_retrieve_by_id.assert_called_once() + conn_record.metadata_set.assert_called_once_with( + session, + MediationManager.METADATA_KEY, + {MediationManager.METADATA_ID: mediation_record.mediation_id}, + ) + + +@pytest.mark.asyncio +async def test_save_mediator_for_connection_no_mediator( + route_manager: RouteManager, conn_record: ConnRecord, profile: Profile +): + with mock.patch.object( + MediationRecord, "retrieve_by_id", mock.CoroutineMock() + ) as mock_retrieve_by_id: + await route_manager.save_mediator_for_connection(conn_record) + mock_retrieve_by_id.assert_not_called() + conn_record.metadata_set.assert_not_called() + + +@pytest.mark.asyncio +async def test_mediation_route_for_key( + mediation_route_manager: CoordinateMediationV1RouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + keylist_update = await mediation_route_manager._route_for_key( + "test-recipient-key", mediation_record, skip_if_exists=False, replace_key=None + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_mediation_route_for_key_skip_if_exists_and_exists( + mediation_route_manager: CoordinateMediationV1RouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + with mock.patch.object( + RouteRecord, "retrieve_by_recipient_key", mock.CoroutineMock() + ): + keylist_update = await mediation_route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=True, + replace_key=None, + ) + assert keylist_update is None + assert not mock_responder.messages + + +@pytest.mark.asyncio +async def test_mediation_route_for_key_skip_if_exists_and_absent( + mediation_route_manager: CoordinateMediationV1RouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + with mock.patch.object( + RouteRecord, + "retrieve_by_recipient_key", + mock.CoroutineMock(side_effect=StorageNotFoundError), + ): + keylist_update = await mediation_route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=True, + replace_key=None, + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_mediation_route_for_key_replace_key( + mediation_route_manager: CoordinateMediationV1RouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + keylist_update = await mediation_route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=False, + replace_key="test-replace-key", + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"}, + {"action": "remove", "recipient_key": "test-replace-key"}, + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_mediation_route_for_key_no_mediator( + mediation_route_manager: CoordinateMediationV1RouteManager, +): + assert ( + await mediation_route_manager._route_for_key( + "test-recipient-key", + None, + skip_if_exists=True, + replace_key="test-replace-key", + ) + is None + ) + + +@pytest.mark.asyncio +async def test_mediation_routing_info_with_mediator( + mediation_route_manager: CoordinateMediationV1RouteManager, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", + connection_id="test-mediator-conn-id", + routing_keys=["test-key-0", "test-key-1"], + endpoint="http://mediator.example.com", + ) + keys, endpoint = await mediation_route_manager.routing_info( + "http://example.com", mediation_record + ) + assert keys == mediation_record.routing_keys + assert endpoint == mediation_record.endpoint + + +@pytest.mark.asyncio +async def test_mediation_routing_info_no_mediator( + mediation_route_manager: CoordinateMediationV1RouteManager, +): + keys, endpoint = await mediation_route_manager.routing_info( + "http://example.com", None + ) + assert keys == [] + assert endpoint == "http://example.com" From 805c43d6df7a3fccf5bed2233ebeeb70d773777d Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 1 Jul 2022 14:08:38 -0400 Subject: [PATCH 24/37] test: multitenant route manager Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/route_manager.py | 7 +- .../multitenant/tests/test_route_manager.py | 348 ++++++++++++++++++ 2 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 aries_cloudagent/multitenant/tests/test_route_manager.py diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index c79cdd6faa..0832f7ec26 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -53,9 +53,10 @@ async def _route_for_key( ) routing_mgr = RoutingManager(self.root_profile) mediation_mgr = MediationManager(self.root_profile) - # Passed in mediation_record is ignored altogether. - # Only the base mediator needs updates. - mediation_record = await self.get_base_wallet_mediator() + # If base wallet had mediator, only notify that mediator. + # Else, if subwallet has mediator, notify that mediator. + base_mediation_record = await self.get_base_wallet_mediator() + mediation_record = base_mediation_record or mediation_record if skip_if_exists: try: diff --git a/aries_cloudagent/multitenant/tests/test_route_manager.py b/aries_cloudagent/multitenant/tests/test_route_manager.py new file mode 100644 index 0000000000..0dc996f087 --- /dev/null +++ b/aries_cloudagent/multitenant/tests/test_route_manager.py @@ -0,0 +1,348 @@ +import pytest +from asynctest import mock + +from aries_cloudagent.protocols.routing.v1_0.manager import RoutingManager + +from ...connections.models.conn_record import ConnRecord +from ...core.in_memory import InMemoryProfile +from ...core.profile import Profile +from ...messaging.responder import BaseResponder, MockResponder +from ...messaging.responder import BaseResponder, MockResponder +from ...protocols.coordinate_mediation.v1_0.manager import MediationManager +from ...protocols.coordinate_mediation.v1_0.messages.keylist_update import KeylistUpdate +from ...protocols.coordinate_mediation.v1_0.models.mediation_record import ( + MediationRecord, +) +from ...protocols.coordinate_mediation.v1_0.route_manager import ( + RouteManager, + RouteManagerError, +) +from ...protocols.routing.v1_0.models.route_record import RouteRecord +from ...storage.error import StorageNotFoundError +from ...wallet.did_info import DIDInfo +from ...wallet.in_memory import InMemoryWallet +from ..route_manager import MultitenantRouteManager + + +@pytest.fixture +def wallet_id(): + yield "test-wallet-id" + + +@pytest.fixture +def mock_responder(): + yield MockResponder() + + +@pytest.fixture +def root_profile(mock_responder: MockResponder): + yield InMemoryProfile.test_profile( + bind={ + BaseResponder: mock_responder, + } + ) + + +@pytest.fixture +def sub_profile(mock_responder: MockResponder, wallet_id: str): + yield InMemoryProfile.test_profile( + settings={ + "wallet.id": wallet_id, + }, + bind={ + BaseResponder: mock_responder, + }, + ) + + +@pytest.fixture +def route_manager(root_profile: Profile, sub_profile: Profile, wallet_id: str): + yield MultitenantRouteManager(root_profile, sub_profile, wallet_id) + + +def test_sub_profile_access( + route_manager: MultitenantRouteManager, sub_profile: Profile +): + assert route_manager.sub_profile == sub_profile + + +@pytest.mark.asyncio +async def test_route_for_key_sub_mediator_no_base_mediator( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, + wallet_id: str, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + + with mock.patch.object( + route_manager, "get_base_wallet_mediator", mock.CoroutineMock(return_value=None) + ), mock.patch.object( + RoutingManager, "create_route_record", mock.CoroutineMock() + ) as mock_create_route_record: + keylist_update = await route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=False, + replace_key=None, + ) + + mock_create_route_record.assert_called_once_with( + recipient_key="test-recipient-key", internal_wallet_id=wallet_id + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_route_for_key_sub_mediator_and_base_mediator( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, + wallet_id: str, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + base_mediation_record = MediationRecord( + mediation_id="test-base-mediation-id", + connection_id="test-base-mediator-conn-id", + ) + + with mock.patch.object( + route_manager, + "get_base_wallet_mediator", + mock.CoroutineMock(return_value=base_mediation_record), + ), mock.patch.object( + RoutingManager, "create_route_record", mock.CoroutineMock() + ) as mock_create_route_record: + keylist_update = await route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=False, + replace_key=None, + ) + + mock_create_route_record.assert_called_once_with( + recipient_key="test-recipient-key", internal_wallet_id=wallet_id + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-base-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_route_for_key_base_mediator_no_sub_mediator( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, + wallet_id: str, +): + base_mediation_record = MediationRecord( + mediation_id="test-base-mediation-id", + connection_id="test-base-mediator-conn-id", + ) + + with mock.patch.object( + route_manager, + "get_base_wallet_mediator", + mock.CoroutineMock(return_value=base_mediation_record), + ), mock.patch.object( + RoutingManager, "create_route_record", mock.CoroutineMock() + ) as mock_create_route_record: + keylist_update = await route_manager._route_for_key( + "test-recipient-key", None, skip_if_exists=False, replace_key=None + ) + + mock_create_route_record.assert_called_once_with( + recipient_key="test-recipient-key", internal_wallet_id=wallet_id + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-base-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_route_for_key_skip_if_exists_and_exists( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + with mock.patch.object( + RouteRecord, "retrieve_by_recipient_key", mock.CoroutineMock() + ): + keylist_update = await route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=True, + replace_key=None, + ) + assert keylist_update is None + assert not mock_responder.messages + + +@pytest.mark.asyncio +async def test_route_for_key_skip_if_exists_and_absent( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + with mock.patch.object( + RouteRecord, + "retrieve_by_recipient_key", + mock.CoroutineMock(side_effect=StorageNotFoundError), + ): + keylist_update = await route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=True, + replace_key=None, + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"} + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_route_for_key_replace_key( + route_manager: MultitenantRouteManager, + mock_responder: MockResponder, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" + ) + keylist_update = await route_manager._route_for_key( + "test-recipient-key", + mediation_record, + skip_if_exists=False, + replace_key="test-replace-key", + ) + assert keylist_update + assert keylist_update.serialize()["updates"] == [ + {"action": "add", "recipient_key": "test-recipient-key"}, + {"action": "remove", "recipient_key": "test-replace-key"}, + ] + assert mock_responder.messages + assert ( + keylist_update, + {"connection_id": "test-mediator-conn-id"}, + ) == mock_responder.messages[0] + + +@pytest.mark.asyncio +async def test_route_for_key_no_mediator( + route_manager: MultitenantRouteManager, +): + assert ( + await route_manager._route_for_key( + "test-recipient-key", + None, + skip_if_exists=True, + replace_key="test-replace-key", + ) + is None + ) + + +@pytest.mark.asyncio +async def test_routing_info_with_mediator( + route_manager: MultitenantRouteManager, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", + connection_id="test-mediator-conn-id", + routing_keys=["test-key-0", "test-key-1"], + endpoint="http://mediator.example.com", + ) + keys, endpoint = await route_manager.routing_info( + "http://example.com", mediation_record + ) + assert keys == mediation_record.routing_keys + assert endpoint == mediation_record.endpoint + + +@pytest.mark.asyncio +async def test_routing_info_no_mediator( + route_manager: MultitenantRouteManager, +): + keys, endpoint = await route_manager.routing_info("http://example.com", None) + assert keys == [] + assert endpoint == "http://example.com" + + +@pytest.mark.asyncio +async def test_routing_info_with_base_mediator( + route_manager: MultitenantRouteManager, +): + base_mediation_record = MediationRecord( + mediation_id="test-base-mediation-id", + connection_id="test-base-mediator-conn-id", + routing_keys=["test-key-0", "test-key-1"], + endpoint="http://base.mediator.example.com", + ) + + with mock.patch.object( + route_manager, + "get_base_wallet_mediator", + mock.CoroutineMock(return_value=base_mediation_record), + ): + keys, endpoint = await route_manager.routing_info("http://example.com", None) + assert keys == base_mediation_record.routing_keys + assert endpoint == base_mediation_record.endpoint + + +@pytest.mark.asyncio +async def test_routing_info_with_base_mediator_and_sub_mediator( + route_manager: MultitenantRouteManager, +): + mediation_record = MediationRecord( + mediation_id="test-mediation-id", + connection_id="test-mediator-conn-id", + routing_keys=["test-key-0", "test-key-1"], + endpoint="http://mediator.example.com", + ) + base_mediation_record = MediationRecord( + mediation_id="test-base-mediation-id", + connection_id="test-base-mediator-conn-id", + routing_keys=["test-base-key-0", "test-base-key-1"], + endpoint="http://base.mediator.example.com", + ) + + with mock.patch.object( + route_manager, + "get_base_wallet_mediator", + mock.CoroutineMock(return_value=base_mediation_record), + ): + keys, endpoint = await route_manager.routing_info( + "http://example.com", mediation_record + ) + assert keys == [*base_mediation_record.routing_keys, *mediation_record.routing_keys] + assert endpoint == mediation_record.endpoint From eb226198b02eaf31dbf2e27caf2eccff4f1faf3d Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 1 Jul 2022 14:09:14 -0400 Subject: [PATCH 25/37] fix: remove unused imports Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/tests/test_route_manager.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/aries_cloudagent/multitenant/tests/test_route_manager.py b/aries_cloudagent/multitenant/tests/test_route_manager.py index 0dc996f087..f140afd8a0 100644 --- a/aries_cloudagent/multitenant/tests/test_route_manager.py +++ b/aries_cloudagent/multitenant/tests/test_route_manager.py @@ -3,24 +3,15 @@ from aries_cloudagent.protocols.routing.v1_0.manager import RoutingManager -from ...connections.models.conn_record import ConnRecord from ...core.in_memory import InMemoryProfile from ...core.profile import Profile from ...messaging.responder import BaseResponder, MockResponder from ...messaging.responder import BaseResponder, MockResponder -from ...protocols.coordinate_mediation.v1_0.manager import MediationManager -from ...protocols.coordinate_mediation.v1_0.messages.keylist_update import KeylistUpdate from ...protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) -from ...protocols.coordinate_mediation.v1_0.route_manager import ( - RouteManager, - RouteManagerError, -) from ...protocols.routing.v1_0.models.route_record import RouteRecord from ...storage.error import StorageNotFoundError -from ...wallet.did_info import DIDInfo -from ...wallet.in_memory import InMemoryWallet from ..route_manager import MultitenantRouteManager From d582a16af7997746c2ee09a1aa44bd97deb8a2e9 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 1 Jul 2022 15:07:20 -0400 Subject: [PATCH 26/37] fix: public did routing in wallet routes Signed-off-by: Daniel Bluhm --- aries_cloudagent/wallet/routes.py | 4 +++- aries_cloudagent/wallet/tests/test_routes.py | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 0acff9d4cc..6f97eebc69 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -582,7 +582,9 @@ async def promote_wallet_public_did( multitenant_mgr = profile.inject_or(BaseMultitenantManager) # Add multitenant relay mapping so implicit invitations are still routed if multitenant_mgr and wallet_id: - await multitenant_mgr.add_key(wallet_id, info.verkey, skip_if_exists=True) + await multitenant_mgr.get_route_manager( + profile, wallet_id + ).route_public_did(info.verkey) return info, attrib_def diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index e7869576fb..4d9f7cfccd 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -1,15 +1,15 @@ -from asynctest import mock as async_mock, TestCase as AsyncTestCase from aiohttp.web import HTTPForbidden +from asynctest import TestCase as AsyncTestCase, mock as async_mock +from .. import routes as test_module from ...admin.request_context import AdminRequestContext from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger from ...multitenant.base import BaseMultitenantManager from ...multitenant.manager import MultitenantManager -from ...wallet.key_type import KeyType +from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ...wallet.did_method import DIDMethod - -from .. import routes as test_module +from ...wallet.key_type import KeyType from ..base import BaseWallet from ..did_info import DIDInfo from ..did_posture import DIDPosture @@ -421,6 +421,10 @@ async def test_set_public_did_multitenant(self): self.profile.context.injector.bind_instance(BaseLedger, ledger) multitenant_mgr = async_mock.MagicMock(MultitenantManager, autospec=True) + route_manager = async_mock.MagicMock(RouteManager) + multitenant_mgr.get_route_manager = async_mock.MagicMock( + return_value=route_manager + ) self.profile.context.injector.bind_instance( BaseMultitenantManager, multitenant_mgr ) @@ -436,9 +440,10 @@ async def test_set_public_did_multitenant(self): ) await test_module.wallet_set_public_did(self.request) - multitenant_mgr.add_key.assert_called_once_with( - "test_wallet", self.test_verkey, skip_if_exists=True + multitenant_mgr.get_route_manager.assert_called_once_with( + self.profile, "test_wallet" ) + route_manager.route_public_did.assert_called_once_with(self.test_verkey) async def test_set_public_did_no_query_did(self): with self.assertRaises(test_module.web.HTTPBadRequest): From 2dcaf68855f12419618fba4236a2b44ff708d430 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 08:57:41 -0500 Subject: [PATCH 27/37] fix: allow mediation record to be none in update-keylist for connection Signed-off-by: Daniel Bluhm --- .../protocols/coordinate_mediation/v1_0/routes.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 3ebfc87f1c..651255bbcd 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -531,24 +531,21 @@ async def update_keylist_for_connection(request: web.BaseRequest): multitenant_mgr = context.inject_or(BaseMultitenantManager) wallet_id = context.settings.get_str("wallet.id") if multitenant_mgr and wallet_id: - routing_manager = multitenant_mgr.get_route_manager( + route_manager = multitenant_mgr.get_route_manager( context.profile, wallet_id ) else: - routing_manager = CoordinateMediationV1RouteManager(context.profile) + route_manager = CoordinateMediationV1RouteManager(context.profile) async with context.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, connection_id) - mediation_record = await routing_manager.mediation_record_for_connection( + mediation_record = await route_manager.mediation_record_for_connection( connection_record, mediation_id, or_default=True ) - if not mediation_record: - raise web.HTTPBadRequest( - reason="No mediation_id specified and no default mediator" - ) - - keylist_update = await routing_manager.route_connection( + # MediationRecord is permitted to be None; route manager will + # ensure the correct mediator is notified. + keylist_update = await route_manager.route_connection( connection_record, mediation_record ) From 960b1b80dcdda2bc44fc0350ecb51d9f80e92825 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:38:35 -0500 Subject: [PATCH 28/37] fix: pytest attempting to collect mock class Signed-off-by: Daniel Bluhm --- .../coordinate_mediation/v1_0/tests/test_route_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py index 3bd3d9e7ff..61b72bb975 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py @@ -19,7 +19,7 @@ ) -class TestRouteManager(RouteManager): +class MockRouteManager(RouteManager): """Concretion of RouteManager for testing.""" _route_for_key = mock.CoroutineMock() @@ -38,7 +38,7 @@ def profile(mock_responder: MockResponder): @pytest.fixture def route_manager(profile: Profile): - manager = TestRouteManager(profile) + manager = MockRouteManager(profile) manager._route_for_key = mock.CoroutineMock( return_value=mock.MagicMock(KeylistUpdate) ) From 0a352a791419287755f30a6abf5d7a99589e9cbb Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:39:18 -0500 Subject: [PATCH 29/37] test: update keylist for connection endpoint Signed-off-by: Daniel Bluhm --- .../v1_0/tests/test_routes.py | 89 ++++++++++++++----- 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index a19070a6e6..29e2224f3f 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -1,22 +1,20 @@ -import json +from asynctest import TestCase as AsyncTestCase, mock as async_mock +from aries_cloudagent.admin.request_context import AdminRequestContext -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -from .....admin.request_context import AdminRequestContext -from .....core.in_memory import InMemoryProfile -from .....config.injection_context import InjectionContext -from .....messaging.request_context import RequestContext +from aries_cloudagent.protocols.coordinate_mediation.v1_0.route_manager import ( + RouteManager, +) from .. import routes as test_module -from ..manager import MediationManager +from .....core.in_memory import InMemoryProfile +from .....multitenant.base import BaseMultitenantManager from ..models.mediation_record import MediationRecord class TestCoordinateMediationRoutes(AsyncTestCase): def setUp(self): self.profile = InMemoryProfile.test_profile() - self.context = self.profile.context - setattr(self.context, "profile", self.profile) + self.context = AdminRequestContext.test_context(profile=self.profile) self.outbound_message_router = async_mock.CoroutineMock() self.request_dict = { "context": self.context, @@ -77,7 +75,7 @@ async def test_list_mediation_requests(self): ) as mock_query, async_mock.patch.object( test_module.web, "json_response" ) as json_response, async_mock.patch.object( - self.context.profile, + self.profile, "session", async_mock.MagicMock(return_value=InMemoryProfile.test_session()), ) as session: @@ -99,7 +97,7 @@ async def test_list_mediation_requests_filters(self): ) as mock_query, async_mock.patch.object( test_module.web, "json_response" ) as json_response, async_mock.patch.object( - self.context.profile, + self.profile, "session", async_mock.MagicMock(return_value=InMemoryProfile.test_session()), ) as session: @@ -394,7 +392,7 @@ async def test_mediation_request_deny_x_storage_error(self): await test_module.mediation_request_deny(self.request) async def test_get_keylist(self): - session = await self.context.profile.session() + session = await self.profile.session() self.request.query["role"] = MediationRecord.ROLE_SERVER self.request.query["conn_id"] = "test-id" @@ -411,7 +409,7 @@ async def test_get_keylist(self): "query", async_mock.CoroutineMock(return_value=query_results), ) as mock_query, async_mock.patch.object( - self.context.profile, + self.profile, "session", async_mock.MagicMock(return_value=session), ) as mock_session, async_mock.patch.object( @@ -427,13 +425,13 @@ async def test_get_keylist(self): ) async def test_get_keylist_no_matching_records(self): - session = await self.context.profile.session() + session = await self.profile.session() with async_mock.patch.object( test_module.RouteRecord, "query", async_mock.CoroutineMock(return_value=[]), ) as mock_query, async_mock.patch.object( - self.context.profile, + self.profile, "session", async_mock.MagicMock(return_value=session), ) as mock_session, async_mock.patch.object( @@ -583,7 +581,6 @@ async def test_send_keylist_query_x_storage_error(self): async def test_get_default_mediator(self): self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.web, "json_response" ) as json_response, async_mock.patch.object( @@ -599,7 +596,6 @@ async def test_get_default_mediator(self): async def test_get_empty_default_mediator(self): self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.web, "json_response" ) as json_response, async_mock.patch.object( @@ -615,7 +611,6 @@ async def test_get_empty_default_mediator(self): async def test_get_default_mediator_storage_error(self): self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.web, "json_response" ) as json_response, async_mock.patch.object( @@ -631,7 +626,6 @@ async def test_set_default_mediator(self): "mediation_id": "fake_id", } self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.MediationManager, "get_default_mediator", @@ -654,7 +648,6 @@ async def test_set_default_mediator_storage_error(self): "mediation_id": "bad_id", } self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.MediationManager, "get_default_mediator", @@ -671,7 +664,6 @@ async def test_set_default_mediator_storage_error(self): async def test_clear_default_mediator(self): self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.MediationManager, "get_default_mediator", @@ -691,7 +683,6 @@ async def test_clear_default_mediator(self): async def test_clear_default_mediator_storage_error(self): self.request.query = {} - self.context.session = async_mock.CoroutineMock() with async_mock.patch.object( test_module.MediationManager, "get_default_mediator", @@ -706,6 +697,58 @@ async def test_clear_default_mediator_storage_error(self): with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.clear_default_mediator(self.request) + async def test_update_keylist_for_connection_multitenant(self): + self.request.query = {} + self.request.json.return_value = {"mediation_id": "test-mediation-id"} + self.request.match_info = { + "conn_id": "test-conn-id", + } + multitenant_mgr = async_mock.MagicMock(BaseMultitenantManager) + self.context.injector.bind_instance(BaseMultitenantManager, multitenant_mgr) + self.context.settings["wallet.id"] = "test-wallet-id" + mock_route_manager = async_mock.MagicMock(RouteManager) + mock_keylist_update = async_mock.MagicMock() + mock_keylist_update.serialize.return_value = {"mock": "serialized"} + mock_route_manager.route_connection = async_mock.CoroutineMock( + return_value=mock_keylist_update + ) + mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + multitenant_mgr.get_route_manager = async_mock.MagicMock( + return_value=mock_route_manager + ) + with async_mock.patch.object( + test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() + ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( + test_module.web, "json_response" + ) as json_response: + await test_module.update_keylist_for_connection(self.request) + json_response.assert_called_once_with({"mock": "serialized"}, status=200) + + async def test_update_keylist_for_connection(self): + self.request.query = {} + self.request.json.return_value = {"mediation_id": "test-mediation-id"} + self.request.match_info = { + "conn_id": "test-conn-id", + } + mock_route_manager = async_mock.MagicMock(RouteManager) + mock_keylist_update = async_mock.MagicMock() + mock_keylist_update.serialize.return_value = {"mock": "serialized"} + mock_route_manager.route_connection = async_mock.CoroutineMock( + return_value=mock_keylist_update + ) + mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + with async_mock.patch.object( + test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() + ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( + test_module.web, "json_response" + ) as json_response, async_mock.patch.object( + test_module, + "CoordinateMediationV1RouteManager", + async_mock.MagicMock(return_value=mock_route_manager), + ): + await test_module.update_keylist_for_connection(self.request) + json_response.assert_called_once_with({"mock": "serialized"}, status=200) + async def test_register(self): mock_app = async_mock.MagicMock() mock_app.add_routes = async_mock.MagicMock() From d576262369edb4d77de13834a7f262ee640e3ed4 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:43:38 -0500 Subject: [PATCH 30/37] test: error conditions on update keylist endpoint Signed-off-by: Daniel Bluhm --- .../v1_0/tests/test_routes.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index 29e2224f3f..9023643fdd 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -4,6 +4,7 @@ from aries_cloudagent.protocols.coordinate_mediation.v1_0.route_manager import ( RouteManager, ) +from aries_cloudagent.storage.error import StorageError, StorageNotFoundError from .. import routes as test_module from .....core.in_memory import InMemoryProfile @@ -749,6 +750,56 @@ async def test_update_keylist_for_connection(self): await test_module.update_keylist_for_connection(self.request) json_response.assert_called_once_with({"mock": "serialized"}, status=200) + async def test_update_keylist_for_connection_not_found(self): + self.request.query = {} + self.request.json.return_value = {"mediation_id": "test-mediation-id"} + self.request.match_info = { + "conn_id": "test-conn-id", + } + mock_route_manager = async_mock.MagicMock(RouteManager) + mock_keylist_update = async_mock.MagicMock() + mock_keylist_update.serialize.return_value = {"mock": "serialized"} + mock_route_manager.route_connection = async_mock.CoroutineMock( + return_value=mock_keylist_update + ) + mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + with async_mock.patch.object( + test_module.ConnRecord, + "retrieve_by_id", + async_mock.CoroutineMock(side_effect=StorageNotFoundError), + ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( + test_module, + "CoordinateMediationV1RouteManager", + async_mock.MagicMock(return_value=mock_route_manager), + ): + with self.assertRaises(test_module.web.HTTPNotFound): + await test_module.update_keylist_for_connection(self.request) + + async def test_update_keylist_for_connection_storage_error(self): + self.request.query = {} + self.request.json.return_value = {"mediation_id": "test-mediation-id"} + self.request.match_info = { + "conn_id": "test-conn-id", + } + mock_route_manager = async_mock.MagicMock(RouteManager) + mock_keylist_update = async_mock.MagicMock() + mock_keylist_update.serialize.return_value = {"mock": "serialized"} + mock_route_manager.route_connection = async_mock.CoroutineMock( + return_value=mock_keylist_update + ) + mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + with async_mock.patch.object( + test_module.ConnRecord, + "retrieve_by_id", + async_mock.CoroutineMock(side_effect=StorageError), + ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( + test_module, + "CoordinateMediationV1RouteManager", + async_mock.MagicMock(return_value=mock_route_manager), + ): + with self.assertRaises(test_module.web.HTTPBadRequest): + await test_module.update_keylist_for_connection(self.request) + async def test_register(self): mock_app = async_mock.MagicMock() mock_app.add_routes = async_mock.MagicMock() From add2f243f6ec282c2fa72467a66421be2152ac32 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:47:07 -0500 Subject: [PATCH 31/37] fix: relative imports Signed-off-by: Daniel Bluhm --- .../coordinate_mediation/v1_0/tests/test_routes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index 9023643fdd..940df53bad 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -1,15 +1,13 @@ from asynctest import TestCase as AsyncTestCase, mock as async_mock -from aries_cloudagent.admin.request_context import AdminRequestContext -from aries_cloudagent.protocols.coordinate_mediation.v1_0.route_manager import ( - RouteManager, -) -from aries_cloudagent.storage.error import StorageError, StorageNotFoundError +from aries_cloudagent.admin.request_context import AdminRequestContext from .. import routes as test_module from .....core.in_memory import InMemoryProfile from .....multitenant.base import BaseMultitenantManager +from .....storage.error import StorageError, StorageNotFoundError from ..models.mediation_record import MediationRecord +from ..route_manager import RouteManager class TestCoordinateMediationRoutes(AsyncTestCase): From af412449d5bc2c0933220cedbadf17004947262a Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:48:06 -0500 Subject: [PATCH 32/37] fix: more relative imports Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/route_manager.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index 0832f7ec26..302cc5c72c 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -3,20 +3,18 @@ import logging from typing import List, Optional, Tuple + from aries_cloudagent.core.profile import Profile from aries_cloudagent.messaging.responder import BaseResponder -from aries_cloudagent.protocols.coordinate_mediation.v1_0.manager import ( - MediationManager, -) -from aries_cloudagent.protocols.routing.v1_0.manager import RoutingManager -from aries_cloudagent.protocols.routing.v1_0.models.route_record import RouteRecord -from aries_cloudagent.storage.error import StorageNotFoundError - +from ..protocols.coordinate_mediation.v1_0.manager import MediationManager from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager +from ..protocols.routing.v1_0.manager import RoutingManager +from ..protocols.routing.v1_0.models.route_record import RouteRecord +from ..storage.error import StorageNotFoundError LOGGER = logging.getLogger(__name__) From f8120a6f04d23e76b2a92b302eb42c9300d7f0e6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 09:50:40 -0500 Subject: [PATCH 33/37] fix: more relative imports Signed-off-by: Daniel Bluhm --- aries_cloudagent/multitenant/route_manager.py | 5 ++--- aries_cloudagent/multitenant/tests/test_route_manager.py | 5 ++--- .../protocols/coordinate_mediation/v1_0/route_manager.py | 5 +---- .../protocols/coordinate_mediation/v1_0/tests/test_routes.py | 3 +-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index 302cc5c72c..e20094bf71 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -4,9 +4,8 @@ import logging from typing import List, Optional, Tuple -from aries_cloudagent.core.profile import Profile -from aries_cloudagent.messaging.responder import BaseResponder - +from ..core.profile import Profile +from ..messaging.responder import BaseResponder from ..protocols.coordinate_mediation.v1_0.manager import MediationManager from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, diff --git a/aries_cloudagent/multitenant/tests/test_route_manager.py b/aries_cloudagent/multitenant/tests/test_route_manager.py index f140afd8a0..856ce7d839 100644 --- a/aries_cloudagent/multitenant/tests/test_route_manager.py +++ b/aries_cloudagent/multitenant/tests/test_route_manager.py @@ -1,7 +1,5 @@ -import pytest from asynctest import mock - -from aries_cloudagent.protocols.routing.v1_0.manager import RoutingManager +import pytest from ...core.in_memory import InMemoryProfile from ...core.profile import Profile @@ -10,6 +8,7 @@ from ...protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) +from ...protocols.routing.v1_0.manager import RoutingManager from ...protocols.routing.v1_0.models.route_record import RouteRecord from ...storage.error import StorageNotFoundError from ..route_manager import MultitenantRouteManager diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index be67b3c9e6..6197c88fd8 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -8,10 +8,6 @@ import logging from typing import List, Optional, Tuple -from aries_cloudagent.protocols.coordinate_mediation.v1_0.messages.keylist_update import ( - KeylistUpdate, -) - from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile from ....messaging.responder import BaseResponder @@ -22,6 +18,7 @@ from ....wallet.key_type import KeyType from ...routing.v1_0.models.route_record import RouteRecord from .manager import MediationManager +from .messages.keylist_update import KeylistUpdate from .models.mediation_record import MediationRecord diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index 940df53bad..f392eab61d 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -1,8 +1,7 @@ from asynctest import TestCase as AsyncTestCase, mock as async_mock -from aries_cloudagent.admin.request_context import AdminRequestContext - from .. import routes as test_module +from .....admin.request_context import AdminRequestContext from .....core.in_memory import InMemoryProfile from .....multitenant.base import BaseMultitenantManager from .....storage.error import StorageError, StorageNotFoundError From ad17f68d63537d6303fa304a05ea89a7cdd2e44e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 10:08:10 -0500 Subject: [PATCH 34/37] feat: inject weak ref to profile Signed-off-by: Daniel Bluhm --- aries_cloudagent/config/wallet.py | 10 ++++--- .../v1_0/route_manager_provider.py | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py diff --git a/aries_cloudagent/config/wallet.py b/aries_cloudagent/config/wallet.py index 9ade368c52..85e080b0fa 100644 --- a/aries_cloudagent/config/wallet.py +++ b/aries_cloudagent/config/wallet.py @@ -2,18 +2,18 @@ import logging from typing import Tuple +import weakref from ..core.error import ProfileNotFoundError from ..core.profile import Profile, ProfileManager, ProfileSession from ..storage.base import BaseStorage from ..storage.error import StorageNotFoundError -from ..version import __version__, RECORD_TYPE_ACAPY_VERSION +from ..version import RECORD_TYPE_ACAPY_VERSION, __version__ from ..wallet.base import BaseWallet -from ..wallet.did_info import DIDInfo from ..wallet.crypto import seed_to_did -from ..wallet.key_type import KeyType +from ..wallet.did_info import DIDInfo from ..wallet.did_method import DIDMethod - +from ..wallet.key_type import KeyType from .base import ConfigError from .injection_context import InjectionContext @@ -137,6 +137,8 @@ async def wallet_config( await txn.commit() + context.injector.bind_instance(Profile, weakref.ref(profile)) + return (profile, public_did_info) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py new file mode 100644 index 0000000000..d053094a52 --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py @@ -0,0 +1,27 @@ +"""RouteManager provider.""" +from ....config.base import BaseInjector, BaseProvider, BaseSettings +from ....core.profile import Profile +from ....multitenant.base import BaseMultitenantManager +from ....multitenant.route_manager import MultitenantRouteManager +from .route_manager import CoordinateMediationV1RouteManager + + +class RouteManagerProvider(BaseProvider): + """Route manager provider. + + Decides whcih route manager to use based on settings. + """ + + def __init__(self, root_profile: Profile): + """Initialize route manager provider.""" + self.root_profile = root_profile + + def provide(self, settings: BaseSettings, injector: BaseInjector): + """Create the appropriate route manager instance.""" + wallet_id = settings.get("wallet.id") + multitenant_mgr = injector.inject_or(BaseMultitenantManager) + profile = injector.inject(Profile) + if multitenant_mgr and wallet_id: + return MultitenantRouteManager(self.root_profile, profile, wallet_id) + + return CoordinateMediationV1RouteManager(profile) From b3dabdc7a31e955f432d4b8f8d6f7766cdbd80d7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 10:21:17 -0500 Subject: [PATCH 35/37] feat: inject route manager Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 14 +------ aries_cloudagent/multitenant/base.py | 8 +--- .../multitenant/tests/test_base.py | 40 ++++++++----------- .../coordinate_mediation/v1_0/routes.py | 12 +----- .../v1_0/tests/test_routes.py | 8 +--- aries_cloudagent/wallet/routes.py | 38 ++++++------------ aries_cloudagent/wallet/tests/test_routes.py | 11 +---- 7 files changed, 36 insertions(+), 95 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 9a1df8e393..162fb26c39 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -18,7 +18,6 @@ from ..core.error import BaseError from ..core.profile import Profile from ..did.did_key import DIDKey -from ..multitenant.base import BaseMultitenantManager from ..protocols.connections.v1_0.messages.connection_invitation import ( ConnectionInvitation, ) @@ -26,7 +25,6 @@ MediationRecord, ) from ..protocols.coordinate_mediation.v1_0.route_manager import ( - CoordinateMediationV1RouteManager, RouteManager, ) from ..resolver.base import ResolverError @@ -61,17 +59,7 @@ def __init__(self, profile: Profile): """ self._logger = logging.getLogger(__name__) self._profile = profile - - multitenant_mgr = profile.inject_or(BaseMultitenantManager) - wallet_id = profile.settings.get_str("wallet.id") - if multitenant_mgr and wallet_id: - self._route_manager: RouteManager = multitenant_mgr.get_route_manager( - profile, wallet_id - ) - else: - self._route_manager: RouteManager = CoordinateMediationV1RouteManager( - profile - ) + self._route_manager = profile.inject(RouteManager) async def create_did_document( self, diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index 785a53f625..10b403ac6e 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -199,8 +199,8 @@ async def create_wallet( public_did_info = await wallet.get_public_did() if public_did_info: - await self.get_route_manager( - profile, wallet_record.wallet_id + await MultitenantRouteManager( + self._profile, profile, wallet_record.wallet_id ).route_public_did(public_did_info.verkey) except Exception: await wallet_record.delete_record(session) @@ -280,10 +280,6 @@ async def remove_wallet_profile(self, profile: Profile): """ - def get_route_manager(self, sub_profile: Profile, wallet_id: str): - """Return a route manager for handling multitenant routing.""" - return MultitenantRouteManager(self._profile, sub_profile, wallet_id) - async def create_auth_token( self, wallet_record: WalletRecord, wallet_key: str = None ) -> str: diff --git a/aries_cloudagent/multitenant/tests/test_base.py b/aries_cloudagent/multitenant/tests/test_base.py index 846d1d78a4..d52c8da678 100644 --- a/aries_cloudagent/multitenant/tests/test_base.py +++ b/aries_cloudagent/multitenant/tests/test_base.py @@ -1,29 +1,29 @@ +from datetime import datetime + from asynctest import TestCase as AsyncTestCase from asynctest import mock as async_mock - import jwt -from datetime import datetime - -from ...core.in_memory import InMemoryProfile +from .. import base as test_module from ...config.base import InjectionError +from ...core.in_memory import InMemoryProfile from ...messaging.responder import BaseResponder -from ...wallet.models.wallet_record import WalletRecord -from ...wallet.in_memory import InMemoryWallet -from ...wallet.did_info import DIDInfo -from ...storage.error import StorageNotFoundError -from ...storage.in_memory import InMemoryStorage -from ...protocols.routing.v1_0.manager import RoutingManager -from ...protocols.routing.v1_0.models.route_record import RouteRecord from ...protocols.coordinate_mediation.v1_0.manager import ( - MediationRecord, MediationManager, + MediationRecord, ) -from ...wallet.key_type import KeyType +from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager +from ...protocols.routing.v1_0.manager import RoutingManager +from ...protocols.routing.v1_0.models.route_record import RouteRecord +from ...storage.error import StorageNotFoundError +from ...storage.in_memory import InMemoryStorage +from ...wallet.did_info import DIDInfo from ...wallet.did_method import DIDMethod +from ...wallet.in_memory import InMemoryWallet +from ...wallet.key_type import KeyType +from ...wallet.models.wallet_record import WalletRecord from ..base import BaseMultitenantManager, MultitenantManagerError from ..error import WalletKeyMissingError -from .. import base as test_module class MockMultitenantManager(BaseMultitenantManager): @@ -212,16 +212,13 @@ async def test_create_wallet_saves_wallet_record_creates_profile(self): mock_route_manager = async_mock.MagicMock() mock_route_manager.route_public_did = async_mock.CoroutineMock() + self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( WalletRecord, "save" ) as wallet_record_save, async_mock.patch.object( self.manager, "get_wallet_profile" - ) as get_wallet_profile, async_mock.patch.object( - self.manager, - "get_route_manager", - async_mock.MagicMock(return_value=mock_route_manager), - ): + ) as get_wallet_profile: get_wallet_profile.return_value = InMemoryProfile.test_profile() wallet_record = await self.manager.create_wallet( @@ -253,16 +250,13 @@ async def test_create_wallet_adds_wallet_route(self): mock_route_manager = async_mock.MagicMock() mock_route_manager.route_public_did = async_mock.CoroutineMock() + self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( WalletRecord, "save" ) as wallet_record_save, async_mock.patch.object( self.manager, "get_wallet_profile" ) as get_wallet_profile, async_mock.patch.object( - self.manager, - "get_route_manager", - async_mock.MagicMock(return_value=mock_route_manager), - ), async_mock.patch.object( InMemoryWallet, "get_public_did" ) as get_public_did: get_wallet_profile.return_value = InMemoryProfile.test_profile() diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 651255bbcd..94abb9dcb4 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -15,7 +15,6 @@ from ....messaging.models.base import BaseModelError from ....messaging.models.openapi import OpenAPISchema from ....messaging.valid import UUIDFour -from ....multitenant.base import BaseMultitenantManager from ....storage.error import StorageError, StorageNotFoundError from ...connections.v1_0.routes import ConnectionsConnIdMatchInfoSchema from ...routing.v1_0.models.route_record import RouteRecord, RouteRecordSchema @@ -30,7 +29,7 @@ from .messages.mediate_deny import MediationDenySchema from .messages.mediate_grant import MediationGrantSchema from .models.mediation_record import MediationRecord, MediationRecordSchema -from .route_manager import CoordinateMediationV1RouteManager +from .route_manager import RouteManager CONNECTION_ID_SCHEMA = fields.UUID( @@ -528,14 +527,7 @@ async def update_keylist_for_connection(request: web.BaseRequest): mediation_id = body.get("mediation_id") connection_id = request.match_info["conn_id"] try: - multitenant_mgr = context.inject_or(BaseMultitenantManager) - wallet_id = context.settings.get_str("wallet.id") - if multitenant_mgr and wallet_id: - route_manager = multitenant_mgr.get_route_manager( - context.profile, wallet_id - ) - else: - route_manager = CoordinateMediationV1RouteManager(context.profile) + route_manager = context.inject(RouteManager) async with context.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, connection_id) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index f392eab61d..c7d72b2a76 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -3,7 +3,6 @@ from .. import routes as test_module from .....admin.request_context import AdminRequestContext from .....core.in_memory import InMemoryProfile -from .....multitenant.base import BaseMultitenantManager from .....storage.error import StorageError, StorageNotFoundError from ..models.mediation_record import MediationRecord from ..route_manager import RouteManager @@ -701,9 +700,6 @@ async def test_update_keylist_for_connection_multitenant(self): self.request.match_info = { "conn_id": "test-conn-id", } - multitenant_mgr = async_mock.MagicMock(BaseMultitenantManager) - self.context.injector.bind_instance(BaseMultitenantManager, multitenant_mgr) - self.context.settings["wallet.id"] = "test-wallet-id" mock_route_manager = async_mock.MagicMock(RouteManager) mock_keylist_update = async_mock.MagicMock() mock_keylist_update.serialize.return_value = {"mock": "serialized"} @@ -711,9 +707,7 @@ async def test_update_keylist_for_connection_multitenant(self): return_value=mock_keylist_update ) mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() - multitenant_mgr.get_route_manager = async_mock.MagicMock( - return_value=mock_route_manager - ) + self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 6f97eebc69..76c3fef5e8 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -1,21 +1,16 @@ """Wallet admin routes.""" import json +import logging from aiohttp import web -from aiohttp_apispec import ( - docs, - querystring_schema, - request_schema, - response_schema, -) -import logging +from aiohttp_apispec import docs, querystring_schema, request_schema, response_schema from marshmallow import fields, validate from ..admin.request_context import AdminRequestContext +from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..connections.models.conn_record import ConnRecord from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError, LedgerError @@ -23,27 +18,26 @@ from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import ( DID_POSTURE, - INDY_OR_KEY_DID, - INDY_DID, ENDPOINT, ENDPOINT_TYPE, + INDY_DID, + INDY_OR_KEY_DID, INDY_RAW_PUBLIC_KEY, ) -from ..multitenant.base import BaseMultitenantManager +from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ..protocols.endorse_transaction.v1_0.manager import ( TransactionManager, TransactionManagerError, ) from ..protocols.endorse_transaction.v1_0.util import ( - is_author_role, get_endorser_connection_id, + is_author_role, ) -from ..storage.error import StorageNotFoundError, StorageError - +from ..storage.error import StorageError, StorageNotFoundError from .base import BaseWallet from .did_info import DIDInfo -from .did_posture import DIDPosture from .did_method import DIDMethod +from .did_posture import DIDPosture from .error import WalletError, WalletNotFoundError from .key_type import KeyType from .util import EVENT_LISTENER_PATTERN @@ -495,10 +489,6 @@ async def promote_wallet_public_did( connection_id: str = None, ) -> DIDInfo: """Promote supplied DID to the wallet public DID.""" - - # if running in multitenant mode this will be the sub-wallet - wallet_id = context.settings.get("wallet.id") - info: DIDInfo = None endorser_did = None ledger = profile.inject_or(BaseLedger) @@ -578,13 +568,9 @@ async def promote_wallet_public_did( # async with ledger: # await ledger.update_endpoint_for_did(info.did, endpoint) - # Multitenancy setup - multitenant_mgr = profile.inject_or(BaseMultitenantManager) - # Add multitenant relay mapping so implicit invitations are still routed - if multitenant_mgr and wallet_id: - await multitenant_mgr.get_route_manager( - profile, wallet_id - ).route_public_did(info.verkey) + # Route the public DID + route_manager = context.inject(RouteManager) + await route_manager.route_public_did(info.verkey) return info, attrib_def diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index 4d9f7cfccd..8faac7be96 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -420,14 +420,8 @@ async def test_set_public_did_multitenant(self): ledger.__aenter__ = async_mock.CoroutineMock(return_value=ledger) self.profile.context.injector.bind_instance(BaseLedger, ledger) - multitenant_mgr = async_mock.MagicMock(MultitenantManager, autospec=True) route_manager = async_mock.MagicMock(RouteManager) - multitenant_mgr.get_route_manager = async_mock.MagicMock( - return_value=route_manager - ) - self.profile.context.injector.bind_instance( - BaseMultitenantManager, multitenant_mgr - ) + self.profile.context.injector.bind_instance(RouteManager, route_manager) with async_mock.patch.object( test_module.web, "json_response", async_mock.Mock() ): @@ -440,9 +434,6 @@ async def test_set_public_did_multitenant(self): ) await test_module.wallet_set_public_did(self.request) - multitenant_mgr.get_route_manager.assert_called_once_with( - self.profile, "test_wallet" - ) route_manager.route_public_did.assert_called_once_with(self.test_verkey) async def test_set_public_did_no_query_did(self): From 44ef00caf75c8e509a755933c08c18e8e3fb89b7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 14:06:47 -0500 Subject: [PATCH 36/37] fix: tests use injected route manager Signed-off-by: Daniel Bluhm --- aries_cloudagent/core/conductor.py | 23 +++++++---- .../core/tests/test_dispatcher.py | 2 + aries_cloudagent/multitenant/base.py | 8 ++-- .../multitenant/tests/test_base.py | 5 ++- .../connections/v1_0/tests/test_manager.py | 20 ++++----- .../v1_0/tests/test_routes.py | 41 +++---------------- .../didexchange/v1_0/tests/test_manager.py | 29 ++++++------- .../didexchange/v1_0/tests/test_routes.py | 8 +++- .../out_of_band/v1_0/tests/test_manager.py | 28 +++++++------ aries_cloudagent/wallet/routes.py | 2 +- aries_cloudagent/wallet/tests/test_routes.py | 41 ++++--------------- 11 files changed, 81 insertions(+), 126 deletions(-) diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 377e04b6f0..c11327e5f9 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -11,19 +11,20 @@ import hashlib import json import logging + from qrcode import QRCode from ..admin.base_server import BaseAdminServer from ..admin.server import AdminResponder, AdminServer from ..config.default_context import ContextBuilder from ..config.injection_context import InjectionContext -from ..config.provider import ClassProvider from ..config.ledger import ( get_genesis_transactions, ledger_config, load_multiple_genesis_transactions_from_config, ) from ..config.logging import LoggingConfigurator +from ..config.provider import ClassProvider from ..config.wallet import wallet_config from ..core.profile import Profile from ..indy.verifier import IndyVerifier @@ -33,8 +34,8 @@ BaseMultipleLedgerManager, MultipleLedgerManagerError, ) -from ..ledger.multiple_ledger.manager_provider import MultiIndyLedgerManagerProvider from ..ledger.multiple_ledger.ledger_requests_executor import IndyLedgerRequestsExecutor +from ..ledger.multiple_ledger.manager_provider import MultiIndyLedgerManagerProvider from ..messaging.responder import BaseResponder from ..multitenant.base import BaseMultitenantManager from ..multitenant.manager_provider import MultitenantManagerProvider @@ -45,8 +46,12 @@ from ..protocols.connections.v1_0.messages.connection_invitation import ( ConnectionInvitation, ) -from ..protocols.coordinate_mediation.v1_0.manager import MediationManager from ..protocols.coordinate_mediation.mediation_invite_store import MediationInviteStore +from ..protocols.coordinate_mediation.v1_0.manager import MediationManager +from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager +from ..protocols.coordinate_mediation.v1_0.route_manager_provider import ( + RouteManagerProvider, +) from ..protocols.out_of_band.v1_0.manager import OutOfBandManager from ..protocols.out_of_band.v1_0.messages.invitation import HSProto, InvitationMessage from ..storage.base import BaseStorage @@ -61,12 +66,11 @@ from ..utils.stats import Collector from ..utils.task_queue import CompletedTask, TaskQueue from ..vc.ld_proofs.document_loader import DocumentLoader -from ..version import __version__, RECORD_TYPE_ACAPY_VERSION +from ..version import RECORD_TYPE_ACAPY_VERSION, __version__ from ..wallet.did_info import DIDInfo -from .oob_processor import OobMessageProcessor - from .dispatcher import Dispatcher -from .util import STARTUP_EVENT_TOPIC, SHUTDOWN_EVENT_TOPIC +from .oob_processor import OobMessageProcessor +from .util import SHUTDOWN_EVENT_TOPIC, STARTUP_EVENT_TOPIC LOGGER = logging.getLogger(__name__) @@ -204,6 +208,11 @@ async def setup(self): BaseMultitenantManager, MultitenantManagerProvider(self.root_profile) ) + # Bind route manager provider + context.injector.bind_provider( + RouteManager, RouteManagerProvider(self.root_profile) + ) + # Bind oob message processor to be able to receive and process un-encrypted # messages context.injector.bind_instance( diff --git a/aries_cloudagent/core/tests/test_dispatcher.py b/aries_cloudagent/core/tests/test_dispatcher.py index b9f00564b1..5caef86516 100644 --- a/aries_cloudagent/core/tests/test_dispatcher.py +++ b/aries_cloudagent/core/tests/test_dispatcher.py @@ -18,6 +18,7 @@ V20CredProblemReport, ) from ...protocols.problem_report.v1_0.message import ProblemReport +from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ...transport.inbound.message import InboundMessage from ...transport.inbound.receipt import MessageReceipt from ...transport.outbound.message import OutboundMessage @@ -31,6 +32,7 @@ def make_profile() -> Profile: profile.context.injector.bind_instance(ProtocolRegistry, ProtocolRegistry()) profile.context.injector.bind_instance(Collector, Collector()) profile.context.injector.bind_instance(EventBus, EventBus()) + profile.context.injector.bind_instance(RouteManager, async_mock.MagicMock()) return profile diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index 10b403ac6e..4c1b8e8ba7 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -10,11 +10,11 @@ from ..config.injection_context import InjectionContext from ..core.error import BaseError from ..core.profile import Profile, ProfileSession -from ..multitenant.route_manager import MultitenantRouteManager from ..protocols.coordinate_mediation.v1_0.manager import ( MediationManager, MediationRecord, ) +from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ..protocols.routing.v1_0.manager import RouteNotFoundError, RoutingManager from ..protocols.routing.v1_0.models.route_record import RouteRecord from ..storage.base import BaseStorage @@ -199,9 +199,9 @@ async def create_wallet( public_did_info = await wallet.get_public_did() if public_did_info: - await MultitenantRouteManager( - self._profile, profile, wallet_record.wallet_id - ).route_public_did(public_did_info.verkey) + await profile.inject(RouteManager).route_public_did( + public_did_info.verkey + ) except Exception: await wallet_record.delete_record(session) raise diff --git a/aries_cloudagent/multitenant/tests/test_base.py b/aries_cloudagent/multitenant/tests/test_base.py index d52c8da678..ccc09fd21e 100644 --- a/aries_cloudagent/multitenant/tests/test_base.py +++ b/aries_cloudagent/multitenant/tests/test_base.py @@ -250,7 +250,6 @@ async def test_create_wallet_adds_wallet_route(self): mock_route_manager = async_mock.MagicMock() mock_route_manager.route_public_did = async_mock.CoroutineMock() - self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( WalletRecord, "save" @@ -259,7 +258,9 @@ async def test_create_wallet_adds_wallet_route(self): ) as get_wallet_profile, async_mock.patch.object( InMemoryWallet, "get_public_did" ) as get_public_did: - get_wallet_profile.return_value = InMemoryProfile.test_profile() + get_wallet_profile.return_value = InMemoryProfile.test_profile( + bind={RouteManager: mock_route_manager} + ) get_public_did.return_value = did_info wallet_record = await self.manager.create_wallet( 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 bbb681645e..66d284123d 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -30,10 +30,6 @@ from .....wallet.key_type import KeyType from ....coordinate_mediation.v1_0.manager import MediationManager from ....coordinate_mediation.v1_0.route_manager import RouteManager -from ....coordinate_mediation.v1_0.messages.inner.keylist_update_rule import ( - KeylistUpdateRule, -) -from ....coordinate_mediation.v1_0.messages.keylist_update import KeylistUpdate from ....coordinate_mediation.v1_0.messages.mediate_request import MediationRequest from ....coordinate_mediation.v1_0.models.mediation_record import MediationRecord from ....discovery.v2_0.manager import V20DiscoveryMgr @@ -77,6 +73,13 @@ async def setUp(self): self.oob_mock = async_mock.MagicMock( clean_finished_oob_record=async_mock.CoroutineMock(return_value=None) ) + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=([], self.test_endpoint) + ) + self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( + return_value=None + ) self.profile = InMemoryProfile.test_profile( { @@ -90,6 +93,7 @@ async def setUp(self): BaseResponder: self.responder, BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, + RouteManager: self.route_manager, }, ) self.context = self.profile.context @@ -106,14 +110,6 @@ async def setUp(self): self.test_mediator_endpoint = "http://mediator.example.com" self.manager = ConnectionManager(self.profile) - self.route_manager = async_mock.MagicMock(RouteManager) - self.route_manager.routing_info = async_mock.CoroutineMock( - return_value=([], self.test_endpoint) - ) - self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( - return_value=None - ) - self.manager._route_manager = self.route_manager assert self.manager.profile async def test_create_invitation_public_and_multi_use_fails(self): diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index c7d72b2a76..91cea3c999 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -694,7 +694,7 @@ async def test_clear_default_mediator_storage_error(self): with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.clear_default_mediator(self.request) - async def test_update_keylist_for_connection_multitenant(self): + async def test_update_keylist_for_connection(self): self.request.query = {} self.request.json.return_value = {"mediation_id": "test-mediation-id"} self.request.match_info = { @@ -716,31 +716,6 @@ async def test_update_keylist_for_connection_multitenant(self): await test_module.update_keylist_for_connection(self.request) json_response.assert_called_once_with({"mock": "serialized"}, status=200) - async def test_update_keylist_for_connection(self): - self.request.query = {} - self.request.json.return_value = {"mediation_id": "test-mediation-id"} - self.request.match_info = { - "conn_id": "test-conn-id", - } - mock_route_manager = async_mock.MagicMock(RouteManager) - mock_keylist_update = async_mock.MagicMock() - mock_keylist_update.serialize.return_value = {"mock": "serialized"} - mock_route_manager.route_connection = async_mock.CoroutineMock( - return_value=mock_keylist_update - ) - mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() - with async_mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( - test_module.web, "json_response" - ) as json_response, async_mock.patch.object( - test_module, - "CoordinateMediationV1RouteManager", - async_mock.MagicMock(return_value=mock_route_manager), - ): - await test_module.update_keylist_for_connection(self.request) - json_response.assert_called_once_with({"mock": "serialized"}, status=200) - async def test_update_keylist_for_connection_not_found(self): self.request.query = {} self.request.json.return_value = {"mediation_id": "test-mediation-id"} @@ -754,15 +729,12 @@ async def test_update_keylist_for_connection_not_found(self): return_value=mock_keylist_update ) mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock(side_effect=StorageNotFoundError), - ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( - test_module, - "CoordinateMediationV1RouteManager", - async_mock.MagicMock(return_value=mock_route_manager), - ): + ) as mock_conn_rec_retrieve_by_id: with self.assertRaises(test_module.web.HTTPNotFound): await test_module.update_keylist_for_connection(self.request) @@ -779,15 +751,12 @@ async def test_update_keylist_for_connection_storage_error(self): return_value=mock_keylist_update ) mock_route_manager.mediation_record_for_connection = async_mock.CoroutineMock() + self.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock(side_effect=StorageError), - ) as mock_conn_rec_retrieve_by_id, async_mock.patch.object( - test_module, - "CoordinateMediationV1RouteManager", - async_mock.MagicMock(return_value=mock_route_manager), - ): + ) as mock_conn_rec_retrieve_by_id: with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.update_keylist_for_connection(self.request) 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 0cbec5deef..131d87670e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -36,10 +36,6 @@ from ....coordinate_mediation.v1_0.manager import MediationManager from ....coordinate_mediation.v1_0.route_manager import RouteManager -from ....coordinate_mediation.v1_0.messages.keylist_update import ( - KeylistUpdate, - KeylistUpdateRule, -) from ....coordinate_mediation.v1_0.models.mediation_record import MediationRecord from ....discovery.v2_0.manager import V20DiscoveryMgr from ....didcomm_prefix import DIDCommPrefix @@ -87,6 +83,17 @@ async def setUp(self): clean_finished_oob_record=async_mock.CoroutineMock(return_value=None) ) + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=([], self.test_endpoint) + ) + self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( + return_value=None + ) + self.route_manager.mediation_record_for_connection = async_mock.CoroutineMock( + return_value=None + ) + self.profile = InMemoryProfile.test_profile( { "default_endpoint": "http://aries.ca/endpoint", @@ -101,6 +108,7 @@ async def setUp(self): BaseResponder: self.responder, BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, + RouteManager: self.route_manager, }, ) self.context = self.profile.context @@ -140,19 +148,6 @@ async def setUp(self): self.test_mediator_conn_id = "mediator-conn-id" self.test_mediator_endpoint = "http://mediator.example.com" - self.route_manager = async_mock.MagicMock(RouteManager) - self.route_manager.routing_info = async_mock.CoroutineMock( - return_value=([], self.test_endpoint) - ) - self.route_manager.mediation_record_if_id = async_mock.CoroutineMock( - return_value=None - ) - self.route_manager.mediation_record_for_connection = async_mock.CoroutineMock( - return_value=None - ) - self.manager._route_manager = self.route_manager - self.oob_manager._route_manager = self.route_manager - async def test_verify_diddoc(self): async with self.profile.session() as session: did_doc = self.make_did_doc( diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py index 2f7bef8d74..44584c5d1e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py @@ -1,16 +1,17 @@ from asynctest import TestCase as AsyncTestCase from asynctest import mock as async_mock +from .. import routes as test_module from .....admin.request_context import AdminRequestContext from .....storage.error import StorageNotFoundError - -from .. import routes as test_module +from ....coordinate_mediation.v1_0.route_manager import RouteManager class TestDIDExchangeConnRoutes(AsyncTestCase): async def setUp(self): self.session_inject = {} self.context = AdminRequestContext.test_context(self.session_inject) + self.profile = self.context.profile self.request_dict = { "context": self.context, "outbound_message_router": async_mock.CoroutineMock(), @@ -21,6 +22,9 @@ async def setUp(self): query={}, __getitem__=lambda _, k: self.request_dict[k], ) + self.profile.context.injector.bind_instance( + RouteManager, async_mock.MagicMock() + ) async def test_didx_accept_invitation(self): self.request.match_info = {"conn_id": "dummy"} diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py index df6a3a9ca4..903f50a410 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py @@ -313,6 +313,17 @@ def setUp(self): self.responder = MockResponder() self.responder.send = async_mock.CoroutineMock() + self.test_mediator_routing_keys = [ + "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRR" + ] + self.test_mediator_conn_id = "mediator-conn-id" + self.test_mediator_endpoint = "http://mediator.example.com" + + self.route_manager = async_mock.MagicMock(RouteManager) + self.route_manager.routing_info = async_mock.CoroutineMock( + return_value=(self.test_mediator_routing_keys, self.test_mediator_endpoint) + ) + self.profile = InMemoryProfile.test_profile( { "default_endpoint": TestConfig.test_endpoint, @@ -320,7 +331,10 @@ def setUp(self): "additional_endpoints": ["http://aries.ca/another-endpoint"], "debug.auto_accept_invites": True, "debug.auto_accept_requests": True, - } + }, + bind={ + RouteManager: self.route_manager, + }, ) self.profile.context.injector.bind_instance(BaseResponder, self.responder) @@ -354,18 +368,6 @@ def setUp(self): save=async_mock.CoroutineMock(), ) - self.test_mediator_routing_keys = [ - "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRR" - ] - self.test_mediator_conn_id = "mediator-conn-id" - self.test_mediator_endpoint = "http://mediator.example.com" - - self.route_manager = async_mock.MagicMock(RouteManager) - self.route_manager.routing_info = async_mock.CoroutineMock( - return_value=(self.test_mediator_routing_keys, self.test_mediator_endpoint) - ) - self.manager._route_manager = self.route_manager - async def test_create_invitation_handshake_succeeds(self): self.profile.context.update_settings({"public_invites": True}) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 76c3fef5e8..ff8da41b7a 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -569,7 +569,7 @@ async def promote_wallet_public_did( # await ledger.update_endpoint_for_did(info.did, endpoint) # Route the public DID - route_manager = context.inject(RouteManager) + route_manager = profile.inject(RouteManager) await route_manager.route_public_did(info.verkey) return info, attrib_def diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index 8faac7be96..6d67d0f17f 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -5,8 +5,6 @@ from ...admin.request_context import AdminRequestContext from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger -from ...multitenant.base import BaseMultitenantManager -from ...multitenant.manager import MultitenantManager from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ...wallet.did_method import DIDMethod from ...wallet.key_type import KeyType @@ -380,6 +378,9 @@ async def test_set_public_did(self): ledger.update_endpoint_for_did = async_mock.CoroutineMock() ledger.__aenter__ = async_mock.CoroutineMock(return_value=ledger) self.profile.context.injector.bind_instance(BaseLedger, ledger) + mock_route_manager = async_mock.MagicMock() + mock_route_manager.route_public_did = async_mock.CoroutineMock() + self.profile.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.web, "json_response", async_mock.Mock() @@ -406,36 +407,6 @@ async def test_set_public_did(self): ) assert result is json_response.return_value - async def test_set_public_did_multitenant(self): - self.context.update_settings( - {"multitenant.enabled": True, "wallet.id": "test_wallet"} - ) - - self.request.query = {"did": self.test_did} - - Ledger = async_mock.MagicMock() - ledger = Ledger() - ledger.get_key_for_did = async_mock.CoroutineMock() - ledger.update_endpoint_for_did = async_mock.CoroutineMock() - ledger.__aenter__ = async_mock.CoroutineMock(return_value=ledger) - self.profile.context.injector.bind_instance(BaseLedger, ledger) - - route_manager = async_mock.MagicMock(RouteManager) - self.profile.context.injector.bind_instance(RouteManager, route_manager) - with async_mock.patch.object( - test_module.web, "json_response", async_mock.Mock() - ): - self.wallet.set_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - DIDPosture.PUBLIC.metadata, - DIDMethod.SOV, - KeyType.ED25519, - ) - await test_module.wallet_set_public_did(self.request) - - route_manager.route_public_did.assert_called_once_with(self.test_verkey) - async def test_set_public_did_no_query_did(self): with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.wallet_set_public_did(self.request) @@ -527,6 +498,9 @@ async def test_set_public_did_update_endpoint(self): ledger.get_key_for_did = async_mock.CoroutineMock() ledger.__aenter__ = async_mock.CoroutineMock(return_value=ledger) self.profile.context.injector.bind_instance(BaseLedger, ledger) + mock_route_manager = async_mock.MagicMock() + mock_route_manager.route_public_did = async_mock.CoroutineMock() + self.profile.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.web, "json_response", async_mock.Mock() @@ -565,6 +539,9 @@ async def test_set_public_did_update_endpoint_use_default_update_in_wallet(self) ledger.get_key_for_did = async_mock.CoroutineMock() ledger.__aenter__ = async_mock.CoroutineMock(return_value=ledger) self.profile.context.injector.bind_instance(BaseLedger, ledger) + mock_route_manager = async_mock.MagicMock() + mock_route_manager.route_public_did = async_mock.CoroutineMock() + self.profile.context.injector.bind_instance(RouteManager, mock_route_manager) with async_mock.patch.object( test_module.web, "json_response", async_mock.Mock() From 822fdfb71d643ce6456638111d8d96410e4153f5 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 6 Jul 2022 14:32:07 -0500 Subject: [PATCH 37/37] refactor: don't inject profile into context Signed-off-by: Daniel Bluhm --- aries_cloudagent/config/wallet.py | 3 - aries_cloudagent/multitenant/base.py | 2 +- aries_cloudagent/multitenant/route_manager.py | 23 +-- .../multitenant/tests/test_base.py | 4 +- .../multitenant/tests/test_route_manager.py | 43 +++-- .../protocols/connections/v1_0/manager.py | 23 ++- .../connections/v1_0/tests/test_manager.py | 4 +- .../v1_0/route_manager.py | 86 ++++++---- .../v1_0/route_manager_provider.py | 5 +- .../v1_0/tests/test_route_manager.py | 150 +++++++++++------- .../protocols/didexchange/v1_0/manager.py | 9 +- .../protocols/out_of_band/v1_0/manager.py | 11 +- aries_cloudagent/wallet/routes.py | 2 +- 13 files changed, 231 insertions(+), 134 deletions(-) diff --git a/aries_cloudagent/config/wallet.py b/aries_cloudagent/config/wallet.py index 85e080b0fa..61e34b2a73 100644 --- a/aries_cloudagent/config/wallet.py +++ b/aries_cloudagent/config/wallet.py @@ -2,7 +2,6 @@ import logging from typing import Tuple -import weakref from ..core.error import ProfileNotFoundError from ..core.profile import Profile, ProfileManager, ProfileSession @@ -137,8 +136,6 @@ async def wallet_config( await txn.commit() - context.injector.bind_instance(Profile, weakref.ref(profile)) - return (profile, public_did_info) diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index 4c1b8e8ba7..cecd2314dd 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -200,7 +200,7 @@ async def create_wallet( if public_did_info: await profile.inject(RouteManager).route_public_did( - public_did_info.verkey + profile, public_did_info.verkey ) except Exception: await wallet_record.delete_record(session) diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index e20094bf71..a7d6cf6878 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -22,16 +22,12 @@ class MultitenantRouteManager(RouteManager): """Multitenancy route manager.""" - def __init__(self, root_profile: Profile, sub_profile: Profile, wallet_id: str): + def __init__( + self, + root_profile: Profile, + ): """Initialize multitenant route manager.""" self.root_profile = root_profile - self.wallet_id = wallet_id - super().__init__(sub_profile) - - @property - def sub_profile(self) -> Profile: - """Return reference to sub wallet profile.""" - return self.profile async def get_base_wallet_mediator(self) -> Optional[MediationRecord]: """Get base wallet's default mediator.""" @@ -39,14 +35,16 @@ async def get_base_wallet_mediator(self) -> Optional[MediationRecord]: async def _route_for_key( self, + profile: Profile, recipient_key: str, mediation_record: Optional[MediationRecord] = None, *, skip_if_exists: bool = False, replace_key: Optional[str] = None, ): + wallet_id = profile.settings["wallet.id"] LOGGER.info( - f"Add route record for recipient {recipient_key} to wallet {self.wallet_id}" + f"Add route record for recipient {recipient_key} to wallet {wallet_id}" ) routing_mgr = RoutingManager(self.root_profile) mediation_mgr = MediationManager(self.root_profile) @@ -66,7 +64,7 @@ async def _route_for_key( pass await routing_mgr.create_route_record( - recipient_key=recipient_key, internal_wallet_id=self.wallet_id + recipient_key=recipient_key, internal_wallet_id=wallet_id ) # External mediation @@ -86,7 +84,10 @@ async def _route_for_key( return keylist_updates async def routing_info( - self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None + self, + profile: Profile, + my_endpoint: str, + mediation_record: Optional[MediationRecord] = None, ) -> Tuple[List[str], str]: """Return routing info.""" routing_keys = [] diff --git a/aries_cloudagent/multitenant/tests/test_base.py b/aries_cloudagent/multitenant/tests/test_base.py index ccc09fd21e..6f7c7b7e78 100644 --- a/aries_cloudagent/multitenant/tests/test_base.py +++ b/aries_cloudagent/multitenant/tests/test_base.py @@ -268,7 +268,9 @@ async def test_create_wallet_adds_wallet_route(self): WalletRecord.MODE_MANAGED, ) - mock_route_manager.route_public_did.assert_called_once_with(did_info.verkey) + mock_route_manager.route_public_did.assert_called_once_with( + get_wallet_profile.return_value, did_info.verkey + ) wallet_record_save.assert_called_once() get_wallet_profile.assert_called_once_with( diff --git a/aries_cloudagent/multitenant/tests/test_route_manager.py b/aries_cloudagent/multitenant/tests/test_route_manager.py index 856ce7d839..87bfef7129 100644 --- a/aries_cloudagent/multitenant/tests/test_route_manager.py +++ b/aries_cloudagent/multitenant/tests/test_route_manager.py @@ -47,13 +47,7 @@ def sub_profile(mock_responder: MockResponder, wallet_id: str): @pytest.fixture def route_manager(root_profile: Profile, sub_profile: Profile, wallet_id: str): - yield MultitenantRouteManager(root_profile, sub_profile, wallet_id) - - -def test_sub_profile_access( - route_manager: MultitenantRouteManager, sub_profile: Profile -): - assert route_manager.sub_profile == sub_profile + yield MultitenantRouteManager(root_profile) @pytest.mark.asyncio @@ -61,6 +55,7 @@ async def test_route_for_key_sub_mediator_no_base_mediator( route_manager: MultitenantRouteManager, mock_responder: MockResponder, wallet_id: str, + sub_profile: Profile, ): mediation_record = MediationRecord( mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" @@ -72,6 +67,7 @@ async def test_route_for_key_sub_mediator_no_base_mediator( RoutingManager, "create_route_record", mock.CoroutineMock() ) as mock_create_route_record: keylist_update = await route_manager._route_for_key( + sub_profile, "test-recipient-key", mediation_record, skip_if_exists=False, @@ -94,6 +90,7 @@ async def test_route_for_key_sub_mediator_no_base_mediator( @pytest.mark.asyncio async def test_route_for_key_sub_mediator_and_base_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, mock_responder: MockResponder, wallet_id: str, @@ -114,6 +111,7 @@ async def test_route_for_key_sub_mediator_and_base_mediator( RoutingManager, "create_route_record", mock.CoroutineMock() ) as mock_create_route_record: keylist_update = await route_manager._route_for_key( + sub_profile, "test-recipient-key", mediation_record, skip_if_exists=False, @@ -136,6 +134,7 @@ async def test_route_for_key_sub_mediator_and_base_mediator( @pytest.mark.asyncio async def test_route_for_key_base_mediator_no_sub_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, mock_responder: MockResponder, wallet_id: str, @@ -153,7 +152,11 @@ async def test_route_for_key_base_mediator_no_sub_mediator( RoutingManager, "create_route_record", mock.CoroutineMock() ) as mock_create_route_record: keylist_update = await route_manager._route_for_key( - "test-recipient-key", None, skip_if_exists=False, replace_key=None + sub_profile, + "test-recipient-key", + None, + skip_if_exists=False, + replace_key=None, ) mock_create_route_record.assert_called_once_with( @@ -172,6 +175,7 @@ async def test_route_for_key_base_mediator_no_sub_mediator( @pytest.mark.asyncio async def test_route_for_key_skip_if_exists_and_exists( + sub_profile: Profile, route_manager: MultitenantRouteManager, mock_responder: MockResponder, ): @@ -182,6 +186,7 @@ async def test_route_for_key_skip_if_exists_and_exists( RouteRecord, "retrieve_by_recipient_key", mock.CoroutineMock() ): keylist_update = await route_manager._route_for_key( + sub_profile, "test-recipient-key", mediation_record, skip_if_exists=True, @@ -193,6 +198,7 @@ async def test_route_for_key_skip_if_exists_and_exists( @pytest.mark.asyncio async def test_route_for_key_skip_if_exists_and_absent( + sub_profile: Profile, route_manager: MultitenantRouteManager, mock_responder: MockResponder, ): @@ -205,6 +211,7 @@ async def test_route_for_key_skip_if_exists_and_absent( mock.CoroutineMock(side_effect=StorageNotFoundError), ): keylist_update = await route_manager._route_for_key( + sub_profile, "test-recipient-key", mediation_record, skip_if_exists=True, @@ -223,6 +230,7 @@ async def test_route_for_key_skip_if_exists_and_absent( @pytest.mark.asyncio async def test_route_for_key_replace_key( + sub_profile: Profile, route_manager: MultitenantRouteManager, mock_responder: MockResponder, ): @@ -230,6 +238,7 @@ async def test_route_for_key_replace_key( mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" ) keylist_update = await route_manager._route_for_key( + sub_profile, "test-recipient-key", mediation_record, skip_if_exists=False, @@ -249,10 +258,12 @@ async def test_route_for_key_replace_key( @pytest.mark.asyncio async def test_route_for_key_no_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, ): assert ( await route_manager._route_for_key( + sub_profile, "test-recipient-key", None, skip_if_exists=True, @@ -264,6 +275,7 @@ async def test_route_for_key_no_mediator( @pytest.mark.asyncio async def test_routing_info_with_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, ): mediation_record = MediationRecord( @@ -273,7 +285,7 @@ async def test_routing_info_with_mediator( endpoint="http://mediator.example.com", ) keys, endpoint = await route_manager.routing_info( - "http://example.com", mediation_record + sub_profile, "http://example.com", mediation_record ) assert keys == mediation_record.routing_keys assert endpoint == mediation_record.endpoint @@ -281,15 +293,19 @@ async def test_routing_info_with_mediator( @pytest.mark.asyncio async def test_routing_info_no_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, ): - keys, endpoint = await route_manager.routing_info("http://example.com", None) + keys, endpoint = await route_manager.routing_info( + sub_profile, "http://example.com", None + ) assert keys == [] assert endpoint == "http://example.com" @pytest.mark.asyncio async def test_routing_info_with_base_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, ): base_mediation_record = MediationRecord( @@ -304,13 +320,16 @@ async def test_routing_info_with_base_mediator( "get_base_wallet_mediator", mock.CoroutineMock(return_value=base_mediation_record), ): - keys, endpoint = await route_manager.routing_info("http://example.com", None) + keys, endpoint = await route_manager.routing_info( + sub_profile, "http://example.com", None + ) assert keys == base_mediation_record.routing_keys assert endpoint == base_mediation_record.endpoint @pytest.mark.asyncio async def test_routing_info_with_base_mediator_and_sub_mediator( + sub_profile: Profile, route_manager: MultitenantRouteManager, ): mediation_record = MediationRecord( @@ -332,7 +351,7 @@ async def test_routing_info_with_base_mediator_and_sub_mediator( mock.CoroutineMock(return_value=base_mediation_record), ): keys, endpoint = await route_manager.routing_info( - "http://example.com", mediation_record + sub_profile, "http://example.com", mediation_record ) assert keys == [*base_mediation_record.routing_keys, *mediation_record.routing_keys] assert endpoint == mediation_record.endpoint diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index 4cdf1b8d77..d937b7ecb2 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -122,6 +122,7 @@ async def create_invitation( # Mediation Record can still be None after this operation if no # mediation id passed and no default mediation_record = await self._route_manager.mediation_record_if_id( + self.profile, mediation_id, or_default=True, ) @@ -159,7 +160,7 @@ async def create_invitation( # Add mapping for multitenant relaying. # Mediation of public keys is not supported yet - await self._route_manager.route_public_did(public_did.verkey) + await self._route_manager.route_public_did(self.profile, public_did.verkey) return None, invitation @@ -206,8 +207,11 @@ async def create_invitation( async with self.profile.session() as session: await connection.save(session, reason="Created new invitation") - await self._route_manager.route_invitation(connection, mediation_record) + await self._route_manager.route_invitation( + self.profile, connection, mediation_record + ) routing_keys, my_endpoint = await self._route_manager.routing_info( + self.profile, my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), mediation_record, ) @@ -297,7 +301,7 @@ async def receive_invitation( await connection.attach_invitation(session, invitation) await self._route_manager.save_mediator_for_connection( - connection, mediation_id=mediation_id + self.profile, connection, mediation_id=mediation_id ) if connection.accept == ConnRecord.ACCEPT_AUTO: @@ -335,6 +339,7 @@ async def create_request( """ mediation_record = await self._route_manager.mediation_record_for_connection( + self.profile, connection, mediation_id, or_default=True, @@ -360,7 +365,7 @@ async def create_request( # Idempotent; if routing has already been set up, no action taken await self._route_manager.route_connection_as_invitee( - connection, mediation_record + self.profile, connection, mediation_record ) # Create connection request message @@ -579,7 +584,7 @@ async def create_response( ) mediation_record = await self._route_manager.mediation_record_for_connection( - connection, mediation_id + self.profile, connection, mediation_id ) # Multitenancy setup @@ -613,7 +618,7 @@ async def create_response( # Idempotent; if routing has already been set up, no action taken await self._route_manager.route_connection_as_inviter( - connection, mediation_record + self.profile, connection, mediation_record ) # Create connection response message @@ -863,7 +868,7 @@ async def create_static_connection( # Routing mediation_record = await self._route_manager.mediation_record_if_id( - mediation_id, or_default=True + self.profile, mediation_id, or_default=True ) multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) @@ -873,7 +878,9 @@ async def create_static_connection( if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator() - await self._route_manager.route_static(connection, mediation_record) + await self._route_manager.route_static( + self.profile, connection, mediation_record + ) # Synthesize their DID doc did_doc = await self.create_did_document( 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 66d284123d..d7c3836236 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -177,7 +177,7 @@ async def test_create_invitation_public(self): assert connect_record is None assert connect_invite.did.endswith(self.test_did) self.route_manager.route_public_did.assert_called_once_with( - self.test_verkey + self.profile, self.test_verkey ) async def test_create_invitation_public_no_public_invites(self): @@ -356,7 +356,7 @@ async def test_create_invitation_mediation_using_default(self): assert invite.routing_keys == self.test_mediator_routing_keys assert invite.endpoint == self.test_mediator_endpoint self.route_manager.routing_info.assert_awaited_once_with( - self.test_endpoint, mediation_record + self.profile, self.test_endpoint, mediation_record ) async def test_receive_invitation(self): diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 6197c88fd8..89fbf92081 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -32,21 +32,19 @@ class RouteManagerError(Exception): class RouteManager(ABC): """Base Route Manager.""" - def __init__(self, profile: Profile): - """Initialize route manager.""" - self.profile = profile - - async def get_or_create_my_did(self, conn_record: ConnRecord) -> DIDInfo: + async def get_or_create_my_did( + self, profile: Profile, conn_record: ConnRecord + ) -> DIDInfo: """Create or retrieve DID info for a conneciton.""" if not conn_record.my_did: - async with self.profile.session() as session: + async with profile.session() as session: wallet = session.inject(BaseWallet) # Create new DID for connection my_info = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519) conn_record.my_did = my_info.did await conn_record.save(session, reason="Connection my did created") else: - async with self.profile.session() as session: + async with profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.get_local_did(conn_record.my_did) @@ -62,12 +60,13 @@ def _validate_mediation_state(self, mediation_record: MediationRecord): async def mediation_record_for_connection( self, + profile: Profile, conn_record: ConnRecord, mediation_id: Optional[str] = None, or_default: bool = False, ): """Return relevant mediator for connection.""" - async with self.profile.session() as session: + async with profile.session() as session: mediation_metadata = await conn_record.metadata_get( session, MediationManager.METADATA_KEY, {} ) @@ -75,13 +74,20 @@ async def mediation_record_for_connection( mediation_metadata.get(MediationManager.METADATA_ID) or mediation_id ) - mediation_record = await self.mediation_record_if_id(mediation_id, or_default) + mediation_record = await self.mediation_record_if_id( + profile, mediation_id, or_default + ) if mediation_record: - await self.save_mediator_for_connection(conn_record, mediation_record) + await self.save_mediator_for_connection( + profile, conn_record, mediation_record + ) return mediation_record async def mediation_record_if_id( - self, mediation_id: Optional[str] = None, or_default: bool = False + self, + profile: Profile, + mediation_id: Optional[str] = None, + or_default: bool = False, ): """Validate mediation and return record. @@ -91,14 +97,12 @@ async def mediation_record_if_id( """ mediation_record = None if mediation_id: - async with self.profile.session() as session: + async with profile.session() as session: mediation_record = await MediationRecord.retrieve_by_id( session, mediation_id ) elif or_default: - mediation_record = await MediationManager( - self.profile - ).get_default_mediator() + mediation_record = await MediationManager(profile).get_default_mediator() if mediation_record: self._validate_mediation_state(mediation_record) @@ -107,6 +111,7 @@ async def mediation_record_if_id( @abstractmethod async def _route_for_key( self, + profile: Profile, recipient_key: str, mediation_record: Optional[MediationRecord] = None, *, @@ -117,25 +122,28 @@ async def _route_for_key( async def route_connection_as_invitee( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: """Set up routing for a new connection when we are the invitee.""" LOGGER.debug("Routing connection as invitee") - my_info = await self.get_or_create_my_did(conn_record) + my_info = await self.get_or_create_my_did(profile, conn_record) return await self._route_for_key( - my_info.verkey, mediation_record, skip_if_exists=True + profile, my_info.verkey, mediation_record, skip_if_exists=True ) async def route_connection_as_inviter( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: """Set up routing for a new connection when we are the inviter.""" LOGGER.debug("Routing connection as inviter") - my_info = await self.get_or_create_my_did(conn_record) + my_info = await self.get_or_create_my_did(profile, conn_record) return await self._route_for_key( + profile, my_info.verkey, mediation_record, replace_key=conn_record.invitation_key, @@ -144,6 +152,7 @@ async def route_connection_as_inviter( async def route_connection( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: @@ -154,53 +163,63 @@ async def route_connection( if conn_record.rfc23_state == ConnRecord.State.INVITATION.rfc23strict( ConnRecord.Role.RESPONDER ): - return await self.route_connection_as_invitee(conn_record, mediation_record) + return await self.route_connection_as_invitee( + profile, conn_record, mediation_record + ) if conn_record.rfc23_state == ConnRecord.State.REQUEST.rfc23strict( ConnRecord.Role.REQUESTER ): - return await self.route_connection_as_inviter(conn_record, mediation_record) + return await self.route_connection_as_inviter( + profile, conn_record, mediation_record + ) return None async def route_invitation( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: """Set up routing for receiving a response to an invitation.""" - await self.save_mediator_for_connection(conn_record, mediation_record) + await self.save_mediator_for_connection(profile, conn_record, mediation_record) if conn_record.invitation_key: return await self._route_for_key( - conn_record.invitation_key, mediation_record, skip_if_exists=True + profile, + conn_record.invitation_key, + mediation_record, + skip_if_exists=True, ) raise ValueError("Expected connection to have invitation_key") - async def route_public_did(self, verkey: str): + async def route_public_did(self, profile: Profile, verkey: str): """Establish routing for a public DID.""" - return await self._route_for_key(verkey, skip_if_exists=True) + return await self._route_for_key(profile, verkey, skip_if_exists=True) async def route_static( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, ) -> Optional[KeylistUpdate]: """Establish routing for a static connection.""" - my_info = await self.get_or_create_my_did(conn_record) + my_info = await self.get_or_create_my_did(profile, conn_record) return await self._route_for_key( - my_info.verkey, mediation_record, skip_if_exists=True + profile, my_info.verkey, mediation_record, skip_if_exists=True ) async def save_mediator_for_connection( self, + profile: Profile, conn_record: ConnRecord, mediation_record: Optional[MediationRecord] = None, mediation_id: Optional[str] = None, ): """Save mediator info to connection metadata.""" - async with self.profile.session() as session: + async with profile.session() as session: if mediation_id: mediation_record = await MediationRecord.retrieve_by_id( session, mediation_id @@ -216,6 +235,7 @@ async def save_mediator_for_connection( @abstractmethod async def routing_info( self, + profile: Profile, my_endpoint: str, mediation_record: Optional[MediationRecord] = None, ) -> Tuple[List[str], str]: @@ -227,6 +247,7 @@ class CoordinateMediationV1RouteManager(RouteManager): async def _route_for_key( self, + profile: Profile, recipient_key: str, mediation_record: Optional[MediationRecord] = None, *, @@ -238,7 +259,7 @@ async def _route_for_key( if skip_if_exists: try: - async with self.profile.session() as session: + async with profile.session() as session: await RouteRecord.retrieve_by_recipient_key(session, recipient_key) return None @@ -246,19 +267,22 @@ async def _route_for_key( pass # Keylist update is idempotent, skip_if_exists ignored - mediation_mgr = MediationManager(self.profile) + mediation_mgr = MediationManager(profile) keylist_update = await mediation_mgr.add_key(recipient_key) if replace_key: keylist_update = await mediation_mgr.remove_key(replace_key, keylist_update) - responder = self.profile.inject(BaseResponder) + responder = profile.inject(BaseResponder) await responder.send( keylist_update, connection_id=mediation_record.connection_id ) return keylist_update async def routing_info( - self, my_endpoint: str, mediation_record: Optional[MediationRecord] = None + self, + profile: Profile, + my_endpoint: str, + mediation_record: Optional[MediationRecord] = None, ) -> Tuple[List[str], str]: """Return routing info for mediator.""" if mediation_record: diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py index d053094a52..693766c922 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager_provider.py @@ -20,8 +20,7 @@ def provide(self, settings: BaseSettings, injector: BaseInjector): """Create the appropriate route manager instance.""" wallet_id = settings.get("wallet.id") multitenant_mgr = injector.inject_or(BaseMultitenantManager) - profile = injector.inject(Profile) if multitenant_mgr and wallet_id: - return MultitenantRouteManager(self.root_profile, profile, wallet_id) + return MultitenantRouteManager(self.root_profile) - return CoordinateMediationV1RouteManager(profile) + return CoordinateMediationV1RouteManager() diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py index 61b72bb975..dcb315337b 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py @@ -37,8 +37,8 @@ def profile(mock_responder: MockResponder): @pytest.fixture -def route_manager(profile: Profile): - manager = MockRouteManager(profile) +def route_manager(): + manager = MockRouteManager() manager._route_for_key = mock.CoroutineMock( return_value=mock.MagicMock(KeylistUpdate) ) @@ -47,8 +47,8 @@ def route_manager(profile: Profile): @pytest.fixture -def mediation_route_manager(profile: Profile): - yield CoordinateMediationV1RouteManager(profile) +def mediation_route_manager(): + yield CoordinateMediationV1RouteManager() @pytest.fixture @@ -61,7 +61,7 @@ def conn_record(): @pytest.mark.asyncio async def test_get_or_create_my_did_no_did( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): conn_record.my_did = None mock_did_info = mock.MagicMock() @@ -72,7 +72,7 @@ async def test_get_or_create_my_did_no_did( ) as mock_create_local_did, mock.patch.object( conn_record, "save", mock.CoroutineMock() ) as mock_save: - info = await route_manager.get_or_create_my_did(conn_record) + info = await route_manager.get_or_create_my_did(profile, conn_record) assert mock_did_info == info mock_create_local_did.assert_called_once() mock_save.assert_called_once() @@ -80,21 +80,21 @@ async def test_get_or_create_my_did_no_did( @pytest.mark.asyncio async def test_get_or_create_my_did_existing_did( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): conn_record.my_did = "test-did" mock_did_info = mock.MagicMock(DIDInfo) with mock.patch.object( InMemoryWallet, "get_local_did", mock.CoroutineMock(return_value=mock_did_info) ) as mock_get_local_did: - info = await route_manager.get_or_create_my_did(conn_record) + info = await route_manager.get_or_create_my_did(profile, conn_record) assert mock_did_info == info mock_get_local_did.assert_called_once() @pytest.mark.asyncio async def test_mediation_record_for_connection_mediation_id( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") with mock.patch.object( @@ -106,18 +106,18 @@ async def test_mediation_record_for_connection_mediation_id( ): assert ( await route_manager.mediation_record_for_connection( - conn_record, mediation_record.mediation_id + profile, conn_record, mediation_record.mediation_id ) == mediation_record ) mock_mediation_record_if_id.assert_called_once_with( - mediation_record.mediation_id, False + profile, mediation_record.mediation_id, False ) @pytest.mark.asyncio async def test_mediation_record_for_connection_mediation_metadata( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") conn_record.metadata_get.return_value = { @@ -132,18 +132,18 @@ async def test_mediation_record_for_connection_mediation_metadata( ): assert ( await route_manager.mediation_record_for_connection( - conn_record, "another-mediation-id" + profile, conn_record, "another-mediation-id" ) == mediation_record ) mock_mediation_record_if_id.assert_called_once_with( - mediation_record.mediation_id, False + profile, mediation_record.mediation_id, False ) @pytest.mark.asyncio async def test_mediation_record_for_connection_default( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") with mock.patch.object( @@ -155,15 +155,17 @@ async def test_mediation_record_for_connection_default( ): assert ( await route_manager.mediation_record_for_connection( - conn_record, None, or_default=True + profile, conn_record, None, or_default=True ) == mediation_record ) - mock_mediation_record_if_id.assert_called_once_with(None, True) + mock_mediation_record_if_id.assert_called_once_with(profile, None, True) @pytest.mark.asyncio -async def test_mediation_record_if_id_with_id(route_manager: RouteManager): +async def test_mediation_record_if_id_with_id( + profile: Profile, route_manager: RouteManager +): mediation_record = MediationRecord( mediation_id="test-mediation-id", state=MediationRecord.STATE_GRANTED ) @@ -173,14 +175,16 @@ async def test_mediation_record_if_id_with_id(route_manager: RouteManager): mock.CoroutineMock(return_value=mediation_record), ) as mock_retrieve_by_id: actual = await route_manager.mediation_record_if_id( - mediation_id=mediation_record.mediation_id + profile, mediation_id=mediation_record.mediation_id ) assert mediation_record == actual mock_retrieve_by_id.assert_called_once() @pytest.mark.asyncio -async def test_mediation_record_if_id_with_id_bad_state(route_manager: RouteManager): +async def test_mediation_record_if_id_with_id_bad_state( + profile: Profile, route_manager: RouteManager +): mediation_record = MediationRecord( mediation_id="test-mediation-id", state=MediationRecord.STATE_DENIED ) @@ -191,12 +195,14 @@ async def test_mediation_record_if_id_with_id_bad_state(route_manager: RouteMana ): with pytest.raises(RouteManagerError): await route_manager.mediation_record_if_id( - mediation_id=mediation_record.mediation_id + profile, mediation_id=mediation_record.mediation_id ) @pytest.mark.asyncio -async def test_mediation_record_if_id_with_id_and_default(route_manager: RouteManager): +async def test_mediation_record_if_id_with_id_and_default( + profile: Profile, route_manager: RouteManager +): mediation_record = MediationRecord( mediation_id="test-mediation-id", state=MediationRecord.STATE_GRANTED ) @@ -208,7 +214,7 @@ async def test_mediation_record_if_id_with_id_and_default(route_manager: RouteMa MediationManager, "get_default_mediator", mock.CoroutineMock() ) as mock_get_default_mediator: actual = await route_manager.mediation_record_if_id( - mediation_id=mediation_record.mediation_id, or_default=True + profile, mediation_id=mediation_record.mediation_id, or_default=True ) assert mediation_record == actual mock_retrieve_by_id.assert_called_once() @@ -217,6 +223,7 @@ async def test_mediation_record_if_id_with_id_and_default(route_manager: RouteMa @pytest.mark.asyncio async def test_mediation_record_if_id_without_id_and_default( + profile: Profile, route_manager: RouteManager, ): mediation_record = MediationRecord( @@ -230,7 +237,7 @@ async def test_mediation_record_if_id_without_id_and_default( mock.CoroutineMock(return_value=mediation_record), ) as mock_get_default_mediator: actual = await route_manager.mediation_record_if_id( - mediation_id=None, or_default=True + profile, mediation_id=None, or_default=True ) assert mediation_record == actual mock_retrieve_by_id.assert_not_called() @@ -239,6 +246,7 @@ async def test_mediation_record_if_id_without_id_and_default( @pytest.mark.asyncio async def test_mediation_record_if_id_without_id_and_no_default( + profile: Profile, route_manager: RouteManager, ): with mock.patch.object( @@ -248,7 +256,7 @@ async def test_mediation_record_if_id_without_id_and_no_default( ) as mock_get_default_mediator: assert ( await route_manager.mediation_record_if_id( - mediation_id=None, or_default=True + profile, mediation_id=None, or_default=True ) is None ) @@ -258,7 +266,7 @@ async def test_mediation_record_if_id_without_id_and_no_default( @pytest.mark.asyncio async def test_route_connection_as_invitee( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") mock_did_info = mock.MagicMock(DIDInfo) @@ -267,15 +275,17 @@ async def test_route_connection_as_invitee( "get_or_create_my_did", mock.CoroutineMock(return_value=mock_did_info), ): - await route_manager.route_connection_as_invitee(conn_record, mediation_record) + await route_manager.route_connection_as_invitee( + profile, conn_record, mediation_record + ) route_manager._route_for_key.assert_called_once_with( - mock_did_info.verkey, mediation_record, skip_if_exists=True + profile, mock_did_info.verkey, mediation_record, skip_if_exists=True ) @pytest.mark.asyncio async def test_route_connection_as_inviter( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") mock_did_info = mock.MagicMock(DIDInfo) @@ -285,8 +295,11 @@ async def test_route_connection_as_inviter( "get_or_create_my_did", mock.CoroutineMock(return_value=mock_did_info), ): - await route_manager.route_connection_as_inviter(conn_record, mediation_record) + await route_manager.route_connection_as_inviter( + profile, conn_record, mediation_record + ) route_manager._route_for_key.assert_called_once_with( + profile, mock_did_info.verkey, mediation_record, replace_key="test-invitation-key", @@ -296,7 +309,7 @@ async def test_route_connection_as_inviter( @pytest.mark.asyncio async def test_route_connection_state_invitee( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") conn_record.state = "invitation" @@ -306,14 +319,14 @@ async def test_route_connection_state_invitee( ) as mock_route_connection_as_invitee, mock.patch.object( route_manager, "route_connection_as_inviter", mock.CoroutineMock() ) as mock_route_connection_as_inviter: - await route_manager.route_connection(conn_record, mediation_record) + await route_manager.route_connection(profile, conn_record, mediation_record) mock_route_connection_as_invitee.assert_called_once() mock_route_connection_as_inviter.assert_not_called() @pytest.mark.asyncio async def test_route_connection_state_inviter( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") conn_record.state = "request" @@ -323,57 +336,62 @@ async def test_route_connection_state_inviter( ) as mock_route_connection_as_invitee, mock.patch.object( route_manager, "route_connection_as_inviter", mock.CoroutineMock() ) as mock_route_connection_as_inviter: - await route_manager.route_connection(conn_record, mediation_record) + await route_manager.route_connection(profile, conn_record, mediation_record) mock_route_connection_as_inviter.assert_called_once() mock_route_connection_as_invitee.assert_not_called() @pytest.mark.asyncio async def test_route_connection_state_other( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") conn_record.state = "response" conn_record.their_role = "requester" - assert await route_manager.route_connection(conn_record, mediation_record) is None + assert ( + await route_manager.route_connection(profile, conn_record, mediation_record) + is None + ) @pytest.mark.asyncio async def test_route_invitation_with_key( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") conn_record.invitation_key = "test-invitation-key" with mock.patch.object( route_manager, "save_mediator_for_connection", mock.CoroutineMock() ): - await route_manager.route_invitation(conn_record, mediation_record) + await route_manager.route_invitation(profile, conn_record, mediation_record) route_manager._route_for_key.assert_called_once() @pytest.mark.asyncio async def test_route_invitation_without_key( - route_manager: RouteManager, conn_record: ConnRecord + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord ): mediation_record = MediationRecord(mediation_id="test-mediation-id") with mock.patch.object( route_manager, "save_mediator_for_connection", mock.CoroutineMock() ): with pytest.raises(ValueError): - await route_manager.route_invitation(conn_record, mediation_record) + await route_manager.route_invitation(profile, conn_record, mediation_record) route_manager._route_for_key.assert_not_called() @pytest.mark.asyncio -async def test_route_public_did(route_manager: RouteManager): - await route_manager.route_public_did("test-verkey") +async def test_route_public_did(profile: Profile, route_manager: RouteManager): + await route_manager.route_public_did(profile, "test-verkey") route_manager._route_for_key.assert_called_once_with( - "test-verkey", skip_if_exists=True + profile, "test-verkey", skip_if_exists=True ) @pytest.mark.asyncio -async def test_route_static(route_manager: RouteManager, conn_record: ConnRecord): +async def test_route_static( + profile: Profile, route_manager: RouteManager, conn_record: ConnRecord +): mediation_record = MediationRecord(mediation_id="test-mediation-id") mock_did_info = mock.MagicMock(DIDInfo) conn_record.invitation_key = "test-invitation-key" @@ -382,8 +400,9 @@ async def test_route_static(route_manager: RouteManager, conn_record: ConnRecord "get_or_create_my_did", mock.CoroutineMock(return_value=mock_did_info), ): - await route_manager.route_static(conn_record, mediation_record) + await route_manager.route_static(profile, conn_record, mediation_record) route_manager._route_for_key.assert_called_once_with( + profile, mock_did_info.verkey, mediation_record, skip_if_exists=True, @@ -392,7 +411,9 @@ async def test_route_static(route_manager: RouteManager, conn_record: ConnRecord @pytest.mark.asyncio async def test_save_mediator_for_connection_record( - route_manager: RouteManager, conn_record: ConnRecord, profile: Profile + profile: Profile, + route_manager: RouteManager, + conn_record: ConnRecord, ): mediation_record = MediationRecord(mediation_id="test-mediation-id") session = mock.MagicMock() @@ -402,7 +423,9 @@ async def test_save_mediator_for_connection_record( with mock.patch.object( MediationRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_retrieve_by_id: - await route_manager.save_mediator_for_connection(conn_record, mediation_record) + await route_manager.save_mediator_for_connection( + profile, conn_record, mediation_record + ) mock_retrieve_by_id.assert_not_called() conn_record.metadata_set.assert_called_once_with( session, @@ -413,7 +436,9 @@ async def test_save_mediator_for_connection_record( @pytest.mark.asyncio async def test_save_mediator_for_connection_id( - route_manager: RouteManager, conn_record: ConnRecord, profile: Profile + profile: Profile, + route_manager: RouteManager, + conn_record: ConnRecord, ): mediation_record = MediationRecord(mediation_id="test-mediation-id") session = mock.MagicMock() @@ -426,7 +451,7 @@ async def test_save_mediator_for_connection_id( mock.CoroutineMock(return_value=mediation_record), ) as mock_retrieve_by_id: await route_manager.save_mediator_for_connection( - conn_record, mediation_id=mediation_record.mediation_id + profile, conn_record, mediation_id=mediation_record.mediation_id ) mock_retrieve_by_id.assert_called_once() conn_record.metadata_set.assert_called_once_with( @@ -438,18 +463,21 @@ async def test_save_mediator_for_connection_id( @pytest.mark.asyncio async def test_save_mediator_for_connection_no_mediator( - route_manager: RouteManager, conn_record: ConnRecord, profile: Profile + profile: Profile, + route_manager: RouteManager, + conn_record: ConnRecord, ): with mock.patch.object( MediationRecord, "retrieve_by_id", mock.CoroutineMock() ) as mock_retrieve_by_id: - await route_manager.save_mediator_for_connection(conn_record) + await route_manager.save_mediator_for_connection(profile, conn_record) mock_retrieve_by_id.assert_not_called() conn_record.metadata_set.assert_not_called() @pytest.mark.asyncio async def test_mediation_route_for_key( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, mock_responder: MockResponder, ): @@ -457,7 +485,11 @@ async def test_mediation_route_for_key( mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" ) keylist_update = await mediation_route_manager._route_for_key( - "test-recipient-key", mediation_record, skip_if_exists=False, replace_key=None + profile, + "test-recipient-key", + mediation_record, + skip_if_exists=False, + replace_key=None, ) assert keylist_update assert keylist_update.serialize()["updates"] == [ @@ -472,6 +504,7 @@ async def test_mediation_route_for_key( @pytest.mark.asyncio async def test_mediation_route_for_key_skip_if_exists_and_exists( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, mock_responder: MockResponder, ): @@ -482,6 +515,7 @@ async def test_mediation_route_for_key_skip_if_exists_and_exists( RouteRecord, "retrieve_by_recipient_key", mock.CoroutineMock() ): keylist_update = await mediation_route_manager._route_for_key( + profile, "test-recipient-key", mediation_record, skip_if_exists=True, @@ -493,6 +527,7 @@ async def test_mediation_route_for_key_skip_if_exists_and_exists( @pytest.mark.asyncio async def test_mediation_route_for_key_skip_if_exists_and_absent( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, mock_responder: MockResponder, ): @@ -505,6 +540,7 @@ async def test_mediation_route_for_key_skip_if_exists_and_absent( mock.CoroutineMock(side_effect=StorageNotFoundError), ): keylist_update = await mediation_route_manager._route_for_key( + profile, "test-recipient-key", mediation_record, skip_if_exists=True, @@ -523,6 +559,7 @@ async def test_mediation_route_for_key_skip_if_exists_and_absent( @pytest.mark.asyncio async def test_mediation_route_for_key_replace_key( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, mock_responder: MockResponder, ): @@ -530,6 +567,7 @@ async def test_mediation_route_for_key_replace_key( mediation_id="test-mediation-id", connection_id="test-mediator-conn-id" ) keylist_update = await mediation_route_manager._route_for_key( + profile, "test-recipient-key", mediation_record, skip_if_exists=False, @@ -549,10 +587,12 @@ async def test_mediation_route_for_key_replace_key( @pytest.mark.asyncio async def test_mediation_route_for_key_no_mediator( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, ): assert ( await mediation_route_manager._route_for_key( + profile, "test-recipient-key", None, skip_if_exists=True, @@ -564,6 +604,7 @@ async def test_mediation_route_for_key_no_mediator( @pytest.mark.asyncio async def test_mediation_routing_info_with_mediator( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, ): mediation_record = MediationRecord( @@ -573,7 +614,7 @@ async def test_mediation_routing_info_with_mediator( endpoint="http://mediator.example.com", ) keys, endpoint = await mediation_route_manager.routing_info( - "http://example.com", mediation_record + profile, "http://example.com", mediation_record ) assert keys == mediation_record.routing_keys assert endpoint == mediation_record.endpoint @@ -581,10 +622,11 @@ async def test_mediation_routing_info_with_mediator( @pytest.mark.asyncio async def test_mediation_routing_info_no_mediator( + profile: Profile, mediation_route_manager: CoordinateMediationV1RouteManager, ): keys, endpoint = await mediation_route_manager.routing_info( - "http://example.com", None + profile, "http://example.com", None ) assert keys == [] assert endpoint == "http://example.com" diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 5ad6c83642..ec80923e9a 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -155,7 +155,7 @@ async def receive_invitation( ].public_key_base58 await self._route_manager.save_mediator_for_connection( - conn_rec, mediation_id=mediation_id + self.profile, conn_rec, mediation_id=mediation_id ) if conn_rec.accept == ConnRecord.ACCEPT_AUTO: @@ -259,6 +259,7 @@ async def create_request( """ # Mediation Support mediation_record = await self._route_manager.mediation_record_for_connection( + self.profile, conn_rec, mediation_id, or_default=True, @@ -290,7 +291,7 @@ async def create_request( # Idempotent; if routing has already been set up, no action taken await self._route_manager.route_connection_as_invitee( - conn_rec, mediation_record + self.profile, conn_rec, mediation_record ) # Create connection request message @@ -550,7 +551,7 @@ async def create_response( ) mediation_record = await self._route_manager.mediation_record_for_connection( - conn_rec, mediation_id + self.profile, conn_rec, mediation_id ) # Multitenancy setup @@ -583,7 +584,7 @@ async def create_response( # Idempotent; if routing has already been set up, no action taken await self._route_manager.route_connection_as_inviter( - conn_rec, mediation_record + self.profile, conn_rec, mediation_record ) # Create connection response message diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index e3f4548d47..7b02b328f7 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -110,6 +110,7 @@ async def create_invitation( """ mediation_record = await self._route_manager.mediation_record_if_id( + self.profile, mediation_id, or_default=True, ) @@ -296,7 +297,7 @@ async def create_invitation( await conn_rec.save(session, reason="Created new connection") routing_keys, my_endpoint = await self._route_manager.routing_info( - my_endpoint, mediation_record + self.profile, my_endpoint, mediation_record ) if not conn_rec: @@ -358,7 +359,9 @@ async def create_invitation( async with self.profile.session() as session: await oob_record.save(session, reason="Created new oob invitation") - await self._route_manager.route_invitation(conn_rec, mediation_record) + await self._route_manager.route_invitation( + self.profile, conn_rec, mediation_record + ) return InvitationRecord( # for return via admin API, not storage oob_id=oob_record.oob_id, @@ -392,7 +395,9 @@ async def receive_invitation( """ if mediation_id: try: - await self._route_manager.mediation_record_if_id(mediation_id) + await self._route_manager.mediation_record_if_id( + self.profile, mediation_id + ) except StorageNotFoundError: mediation_id = None diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index ff8da41b7a..fd26fe8c45 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -570,7 +570,7 @@ async def promote_wallet_public_did( # Route the public DID route_manager = profile.inject(RouteManager) - await route_manager.route_public_did(info.verkey) + await route_manager.route_public_did(profile, info.verkey) return info, attrib_def