diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index b321eaad96..d82ebb79ea 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -297,15 +297,6 @@ def add_arguments(self, parser: ArgumentParser): "invitation URL. Default: false." ), ) - parser.add_argument( - "--connections-invite", - action="store_true", - env_var="ACAPY_CONNECTIONS_INVITE", - help=( - "After startup, generate and print a new connections protocol " - "style invitation URL. Default: false." - ), - ) parser.add_argument( "--invite-label", dest="invite_label", @@ -445,8 +436,6 @@ def get_settings(self, args: Namespace) -> dict: settings["debug.seed"] = args.debug_seed if args.invite: settings["debug.print_invitation"] = True - if args.connections_invite: - settings["debug.print_connections_invitation"] = True if args.invite_label: settings["debug.invite_label"] = args.invite_label if args.invite_multi_use: @@ -1467,24 +1456,12 @@ def add_arguments(self, parser: ArgumentParser): "and send mediation request and set as default mediator." ), ) - parser.add_argument( - "--mediator-connections-invite", - action="store_true", - env_var="ACAPY_MEDIATION_CONNECTIONS_INVITE", - help=( - "Connect to mediator through a connection invitation. " - "If not specified, connect using an OOB invitation. " - "Default: false." - ), - ) def get_settings(self, args: Namespace): """Extract mediation invitation settings.""" settings = {} if args.mediator_invitation: settings["mediation.invite"] = args.mediator_invitation - if args.mediator_connections_invite: - settings["mediation.connections_invite"] = True return settings diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index c012b676a7..8f011f8602 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -3,10 +3,11 @@ from ..anoncreds.registry import AnonCredsRegistry from ..cache.base import BaseCache from ..cache.in_memory import InMemoryCache +from ..connections.base_manager import BaseConnectionManager from ..core.event_bus import EventBus from ..core.goal_code_registry import GoalCodeRegistry from ..core.plugin_registry import PluginRegistry -from ..core.profile import ProfileManager, ProfileManagerProvider +from ..core.profile import Profile, ProfileManager, ProfileManagerProvider from ..core.protocol_registry import ProtocolRegistry from ..protocols.actionmenu.v1_0.base_service import BaseMenuService from ..protocols.actionmenu.v1_0.driver_service import DriverMenuService @@ -117,6 +118,12 @@ async def bind_providers(self, context: InjectionContext): context.injector.bind_instance(BaseMenuService, DriverMenuService(context)) context.injector.bind_instance(BaseIntroductionService, DemoIntroductionService()) + # Allow BaseConnectionManager to be overridden + context.injector.bind_provider( + BaseConnectionManager, + ClassProvider(BaseConnectionManager, ClassProvider.Inject(Profile)), + ) + async def load_plugins(self, context: InjectionContext): """Set up plugin registry and load plugins.""" @@ -126,6 +133,9 @@ async def load_plugins(self, context: InjectionContext): wallet_type = self.settings.get("wallet.type") context.injector.bind_instance(PluginRegistry, plugin_registry) + # Connection management endpoints + plugin_registry.register_plugin("aries_cloudagent.connections") + # Register standard protocol plugins if not self.settings.get("transport.disabled"): plugin_registry.register_package("aries_cloudagent.protocols") diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index f5ea4451b5..6b02ee1224 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -6,6 +6,7 @@ import json import logging from typing import Dict, List, Optional, Sequence, Text, Tuple, Union +import warnings import pydid from base58 import b58decode @@ -28,10 +29,7 @@ from ..core.profile import Profile from ..did.did_key import DIDKey from ..multitenant.base import BaseMultitenantManager -from ..protocols.connections.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO -from ..protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) +from ..protocols.didexchange.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO from ..protocols.coordinate_mediation.v1_0.models.mediation_record import ( MediationRecord, ) @@ -301,6 +299,8 @@ async def create_did_document( ) -> DIDDoc: """Create our DID doc for a given DID. + This method is deprecated and will be removed. + Args: did_info (DIDInfo): The DID information (DID and verkey) used in the connection. @@ -313,6 +313,7 @@ async def create_did_document( DIDDoc: The prepared `DIDDoc` instance. """ + warnings.warn("create_did_document is deprecated and will be removed soon") did_doc = DIDDoc(did=did_info.did) did_controller = did_info.did did_key = did_info.verkey @@ -615,7 +616,7 @@ def _extract_key_material_in_base58_format(method: VerificationMethod) -> str: async def _fetch_connection_targets_for_invitation( self, connection: ConnRecord, - invitation: Union[ConnectionInvitation, InvitationMessage], + invitation: InvitationMessage, sender_verkey: str, ) -> Sequence[ConnectionTarget]: """Get a list of connection targets for an invitation. @@ -625,7 +626,7 @@ async def _fetch_connection_targets_for_invitation( Args: connection (ConnRecord): The connection record associated with the invitation. - invitation (Union[ConnectionInvitation, InvitationMessage]): The connection + invitation (InvitationMessage): The connection or OOB invitation retrieved from the connection record. sender_verkey (str): The sender's verification key. @@ -633,40 +634,23 @@ async def _fetch_connection_targets_for_invitation( Sequence[ConnectionTarget]: A list of `ConnectionTarget` objects representing the connection targets for the invitation. """ - if isinstance(invitation, ConnectionInvitation): - # conn protocol invitation - if invitation.did: - did = invitation.did - ( - endpoint, - recipient_keys, - routing_keys, - ) = await self.resolve_invitation(did) + # out-of-band invitation + oob_service_item = invitation.services[0] + if isinstance(oob_service_item, str): + ( + endpoint, + recipient_keys, + routing_keys, + ) = await self.resolve_invitation(oob_service_item) - else: - endpoint = invitation.endpoint - recipient_keys = invitation.recipient_keys - routing_keys = invitation.routing_keys else: - # out-of-band invitation - oob_service_item = invitation.services[0] - if isinstance(oob_service_item, str): - ( - endpoint, - recipient_keys, - routing_keys, - ) = await self.resolve_invitation(oob_service_item) - - else: - endpoint = oob_service_item.service_endpoint - recipient_keys = [ - DIDKey.from_did(k).public_key_b58 - for k in oob_service_item.recipient_keys - ] - routing_keys = [ - DIDKey.from_did(k).public_key_b58 - for k in oob_service_item.routing_keys - ] + endpoint = oob_service_item.service_endpoint + recipient_keys = [ + DIDKey.from_did(k).public_key_b58 for k in oob_service_item.recipient_keys + ] + routing_keys = [ + DIDKey.from_did(k).public_key_b58 for k in oob_service_item.routing_keys + ] return [ ConnectionTarget( diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index 874d8c3d7f..2fdc1f1cf7 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -15,16 +15,6 @@ INDY_RAW_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, ) -from ...protocols.connections.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO -from ...protocols.connections.v1_0.message_types import ( - CONNECTION_INVITATION, - CONNECTION_REQUEST, -) -from ...protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) -from ...protocols.connections.v1_0.messages.connection_request import ConnectionRequest -from ...protocols.didcomm_prefix import DIDCommPrefix from ...protocols.didexchange.v1_0.message_types import ARIES_PROTOCOL as DIDEX_1_1 from ...protocols.didexchange.v1_0.message_types import DIDEX_1_0 from ...protocols.didexchange.v1_0.messages.request import DIDXRequest @@ -44,7 +34,7 @@ class Meta: schema_class = "MaybeStoredConnRecordSchema" - SUPPORTED_PROTOCOLS = (CONN_PROTO, DIDEX_1_0, DIDEX_1_1) + SUPPORTED_PROTOCOLS = (DIDEX_1_0, DIDEX_1_1) class Role(Enum): """RFC 160 (inviter, invitee) = RFC 23 (responder, requester).""" @@ -430,7 +420,7 @@ async def retrieve_by_alias(cls, session: ProfileSession, alias: str) -> "ConnRe async def attach_invitation( self, session: ProfileSession, - invitation: Union[ConnectionInvitation, OOBInvitation], + invitation: OOBInvitation, ): """Persist the related connection invitation to storage. @@ -447,9 +437,7 @@ async def attach_invitation( storage = session.inject(BaseStorage) await storage.add_record(record) - async def retrieve_invitation( - self, session: ProfileSession - ) -> Union[ConnectionInvitation, OOBInvitation]: + async def retrieve_invitation(self, session: ProfileSession) -> OOBInvitation: """Retrieve the related connection invitation. Args: @@ -462,16 +450,12 @@ async def retrieve_invitation( {"connection_id": self.connection_id}, ) ser = json.loads(result.value) - return ( - ConnectionInvitation - if DIDCommPrefix.unqualify(ser["@type"]) == CONNECTION_INVITATION - else OOBInvitation - ).deserialize(ser) + return OOBInvitation.deserialize(ser) async def attach_request( self, session: ProfileSession, - request: Union[ConnectionRequest, DIDXRequest], + request: DIDXRequest, ): """Persist the related connection request to storage. @@ -491,7 +475,7 @@ async def attach_request( async def retrieve_request( self, session: ProfileSession, - ) -> Union[ConnectionRequest, DIDXRequest]: + ) -> DIDXRequest: """Retrieve the related connection invitation. Args: @@ -503,11 +487,7 @@ async def retrieve_request( self.RECORD_TYPE_REQUEST, {"connection_id": self.connection_id} ) ser = json.loads(result.value) - return ( - ConnectionRequest - if DIDCommPrefix.unqualify(ser["@type"]) == CONNECTION_REQUEST - else DIDXRequest - ).deserialize(ser) + return DIDXRequest.deserialize(ser) @property def is_ready(self) -> str: @@ -709,7 +689,7 @@ class Meta: validate=validate.OneOf(ConnRecord.SUPPORTED_PROTOCOLS), metadata={ "description": "Connection protocol used", - "example": "connections/1.0", + "example": "didexchange/1.1", }, ) rfc23_state = fields.Str( diff --git a/aries_cloudagent/connections/models/tests/test_conn_record.py b/aries_cloudagent/connections/models/tests/test_conn_record.py index c73713275d..de2688b754 100644 --- a/aries_cloudagent/connections/models/tests/test_conn_record.py +++ b/aries_cloudagent/connections/models/tests/test_conn_record.py @@ -1,15 +1,17 @@ from unittest import IsolatedAsyncioTestCase -from ....core.in_memory import InMemoryProfile -from ....protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, +from aries_cloudagent.protocols.didexchange.v1_0.messages.request import DIDXRequest +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, ) -from ....protocols.connections.v1_0.messages.connection_request import ConnectionRequest -from ....protocols.connections.v1_0.models.connection_detail import ConnectionDetail +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service +from aries_cloudagent.wallet.key_type import ED25519 +from ....did.did_key import DIDKey + +from ....core.in_memory import InMemoryProfile from ....storage.base import BaseStorage from ....storage.error import StorageNotFoundError from ..conn_record import ConnRecord -from ..diddoc.diddoc import DIDDoc class TestConnRecord(IsolatedAsyncioTestCase): @@ -19,6 +21,7 @@ def setUp(self): self.test_seed = "testseed000000000000000000000001" self.test_did = "55GkHamhTU1ZbTbV2ab9DE" self.test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" + self.test_didkey = DIDKey.from_public_key_b58(self.test_verkey, ED25519) self.test_endpoint = "http://localhost" self.test_target_did = "GbuDUYXaUZRfHD2jeDuQuP" @@ -354,14 +357,20 @@ async def test_attach_retrieve_invitation(self): ) connection_id = await record.save(self.session) - invi = ConnectionInvitation( + service = Service( + _id="asdf", + _type="did-communication", + recipient_keys=[self.test_didkey.did], + service_endpoint="http://localhost:8999", + ) + invi = InvitationMessage( + handshake_protocols=["didexchange/1.1"], + services=[service], label="abc123", - recipient_keys=[self.test_verkey], - endpoint="http://localhost:8999", ) await record.attach_invitation(self.session, invi) retrieved = await record.retrieve_invitation(self.session) - assert isinstance(retrieved, ConnectionInvitation) + assert isinstance(retrieved, InvitationMessage) async def test_attach_retrieve_request(self): record = ConnRecord( @@ -370,13 +379,13 @@ async def test_attach_retrieve_request(self): ) connection_id = await record.save(self.session) - req = ConnectionRequest( - connection=ConnectionDetail(did=self.test_did, did_doc=DIDDoc(self.test_did)), + req = DIDXRequest( + did=self.test_did, label="abc123", ) await record.attach_request(self.session, req) retrieved = await record.retrieve_request(self.session) - assert isinstance(retrieved, ConnectionRequest) + assert isinstance(retrieved, DIDXRequest) async def test_attach_request_abstain_on_alien_deco(self): record = ConnRecord( @@ -385,19 +394,19 @@ async def test_attach_request_abstain_on_alien_deco(self): ) connection_id = await record.save(self.session) - req = ConnectionRequest( - connection=ConnectionDetail(did=self.test_did, did_doc=DIDDoc(self.test_did)), + req = DIDXRequest( + did=self.test_did, label="abc123", ) ser = req.serialize() ser["~alien"] = [{"nickname": "profile-image", "data": {"links": ["face.png"]}}] - alien_req = ConnectionRequest.deserialize(ser) + alien_req = DIDXRequest.deserialize(ser) await record.attach_request(self.session, alien_req) alien_ser = alien_req.serialize() assert "~alien" in alien_ser ser["~alien"] = None - alien_req = ConnectionRequest.deserialize(ser) + alien_req = DIDXRequest.deserialize(ser) await record.attach_request(self.session, alien_req) alien_ser = alien_req.serialize() assert "~alien" not in alien_ser @@ -428,11 +437,11 @@ async def test_deserialize_connection_protocol(self): state=ConnRecord.State.INIT, my_did=self.test_did, their_role=ConnRecord.Role.REQUESTER, - connection_protocol="connections/1.0", + connection_protocol="didexchange/1.0", ) ser = record.serialize() deser = ConnRecord.deserialize(ser) - assert deser.connection_protocol == "connections/1.0" + assert deser.connection_protocol == "didexchange/1.0" async def test_metadata_set_get(self): record = ConnRecord( diff --git a/aries_cloudagent/protocols/connections/v1_0/routes.py b/aries_cloudagent/connections/routes.py similarity index 55% rename from aries_cloudagent/protocols/connections/v1_0/routes.py rename to aries_cloudagent/connections/routes.py index c6cbe47ecd..5f93754095 100644 --- a/aries_cloudagent/protocols/connections/v1_0/routes.py +++ b/aries_cloudagent/connections/routes.py @@ -1,8 +1,5 @@ """Connection handling admin routes.""" -import json -from typing import cast - from aiohttp import web from aiohttp_apispec import ( docs, @@ -11,16 +8,16 @@ request_schema, response_schema, ) -from marshmallow import fields, validate, validates_schema - -from ....admin.decorators.auth import tenant_authentication -from ....admin.request_context import AdminRequestContext -from ....cache.base import BaseCache -from ....connections.models.conn_record import ConnRecord, ConnRecordSchema -from ....messaging.models.base import BaseModelError -from ....messaging.models.openapi import OpenAPISchema -from ....messaging.models.paginated_query import PaginatedQuerySchema, get_limit_offset -from ....messaging.valid import ( +from marshmallow import fields, validate + +from ..admin.decorators.auth import tenant_authentication +from ..admin.request_context import AdminRequestContext +from ..cache.base import BaseCache +from ..connections.models.conn_record import ConnRecord, ConnRecordSchema +from ..messaging.models.base import BaseModelError +from ..messaging.models.openapi import OpenAPISchema +from ..messaging.models.paginated_query import PaginatedQuerySchema, get_limit_offset +from ..messaging.valid import ( ENDPOINT_EXAMPLE, ENDPOINT_VALIDATE, GENERIC_DID_VALIDATE, @@ -29,16 +26,10 @@ INDY_RAW_PUBLIC_KEY_EXAMPLE, INDY_RAW_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, - UUID4_VALIDATE, -) -from ....storage.error import StorageError, StorageNotFoundError -from ....wallet.error import WalletError -from .manager import ConnectionManager, ConnectionManagerError -from .message_types import SPEC_URI -from .messages.connection_invitation import ( - ConnectionInvitation, - ConnectionInvitationSchema, ) +from ..storage.error import StorageError, StorageNotFoundError +from ..wallet.error import WalletError +from .base_manager import BaseConnectionManager as ConnectionManager class ConnectionModuleResponseSchema(OpenAPISchema): @@ -78,89 +69,6 @@ class ConnectionMetadataQuerySchema(OpenAPISchema): key = fields.Str(required=False, metadata={"description": "Key to retrieve."}) -class ReceiveInvitationRequestSchema(ConnectionInvitationSchema): - """Request schema for receive invitation request.""" - - @validates_schema - def validate_fields(self, data, **kwargs): - """Bypass middleware field validation: marshmallow has no data yet.""" - - -class CreateInvitationRequestSchema(OpenAPISchema): - """Request schema for invitation connection target.""" - - recipient_keys = fields.List( - fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, - }, - ), - required=False, - metadata={"description": "List of recipient keys"}, - ) - service_endpoint = fields.Str( - required=False, - metadata={ - "description": "Connection endpoint", - "example": "http://192.168.56.102:8020", - }, - ) - routing_keys = fields.List( - fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, - }, - ), - required=False, - metadata={"description": "List of routing keys"}, - ) - my_label = fields.Str( - required=False, - metadata={ - "description": "Optional label for connection invitation", - "example": "Bob", - }, - ) - metadata = fields.Dict( - required=False, - metadata={ - "description": ( - "Optional metadata to attach to the connection created with the" - " invitation" - ) - }, - ) - mediation_id = fields.Str( - required=False, - validate=UUID4_VALIDATE, - metadata={ - "description": "Identifier for active mediation record to be used", - "example": UUID4_EXAMPLE, - }, - ) - - -class InvitationResultSchema(OpenAPISchema): - """Result schema for a new connection invitation.""" - - connection_id = fields.Str( - required=True, - metadata={"description": "Connection identifier", "example": UUID4_EXAMPLE}, - ) - invitation = fields.Nested(ConnectionInvitationSchema(), required=True) - invitation_url = fields.Str( - required=True, - metadata={ - "description": "Invitation URL", - "example": "http://192.168.56.101:8020/invite?c_i=eyJAdHlwZSI6Li4ufQ==", - }, - ) - - class ConnectionStaticRequestSchema(OpenAPISchema): """Request schema for a new static connection.""" @@ -300,78 +208,6 @@ class ConnectionsListQueryStringSchema(PaginatedQuerySchema): ) -class CreateInvitationQueryStringSchema(OpenAPISchema): - """Parameters and validators for create invitation request query string.""" - - alias = fields.Str( - required=False, metadata={"description": "Alias", "example": "Barry"} - ) - auto_accept = fields.Boolean( - required=False, - metadata={"description": "Auto-accept connection (defaults to configuration)"}, - ) - public = fields.Boolean( - required=False, - metadata={"description": "Create invitation from public DID (default false)"}, - ) - multi_use = fields.Boolean( - required=False, - metadata={"description": "Create invitation for multiple use (default false)"}, - ) - - -class ReceiveInvitationQueryStringSchema(OpenAPISchema): - """Parameters and validators for receive invitation request query string.""" - - alias = fields.Str( - required=False, metadata={"description": "Alias", "example": "Barry"} - ) - auto_accept = fields.Boolean( - required=False, - metadata={"description": "Auto-accept connection (defaults to configuration)"}, - ) - mediation_id = fields.Str( - required=False, - validate=UUID4_VALIDATE, - metadata={ - "description": "Identifier for active mediation record to be used", - "example": UUID4_EXAMPLE, - }, - ) - - -class AcceptInvitationQueryStringSchema(OpenAPISchema): - """Parameters and validators for accept invitation request query string.""" - - my_endpoint = fields.Str( - required=False, - validate=ENDPOINT_VALIDATE, - metadata={"description": "My URL endpoint", "example": ENDPOINT_EXAMPLE}, - ) - my_label = fields.Str( - required=False, - metadata={"description": "Label for connection", "example": "Broker"}, - ) - mediation_id = fields.Str( - required=False, - validate=UUID4_VALIDATE, - metadata={ - "description": "Identifier for active mediation record to be used", - "example": UUID4_EXAMPLE, - }, - ) - - -class AcceptRequestQueryStringSchema(OpenAPISchema): - """Parameters and validators for accept conn-request web-request query string.""" - - my_endpoint = fields.Str( - required=False, - validate=ENDPOINT_VALIDATE, - metadata={"description": "My URL endpoint", "example": ENDPOINT_EXAMPLE}, - ) - - class ConnectionsConnIdMatchInfoSchema(OpenAPISchema): """Path parameters and validators for request taking connection id.""" @@ -602,216 +438,6 @@ async def connections_metadata_set(request: web.BaseRequest): return web.json_response({"results": result}) -@docs( - tags=["connection"], - summary="Create a new connection invitation", - deprecated=True, -) -@querystring_schema(CreateInvitationQueryStringSchema()) -@request_schema(CreateInvitationRequestSchema()) -@response_schema(InvitationResultSchema(), 200, description="") -@tenant_authentication -async def connections_create_invitation(request: web.BaseRequest): - """Request handler for creating a new connection invitation. - - Args: - request: aiohttp request object - - Returns: - The connection invitation details - - """ - context: AdminRequestContext = request["context"] - auto_accept = json.loads(request.query.get("auto_accept", "null")) - alias = request.query.get("alias") - public = json.loads(request.query.get("public", "false")) - multi_use = json.loads(request.query.get("multi_use", "false")) - body = await request.json() if request.body_exists else {} - my_label = body.get("my_label") - recipient_keys = body.get("recipient_keys") - service_endpoint = body.get("service_endpoint") - routing_keys = body.get("routing_keys") - metadata = body.get("metadata") - mediation_id = body.get("mediation_id") - - if public and not context.settings.get("public_invites"): - raise web.HTTPForbidden( - reason="Configuration does not include public invitations" - ) - profile = context.profile - base_url = profile.settings.get("invite_base_url") - - connection_mgr = ConnectionManager(profile) - try: - (connection, invitation) = await connection_mgr.create_invitation( - my_label=my_label, - auto_accept=auto_accept, - public=public, - multi_use=multi_use, - alias=alias, - recipient_keys=recipient_keys, - my_endpoint=service_endpoint, - routing_keys=routing_keys, - metadata=metadata, - mediation_id=mediation_id, - ) - invitation_url = invitation.to_url(base_url) - base_endpoint = service_endpoint or cast( - str, profile.settings.get("default_endpoint") - ) - result = { - "connection_id": connection and connection.connection_id, - "invitation": invitation.serialize(), - "invitation_url": ( - f"{base_endpoint}{invitation_url}" - if invitation_url.startswith("?") - else invitation_url - ), - } - except (ConnectionManagerError, StorageError, BaseModelError) as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - if connection and connection.alias: - result["alias"] = connection.alias - - return web.json_response(result) - - -@docs( - tags=["connection"], - summary="Receive a new connection invitation", - deprecated=True, -) -@querystring_schema(ReceiveInvitationQueryStringSchema()) -@request_schema(ReceiveInvitationRequestSchema()) -@response_schema(ConnRecordSchema(), 200, description="") -@tenant_authentication -async def connections_receive_invitation(request: web.BaseRequest): - """Request handler for receiving a new connection invitation. - - Args: - request: aiohttp request object - - Returns: - The resulting connection record details - - """ - context: AdminRequestContext = request["context"] - if context.settings.get("admin.no_receive_invites"): - raise web.HTTPForbidden( - reason="Configuration does not allow receipt of invitations" - ) - profile = context.profile - connection_mgr = ConnectionManager(profile) - invitation_json = await request.json() - - try: - invitation = ConnectionInvitation.deserialize(invitation_json) - auto_accept = json.loads(request.query.get("auto_accept", "null")) - alias = request.query.get("alias") - mediation_id = request.query.get("mediation_id") - connection = await connection_mgr.receive_invitation( - invitation, auto_accept=auto_accept, alias=alias, mediation_id=mediation_id - ) - result = connection.serialize() - except (ConnectionManagerError, StorageError, BaseModelError) as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - return web.json_response(result) - - -@docs( - tags=["connection"], - summary="Accept a stored connection invitation", - deprecated=True, -) -@match_info_schema(ConnectionsConnIdMatchInfoSchema()) -@querystring_schema(AcceptInvitationQueryStringSchema()) -@response_schema(ConnRecordSchema(), 200, description="") -@tenant_authentication -async def connections_accept_invitation(request: web.BaseRequest): - """Request handler for accepting a stored connection invitation. - - Args: - request: aiohttp request object - - Returns: - The resulting connection record details - - """ - context: AdminRequestContext = request["context"] - outbound_handler = request["outbound_message_router"] - connection_id = request.match_info["conn_id"] - profile = context.profile - - try: - async with profile.session() as session: - connection = await ConnRecord.retrieve_by_id(session, connection_id) - connection_mgr = ConnectionManager(profile) - my_label = request.query.get("my_label") - my_endpoint = request.query.get("my_endpoint") - mediation_id = request.query.get("mediation_id") - - try: - request = await connection_mgr.create_request( - connection, my_label, my_endpoint, mediation_id=mediation_id - ) - except StorageError as err: - # Handle storage errors (including not found errors) from - # create_request separately as these errors represent a bad request - # rather than a bad url - raise web.HTTPBadRequest(reason=err.roll_up) from err - - result = connection.serialize() - except StorageNotFoundError as err: - raise web.HTTPNotFound(reason=err.roll_up) from err - except (StorageError, WalletError, ConnectionManagerError, BaseModelError) as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - await outbound_handler(request, connection_id=connection.connection_id) - return web.json_response(result) - - -@docs( - tags=["connection"], - summary="Accept a stored connection request", - deprecated=True, -) -@match_info_schema(ConnectionsConnIdMatchInfoSchema()) -@querystring_schema(AcceptRequestQueryStringSchema()) -@response_schema(ConnRecordSchema(), 200, description="") -@tenant_authentication -async def connections_accept_request(request: web.BaseRequest): - """Request handler for accepting a stored connection request. - - Args: - request: aiohttp request object - - Returns: - The resulting connection record details - - """ - context: AdminRequestContext = request["context"] - outbound_handler = request["outbound_message_router"] - connection_id = request.match_info["conn_id"] - - profile = context.profile - try: - async with profile.session() as session: - connection = await ConnRecord.retrieve_by_id(session, connection_id) - connection_mgr = ConnectionManager(profile) - my_endpoint = request.query.get("my_endpoint") or None - response = await connection_mgr.create_response(connection, my_endpoint) - result = connection.serialize() - except StorageNotFoundError as err: - raise web.HTTPNotFound(reason=err.roll_up) from err - except (StorageError, WalletError, ConnectionManagerError, BaseModelError) as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - await outbound_handler(response, connection_id=connection.connection_id) - return web.json_response(result) - - @docs(tags=["connection"], summary="Remove an existing connection record") @match_info_schema(ConnectionsConnIdMatchInfoSchema()) @response_schema(ConnectionModuleResponseSchema, 200, description="") @@ -908,16 +534,6 @@ async def register(app: web.Application): allow_head=False, ), web.post("/connections/create-static", connections_create_static), - web.post("/connections/create-invitation", connections_create_invitation), - web.post("/connections/receive-invitation", connections_receive_invitation), - web.post( - "/connections/{conn_id}/accept-invitation", - connections_accept_invitation, - ), - web.post( - "/connections/{conn_id}/accept-request", - connections_accept_request, - ), web.delete("/connections/{conn_id}", connections_remove), ] ) @@ -933,6 +549,5 @@ def post_process_routes(app: web.Application): { "name": "connection", "description": "Connection management", - "externalDocs": {"description": "Specification", "url": SPEC_URI}, } ) diff --git a/aries_cloudagent/connections/tests/test_base_manager.py b/aries_cloudagent/connections/tests/test_base_manager.py index b721935ff8..a195e46434 100644 --- a/aries_cloudagent/connections/tests/test_base_manager.py +++ b/aries_cloudagent/connections/tests/test_base_manager.py @@ -2,7 +2,9 @@ from unittest import IsolatedAsyncioTestCase from unittest.mock import call +import secrets +import base58 from pydid import DID, DIDDocument, DIDDocumentBuilder from pydid.doc.builder import ServiceBuilder from pydid.verification_method import ( @@ -12,6 +14,9 @@ JsonWebKey2020, ) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) from aries_cloudagent.tests import mock from ...cache.base import BaseCache @@ -21,18 +26,13 @@ from ...connections.models.conn_record import ConnRecord from ...connections.models.connection_target import ConnectionTarget from ...connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service +from ...protocols.out_of_band.v1_0.messages.service import Service as OOBService from ...core.in_memory import InMemoryProfile from ...core.oob_processor import OobMessageProcessor from ...did.did_key import DIDKey from ...messaging.responder import BaseResponder, MockResponder from ...multitenant.base import BaseMultitenantManager from ...multitenant.manager import MultitenantManager -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, @@ -40,11 +40,12 @@ from ...protocols.discovery.v2_0.manager import V20DiscoveryMgr from ...resolver.default.key import KeyDIDResolver from ...resolver.default.legacy_peer import LegacyPeerDIDResolver +from ...resolver.default.peer4 import PeerDID4Resolver from ...resolver.did_resolver import DIDResolver from ...storage.error import StorageNotFoundError from ...transport.inbound.receipt import MessageReceipt from ...utils.multiformats import multibase, multicodec -from ...wallet.base import DIDInfo +from ...wallet.base import BaseWallet, DIDInfo from ...wallet.did_method import SOV, DIDMethods from ...wallet.error import WalletNotFoundError from ...wallet.in_memory import InMemoryWallet @@ -91,6 +92,7 @@ async def asyncSetUp(self): self.resolver = DIDResolver() self.resolver.register_resolver(LegacyPeerDIDResolver()) self.resolver.register_resolver(KeyDIDResolver()) + self.resolver.register_resolver(PeerDID4Resolver()) self.profile = InMemoryProfile.test_profile( { @@ -109,6 +111,13 @@ async def asyncSetUp(self): DIDResolver: self.resolver, }, ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + info = await wallet.create_local_did(method=SOV, key_type=ED25519) + + self.did = info.did + self.verkey = info.verkey + self.context = self.profile.context self.multitenant_mgr = mock.MagicMock(MultitenantManager, autospec=True) @@ -123,113 +132,6 @@ async def asyncSetUp(self): self.manager = BaseConnectionManager(self.profile) assert self.manager._profile - async def test_create_did_document(self): - did_info = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - - did_doc = await self.manager.create_did_document( - did_info=did_info, - svc_endpoints=[self.test_endpoint], - ) - - async def test_create_did_document_mediation(self): - did_info = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - 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, - ) - doc = await self.manager.create_did_document( - did_info, mediation_records=[mediation_record] - ) - assert doc.service - services = list(doc.service.values()) - assert len(services) == 1 - (service,) = services - assert service.routing_keys - service_routing_key = service.routing_keys[0] - assert service_routing_key == mediation_record.routing_keys[0] - assert service.endpoint == mediation_record.endpoint - - async def test_create_did_document_multiple_mediators(self): - did_info = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - mediation_record1 = 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, - ) - mediation_record2 = MediationRecord( - role=MediationRecord.ROLE_CLIENT, - state=MediationRecord.STATE_GRANTED, - connection_id="mediator-conn-id2", - routing_keys=[ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDz#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDz" - ], - endpoint="http://mediatorw.example.com", - ) - doc = await self.manager.create_did_document( - did_info, mediation_records=[mediation_record1, mediation_record2] - ) - assert doc.service - services = list(doc.service.values()) - assert len(services) == 1 - (service,) = services - assert service.routing_keys[0] == mediation_record1.routing_keys[0] - assert service.routing_keys[1] == mediation_record2.routing_keys[0] - assert service.endpoint == mediation_record2.endpoint - - async def test_create_did_document_mediation_svc_endpoints_overwritten(self): - did_info = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - 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, - ) - self.route_manager.routing_info = mock.CoroutineMock( - return_value=(mediation_record.routing_keys, mediation_record.endpoint) - ) - doc = await self.manager.create_did_document( - did_info, - svc_endpoints=[self.test_endpoint], - mediation_records=[mediation_record], - ) - assert doc.service - services = list(doc.service.values()) - assert len(services) == 1 - (service,) = services - service_public_keys = service.routing_keys[0] - assert service_public_keys == mediation_record.routing_keys[0] - assert service.endpoint == mediation_record.endpoint - async def test_did_key_storage(self): await self.manager.add_key_for_did( did=self.test_target_did, key=self.test_target_verkey @@ -247,160 +149,83 @@ async def test_fetch_connection_targets_no_my_did(self): mock_conn.my_did = None assert await self.manager.fetch_connection_targets(mock_conn) == [] - async def test_fetch_connection_targets_conn_invitation_did_no_resolver(self): - async with self.profile.session() as session: - self.context.injector.bind_instance(DIDResolver, DIDResolver([])) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=self.test_target_did, - endpoint=self.test_endpoint, - recipient_keys=[self.test_target_verkey], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=self.test_did, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - - with self.assertRaises(BaseConnectionManagerError): - await self.manager.fetch_connection_targets(mock_conn) - - async def test_fetch_connection_targets_conn_invitation_did_resolver(self): - async with self.profile.session() as session: - builder = DIDDocumentBuilder("did:sov:" + self.test_target_did) - vmethod = builder.verification_method.add( - Ed25519VerificationKey2018, public_key_base58=self.test_target_verkey - ) - builder.service.add_didcomm( - ident="did-communication", - service_endpoint=self.test_endpoint, - recipient_keys=[vmethod], - ) - did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) - self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) - self.resolver.dereference = mock.CoroutineMock( - return_value=did_doc.verification_method[0] - ) - self.context.injector.bind_instance(DIDResolver, self.resolver) - - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=self.test_target_did, - endpoint=self.test_endpoint, - recipient_keys=[self.test_target_verkey], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=self.test_did, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == conn_invite.endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey - - async def test_fetch_connection_targets_conn_invitation_btcr_resolver(self): - async with self.profile.session() as session: - builder = DIDDocumentBuilder("did:btcr:x705-jznz-q3nl-srs") - vmethod = builder.verification_method.add( - Ed25519VerificationKey2018, public_key_base58=self.test_target_verkey - ) - builder.service.add_didcomm( - type_="IndyAgent", - recipient_keys=[vmethod], - routing_keys=[vmethod], - service_endpoint=self.test_endpoint, - priority=1, - ) - - builder.service.add_didcomm( - recipient_keys=[vmethod], - routing_keys=[vmethod], - service_endpoint=self.test_endpoint, - priority=0, - ) - builder.service.add_didcomm( - recipient_keys=[vmethod], - routing_keys=[vmethod], - service_endpoint="{}/priority2".format(self.test_endpoint), - priority=2, - ) - did_doc = builder.build() - - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) - self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) - self.resolver.dereference = mock.CoroutineMock( - return_value=did_doc.verification_method[0] - ) - self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=[vmethod.material], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=did_doc.id, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) + async def test_fetch_connection_targets_in_progress_conn(self): + mock_conn = mock.MagicMock( + my_did=self.did, + their_did=self.test_target_did, + connection_id="dummy", + their_role=ConnRecord.Role.RESPONDER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + ) + with mock.patch.object( + self.manager, + "_fetch_targets_for_connection_in_progress", + mock.CoroutineMock(), + ) as mock_fetch_in_progress: + await self.manager.fetch_connection_targets(mock_conn) + mock_fetch_in_progress.assert_called() + + async def test_fetch_targets_for_connection_in_progress_inv(self): + mock_conn = mock.MagicMock( + my_did=self.test_did, + their_did=self.test_target_did, + connection_id="dummy", + their_role=ConnRecord.Role.RESPONDER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + invitation_msg_id="test-invite-msg-id", + ) + mock_conn.retrieve_invitation = mock.CoroutineMock() + with mock.patch.object( + self.manager, + "_fetch_connection_targets_for_invitation", + mock.CoroutineMock(), + ) as mock_fetch_connection_targets_for_invitation: + await self.manager._fetch_targets_for_connection_in_progress( + mock_conn, self.test_did + ) + mock_fetch_connection_targets_for_invitation.assert_called() + + async def test_fetch_targets_for_connection_in_progress_implicit(self): + mock_conn = mock.MagicMock( + my_did=self.test_did, + their_did=self.test_target_did, + connection_id="dummy", + their_role=ConnRecord.Role.RESPONDER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + invitation_msg_id=None, + invitation_key=None, + ) + with mock.patch.object( + self.manager, + "resolve_invitation", + mock.CoroutineMock( + return_value=(mock.MagicMock(), mock.MagicMock(), mock.MagicMock()) + ), + ) as mock_resolve_invitation: + await self.manager._fetch_targets_for_connection_in_progress( + mock_conn, self.test_did + ) + mock_resolve_invitation.assert_called() + + async def test_fetch_connection_targets_for_invitation_did_resolver(self): + target_did_info = await self.manager.create_did_peer_4() + target_did = target_did_info.did + invitation = InvitationMessage(services=[OOBService(did=target_did)]) + + mock_conn = mock.MagicMock( + my_did=self.test_did, + their_did=target_did, + connection_id="dummy", + their_role=ConnRecord.Role.RESPONDER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + ) - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == [vmethod.material] - assert target.sender_key == local_did.verkey + targets = await self.manager._fetch_connection_targets_for_invitation( + mock_conn, invitation, self.test_did + ) + assert len(targets) == 1 + target = targets[0] + assert target.did == mock_conn.their_did async def test_fetch_connection_targets_conn_invitation_btcr_without_services(self): async with self.profile.session() as session: @@ -426,78 +251,71 @@ async def test_fetch_connection_targets_conn_invitation_btcr_without_services(se ], } did_doc = DIDDocument.deserialize(did_doc_json) - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=["{}#1".format(did_doc.id)], - routing_keys=[self.test_verkey], - label="label", - ) + invitation = InvitationMessage(services=[did_doc.id]) mock_conn = mock.MagicMock( my_did=did_doc.id, their_did=self.test_target_did, connection_id="dummy", their_role=ConnRecord.Role.RESPONDER.rfc23, state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), + retrieve_invitation=mock.CoroutineMock(return_value=invitation), ) with self.assertRaises(BaseConnectionManagerError): - await self.manager.fetch_connection_targets(mock_conn) + await self.manager._fetch_connection_targets_for_invitation( + mock_conn, invitation, self.test_did + ) async def test_fetch_connection_targets_conn_invitation_no_didcomm_services(self): async with self.profile.session() as session: - builder = DIDDocumentBuilder("did:btcr:x705-jznz-q3nl-srs") + did_doc_json = { + "@context": ["https://www.w3.org/ns/did/v1"], + "id": "did:btcr:x705-jznz-q3nl-srs", + "verificationMethod": [ + { + "type": "EcdsaSecp256k1VerificationKey2019", + "id": "did:btcr:x705-jznz-q3nl-srs#key-0", + "publicKeyBase58": "02e0e01a8c302976e1556e95c54146e8464adac8626a5d29474718a7281133ff49", + }, + { + "type": "EcdsaSecp256k1VerificationKey2019", + "id": "did:btcr:x705-jznz-q3nl-srs#key-1", + "publicKeyBase58": "02e0e01a8c302976e1556e95c54146e8464adac8626a5d29474718a7281133ff49", + }, + { + "type": "EcdsaSecp256k1VerificationKey2019", + "id": "did:btcr:x705-jznz-q3nl-srs#satoshi", + "publicKeyBase58": "02e0e01a8c302976e1556e95c54146e8464adac8626a5d29474718a7281133ff49", + }, + ], + } + + did_doc = DIDDocument.deserialize(did_doc_json) + builder = DIDDocumentBuilder.from_doc(did_doc) builder.verification_method.add( Ed25519VerificationKey2018, public_key_base58=self.test_target_verkey ) builder.service.add(type_="LinkedData", service_endpoint=self.test_endpoint) did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) self.context.injector.bind_instance(DIDResolver, self.resolver) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=["{}#1".format(did_doc.id)], - routing_keys=[self.test_verkey], - label="label", - ) + invitation = InvitationMessage(services=[did_doc.id]) mock_conn = mock.MagicMock( my_did=did_doc.id, their_did=self.test_target_did, connection_id="dummy", their_role=ConnRecord.Role.RESPONDER.rfc23, state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), + retrieve_invitation=mock.CoroutineMock(return_value=invitation), ) with self.assertRaises(BaseConnectionManagerError): - await self.manager.fetch_connection_targets(mock_conn) + await self.manager._fetch_connection_targets_for_invitation( + mock_conn, invitation, self.test_did + ) - async def test_fetch_connection_targets_conn_invitation_supports_Ed25519VerificationKey2018_key_type_no_multicodec( + async def test_resolve_invitation_supports_Ed25519VerificationKey2018_key_type_no_multicodec( self, ): async with self.profile.session() as session: @@ -514,49 +332,19 @@ async def test_fetch_connection_targets_conn_invitation_supports_Ed25519Verifica recipient_keys=[vmethod], ) did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) + assert did_doc.verification_method self.resolver.dereference = mock.CoroutineMock( return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=[vmethod.public_key_jwk], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=did_doc.id, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == [self.test_target_verkey] - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey + endpoint, recips, routes = await self.manager.resolve_invitation(did_doc.id) + assert endpoint == self.test_endpoint + assert recips == [self.test_target_verkey] + assert routes == [] - async def test_fetch_connection_targets_conn_invitation_supports_Ed25519VerificationKey2018_key_type_with_multicodec( + async def test_resolve_invitation_supports_Ed25519VerificationKey2018_key_type_with_multicodec( self, ): async with self.profile.session() as session: @@ -574,49 +362,19 @@ async def test_fetch_connection_targets_conn_invitation_supports_Ed25519Verifica recipient_keys=[vmethod], ) did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) + assert did_doc.verification_method self.resolver.dereference = mock.CoroutineMock( return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=[vmethod.public_key_jwk], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=did_doc.id, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == [self.test_target_verkey] - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey + endpoint, recips, routes = await self.manager.resolve_invitation(did_doc.id) + assert endpoint == self.test_endpoint + assert recips == [self.test_target_verkey] + assert routes == [] - async def test_fetch_connection_targets_conn_invitation_supported_JsonWebKey2020_key_type( + async def test_resolve_invitation_supported_JsonWebKey2020_key_type( self, ): async with self.profile.session() as session: @@ -636,49 +394,19 @@ async def test_fetch_connection_targets_conn_invitation_supported_JsonWebKey2020 recipient_keys=[vmethod], ) did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) + assert did_doc.verification_method self.resolver.dereference = mock.CoroutineMock( return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=[vmethod.public_key_jwk], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=did_doc.id, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == [self.test_target_verkey] - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey + endpoint, recips, routes = await self.manager.resolve_invitation(did_doc.id) + assert endpoint == self.test_endpoint + assert recips == [self.test_target_verkey] + assert routes == [] - async def test_fetch_connection_targets_conn_invitation_unsupported_key_type(self): + async def test_resolve_invitation_unsupported_key_type(self): async with self.profile.session() as session: builder = DIDDocumentBuilder("did:btcr:x705-jznz-q3nl-srs") vmethod = builder.verification_method.add( @@ -697,167 +425,14 @@ async def test_fetch_connection_targets_conn_invitation_unsupported_key_type(sel recipient_keys=[vmethod], ) did_doc = builder.build() - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) + assert did_doc.verification_method self.resolver.dereference = mock.CoroutineMock( return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) - - conn_invite = ConnectionInvitation( - did=did_doc.id, - endpoint=self.test_endpoint, - recipient_keys=["{}#1".format(did_doc.id)], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=did_doc.id, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.fetch_connection_targets(mock_conn) - - async def test_fetch_connection_targets_oob_invitation_svc_did_no_resolver(self): - async with self.profile.session() as session: - self.context.injector.bind_instance(DIDResolver, DIDResolver([])) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - mock_oob_invite = mock.MagicMock(services=[self.test_did]) - - mock_conn = mock.MagicMock( - my_did=self.test_did, - retrieve_invitation=mock.CoroutineMock(return_value=mock_oob_invite), - state=ConnRecord.State.INVITATION.rfc23, - their_role=ConnRecord.Role.RESPONDER.rfc23, - ) - with self.assertRaises(BaseConnectionManagerError): - await self.manager.fetch_connection_targets(mock_conn) - - async def test_fetch_connection_targets_oob_invitation_svc_did_resolver(self): - async with self.profile.session() as session: - builder = DIDDocumentBuilder("did:sov:" + self.test_target_did) - vmethod = builder.verification_method.add( - Ed25519VerificationKey2018, - ident="1", - public_key_base58=self.test_target_verkey, - ) - builder.service.add_didcomm( - ident="did-communication", - service_endpoint=self.test_endpoint, - recipient_keys=[vmethod], - ) - did_doc = builder.build() - - self.resolver.resolve = mock.CoroutineMock(return_value=did_doc) - self.resolver.dereference = mock.CoroutineMock( - return_value=did_doc.verification_method[0] - ) - self.context.injector.bind_instance(DIDResolver, self.resolver) - - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - mock_oob_invite = mock.MagicMock( - label="a label", - their_did=self.test_target_did, - services=["dummy"], - ) - mock_conn = mock.MagicMock( - my_did=self.test_did, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=mock_oob_invite), - ) - - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == mock_oob_invite.label - assert target.recipient_keys == [vmethod.material] - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey - - async def test_fetch_connection_targets_oob_invitation_svc_block_resolver(self): - async with self.profile.session() as session: - self.resolver.get_endpoint_for_did = mock.CoroutineMock( - return_value=self.test_endpoint - ) - self.resolver.get_key_for_did = mock.CoroutineMock( - return_value=self.test_target_verkey - ) - self.context.injector.bind_instance(DIDResolver, self.resolver) - - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - mock_oob_invite = mock.MagicMock( - label="a label", - their_did=self.test_target_did, - services=[ - mock.MagicMock( - service_endpoint=self.test_endpoint, - recipient_keys=[ - DIDKey.from_public_key_b58( - self.test_target_verkey, ED25519 - ).did - ], - routing_keys=[], - ) - ], - ) - mock_conn = mock.MagicMock( - my_did=self.test_did, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=mock_oob_invite), - ) - - targets = await self.manager.fetch_connection_targets(mock_conn) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == self.test_endpoint - assert target.label == mock_oob_invite.label - assert target.recipient_keys == [self.test_target_verkey] - assert target.routing_keys == [] - assert target.sender_key == local_did.verkey + await self.manager.resolve_invitation(did_doc.id) async def test_fetch_connection_targets_conn_initiator_completed_no_their_did(self): async with self.profile.session() as session: @@ -1230,65 +805,6 @@ async def test_resolve_inbound_connection_wallet_not_found_error(self): assert await self.manager.resolve_inbound_connection(receipt) - async def test_get_connection_targets_conn_invitation_no_did(self): - async with self.profile.session() as session: - local_did = await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - - did_doc = self.make_did_doc( - did=self.test_target_did, verkey=self.test_target_verkey - ) - await self.manager.store_did_document(did_doc) - - # First pass: not yet in cache - conn_invite = ConnectionInvitation( - did=None, - endpoint=self.test_endpoint, - recipient_keys=[self.test_target_verkey], - routing_keys=[self.test_verkey], - label="label", - ) - mock_conn = mock.MagicMock( - my_did=self.test_did, - their_did=self.test_target_did, - connection_id="dummy", - their_role=ConnRecord.Role.RESPONDER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - retrieve_invitation=mock.CoroutineMock(return_value=conn_invite), - ) - - targets = await self.manager.get_connection_targets( - connection_id=None, - connection=mock_conn, - ) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == conn_invite.endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys - assert target.sender_key == local_did.verkey - - # Next pass: exercise cache - targets = await self.manager.get_connection_targets( - connection_id=None, - connection=mock_conn, - ) - assert len(targets) == 1 - target = targets[0] - assert target.did == mock_conn.their_did - assert target.endpoint == conn_invite.endpoint - assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys - assert target.sender_key == local_did.verkey - async def test_get_connection_targets_retrieve_connection(self): async with self.profile.session() as session: local_did = await session.wallet.create_local_did( @@ -1305,11 +821,16 @@ async def test_get_connection_targets_retrieve_connection(self): await self.manager.store_did_document(did_doc) # Connection target not in cache - conn_invite = ConnectionInvitation( + service = OOBService( did=None, - endpoint=self.test_endpoint, - recipient_keys=[self.test_target_verkey], - routing_keys=[self.test_verkey], + service_endpoint=self.test_endpoint, + recipient_keys=[ + DIDKey.from_public_key_b58(self.test_target_verkey, ED25519).did + ], + routing_keys=[DIDKey.from_public_key_b58(self.test_verkey, ED25519).did], + ) + conn_invite = InvitationMessage( + services=[service], label="label", ) mock_conn = mock.MagicMock( @@ -1335,10 +856,10 @@ async def test_get_connection_targets_retrieve_connection(self): assert len(targets) == 1 target = targets[0] assert target.did == mock_conn.their_did - assert target.endpoint == conn_invite.endpoint + assert target.endpoint == service.service_endpoint assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys + assert target.recipient_keys == [self.test_target_verkey] + assert target.routing_keys == [self.test_verkey] assert target.sender_key == local_did.verkey async def test_get_connection_targets_from_cache(self): @@ -1450,11 +971,16 @@ async def test_get_conn_targets_conn_invitation_no_cache(self): ) await self.manager.store_did_document(did_doc) - conn_invite = ConnectionInvitation( + service = OOBService( did=None, - endpoint=self.test_endpoint, - recipient_keys=[self.test_target_verkey], - routing_keys=[self.test_verkey], + service_endpoint=self.test_endpoint, + recipient_keys=[ + DIDKey.from_public_key_b58(self.test_target_verkey, ED25519).did + ], + routing_keys=[DIDKey.from_public_key_b58(self.test_verkey, ED25519).did], + ) + conn_invite = InvitationMessage( + services=[service], label="label", ) mock_conn = mock.MagicMock( @@ -1473,16 +999,16 @@ async def test_get_conn_targets_conn_invitation_no_cache(self): assert len(targets) == 1 target = targets[0] assert target.did == mock_conn.their_did - assert target.endpoint == conn_invite.endpoint + assert target.endpoint == service.service_endpoint assert target.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys + assert target.recipient_keys == [self.test_target_verkey] + assert target.routing_keys == [self.test_verkey] assert target.sender_key == local_did.verkey async def test_create_static_connection(self): with mock.patch.object(ConnRecord, "save", autospec=True) as mock_conn_rec_save: _my, _their, conn_rec = await self.manager.create_static_connection( - my_did=self.test_did, + my_did=base58.b58encode(secrets.token_bytes(16)).decode(), their_did=self.test_target_did, their_verkey=self.test_target_verkey, their_endpoint=self.test_endpoint, diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index b81d29636b..8cb45a12b1 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -42,12 +42,9 @@ from ..messaging.responder import BaseResponder from ..multitenant.base import BaseMultitenantManager from ..multitenant.manager_provider import MultitenantManagerProvider -from ..protocols.connections.v1_0.manager import ( - ConnectionManager, - ConnectionManagerError, -) -from ..protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, +from ..connections.base_manager import ( + BaseConnectionManager, + BaseConnectionManagerError, ) from ..protocols.coordinate_mediation.mediation_invite_store import MediationInviteStore from ..protocols.coordinate_mediation.v1_0.manager import MediationManager @@ -273,7 +270,7 @@ async def setup(self): ) # at the class level (!) should not be performed multiple times collector.wrap( - ConnectionManager, + BaseConnectionManager, ( # "get_connection_targets", "fetch_did_document", @@ -393,7 +390,7 @@ async def start(self) -> None: # Create a static connection for use by the test-suite if context.settings.get("debug.test_suite_endpoint"): - mgr = ConnectionManager(self.root_profile) + mgr = BaseConnectionManager(self.root_profile) their_endpoint = context.settings["debug.test_suite_endpoint"] test_conn = await mgr.create_static_connection( my_seed=hashlib.sha256(b"aries-protocol-test-subject").digest(), @@ -449,29 +446,6 @@ async def start(self) -> None: except Exception: LOGGER.exception("Error creating invitation") - # Print connections protocol invitation to the terminal - if context.settings.get("debug.print_connections_invitation"): - try: - mgr = ConnectionManager(self.root_profile) - _record, invite = await mgr.create_invitation( - my_label=context.settings.get("debug.invite_label"), - public=context.settings.get("debug.invite_public", False), - multi_use=context.settings.get("debug.invite_multi_use", False), - metadata=json.loads( - context.settings.get("debug.invite_metadata_json", "{}") - ), - ) - base_url = context.settings.get("invite_base_url") - invite_url = invite.to_url(base_url) - print("Invitation URL (Connections protocol):") - print(invite_url, flush=True) - qr = QRCode(border=1) - qr.add_data(invite_url) - qr.print_ascii(invert=True) - del mgr - except Exception: - LOGGER.exception("Error creating invitation") - # mediation connection establishment provided_invite: str = context.settings.get("mediation.invite") @@ -488,27 +462,14 @@ async def start(self) -> None: # Accept mediation invitation if one was specified or stored if mediation_invite_record is not None: try: - mediation_connections_invite = context.settings.get( - "mediation.connections_invite", False - ) - invitation_handler = ( - ConnectionInvitation - if mediation_connections_invite - else InvitationMessage - ) - if not mediation_invite_record.used: # clear previous mediator configuration before establishing a # new one await MediationManager(self.root_profile).clear_default_mediator() - mgr = ( - ConnectionManager(self.root_profile) - if mediation_connections_invite - else OutOfBandManager(self.root_profile) - ) + mgr = OutOfBandManager(self.root_profile) record = await mgr.receive_invitation( - invitation=invitation_handler.from_url( + invitation=InvitationMessage.from_url( mediation_invite_record.invite ), auto_accept=True, @@ -718,12 +679,12 @@ async def queue_outbound( # populate connection target(s) if not has_target and outbound.connection_id: - conn_mgr = ConnectionManager(profile) + conn_mgr = profile.inject(BaseConnectionManager) try: outbound.target_list = await self.dispatcher.run_task( conn_mgr.get_connection_targets(connection_id=outbound.connection_id) ) - except ConnectionManagerError: + except BaseConnectionManagerError: LOGGER.exception("Error preparing outbound message for transmission") return OutboundSendStatus.UNDELIVERABLE except (LedgerConfigError, LedgerTransactionError) as e: diff --git a/aries_cloudagent/core/tests/test_conductor.py b/aries_cloudagent/core/tests/test_conductor.py index 669c472a12..e1485e36ff 100644 --- a/aries_cloudagent/core/tests/test_conductor.py +++ b/aries_cloudagent/core/tests/test_conductor.py @@ -1,6 +1,6 @@ -from io import StringIO from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.connections.base_manager import BaseConnectionManager from aries_cloudagent.tests import mock from ...admin.base_server import BaseAdminServer @@ -95,6 +95,7 @@ async def build_context(self) -> InjectionContext: context.injector.bind_instance(DIDMethods, DIDMethods()) context.injector.bind_instance(DIDResolver, DIDResolver([])) context.injector.bind_instance(EventBus, MockEventBus()) + context.injector.bind_instance(BaseConnectionManager, mock.MagicMock()) return context @@ -666,13 +667,14 @@ async def test_outbound_message_handler_with_connection(self): with mock.patch.object( test_module, "OutboundTransportManager", autospec=True - ) as mock_outbound_mgr, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as conn_mgr: + ) as mock_outbound_mgr: mock_outbound_mgr.return_value.registered_transports = { "test": mock.MagicMock(schemes=["http"]) } await conductor.setup() + conn_mgr = mock.MagicMock(autospec=True) + conn_mgr.get_connection_targets = mock.CoroutineMock() + conductor.context.injector.bind_instance(BaseConnectionManager, conn_mgr) bus = conductor.root_profile.inject(EventBus) @@ -690,13 +692,10 @@ async def test_outbound_message_handler_with_connection(self): assert bus.events[0][1].topic == status.topic assert bus.events[0][1].payload == message - conn_mgr.return_value.get_connection_targets.assert_awaited_once_with( + conn_mgr.get_connection_targets.assert_awaited_once_with( connection_id=connection_id ) - assert ( - message.target_list - is conn_mgr.return_value.get_connection_targets.return_value - ) + assert message.target_list is conn_mgr.get_connection_targets.return_value mock_outbound_mgr.return_value.enqueue_message.assert_called_once_with( conductor.root_profile, message @@ -762,16 +761,17 @@ async def test_handle_nots(self): conductor.handle_not_returned(conductor.root_profile, message) + mock_conn_mgr = mock.MagicMock() + conductor.context.injector.bind_instance(BaseConnectionManager, mock_conn_mgr) + with mock.patch.object( - test_module, "ConnectionManager" - ) as mock_conn_mgr, mock.patch.object( conductor.dispatcher, "run_task", mock.MagicMock() ) as mock_run_task: # Normally this should be a coroutine mock; however, the coroutine # is awaited by dispatcher.run_task, which is mocked here. MagicMock # to prevent unawaited coroutine warning. mock_conn_mgr.return_value.get_connection_targets = mock.MagicMock() - mock_run_task.side_effect = test_module.ConnectionManagerError() + mock_run_task.side_effect = test_module.BaseConnectionManagerError() await conductor.queue_outbound(conductor.root_profile, message) mock_outbound_mgr.return_value.enqueue_message.assert_not_called() @@ -849,9 +849,9 @@ async def test_queue_outbound_ledger_x(self): } await conductor.setup() + conn_mgr = mock.MagicMock() + conductor.context.injector.bind_instance(BaseConnectionManager, conn_mgr) with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as conn_mgr, mock.patch.object( conductor.dispatcher, "run_task", mock.MagicMock() ) as mock_dispatch_run, mock.patch.object( conductor.admin_server, "notify_fatal_error", mock.MagicMock() @@ -859,7 +859,7 @@ async def test_queue_outbound_ledger_x(self): # Normally this should be a coroutine mock; however, the coroutine # is awaited by dispatcher.run_task, which is mocked here. MagicMock # to prevent unawaited coroutine warning. - conn_mgr.return_value.get_connection_targets = mock.MagicMock() + conn_mgr.get_connection_targets = mock.MagicMock() mock_dispatch_run.side_effect = test_module.LedgerConfigError( "No such ledger" ) @@ -955,7 +955,7 @@ async def test_admin_startx(self): ) as admin_stop, mock.patch.object( test_module, "OutOfBandManager" ) as oob_mgr, mock.patch.object( - test_module, "ConnectionManager" + test_module, "BaseConnectionManager" ) as conn_mgr, mock.patch.object( test_module, "upgrade_wallet_to_anoncreds_if_requested", return_value=False ) as mock_upgrade, mock.patch.object( @@ -1003,7 +1003,7 @@ async def test_start_static(self): conductor = test_module.Conductor(builder) with mock.patch.object( - test_module, "ConnectionManager" + test_module, "BaseConnectionManager" ) as mock_mgr, mock.patch.object( BaseStorage, "find_record", @@ -1040,7 +1040,7 @@ async def test_start_x_in(self): conductor = test_module.Conductor(builder) with mock.patch.object( - test_module, "ConnectionManager" + test_module, "BaseConnectionManager" ) as mock_mgr, mock.patch.object( test_module, "InboundTransportManager" ) as mock_intx_mgr, mock.patch.object( @@ -1064,7 +1064,7 @@ async def test_start_x_out_a(self): conductor = test_module.Conductor(builder) with mock.patch.object( - test_module, "ConnectionManager" + test_module, "BaseConnectionManager" ) as mock_mgr, mock.patch.object( test_module, "OutboundTransportManager" ) as mock_outx_mgr: @@ -1084,7 +1084,7 @@ async def test_start_x_out_b(self): conductor = test_module.Conductor(builder) with mock.patch.object( - test_module, "ConnectionManager" + test_module, "BaseConnectionManager" ) as mock_mgr, mock.patch.object( test_module, "OutboundTransportManager" ) as mock_outx_mgr: @@ -1166,52 +1166,6 @@ async def test_dispatch_complete_fatal_x(self): conductor.dispatch_complete(message, mock_task) mock_notify.assert_called_once_with() - async def test_print_invite_connection(self): - builder: ContextBuilder = StubContextBuilder(self.test_settings) - builder.update_settings( - { - "debug.print_invitation": True, - "debug.print_connections_invitation": True, - "invite_base_url": "http://localhost", - "wallet.type": "askar", - "default_endpoint": "http://localhost", - "default_label": "test", - } - ) - conductor = test_module.Conductor(builder) - - with mock.patch("sys.stdout", new=StringIO()) as captured, mock.patch.object( - BaseStorage, - "find_record", - mock.CoroutineMock( - side_effect=[ - mock.MagicMock(value="askar"), - mock.MagicMock(value=f"v{__version__}"), - ] - ), - ), mock.patch.object( - test_module, "OutboundTransportManager", autospec=True - ) as mock_outbound_mgr, mock.patch.object( - test_module, "upgrade_wallet_to_anoncreds_if_requested", return_value=False - ) as mock_upgrade: - mock_outbound_mgr.return_value.registered_transports = { - "test": mock.MagicMock(schemes=["http"]) - } - await conductor.setup() - - session = await conductor.root_profile.session() - wallet = session.inject(BaseWallet) - await wallet.create_public_did( - SOV, - ED25519, - ) - - await conductor.start() - await conductor.stop() - value = captured.getvalue() - assert "http://localhost?oob=" in value - assert "http://localhost?c_i=" in value - async def test_clear_default_mediator(self): builder: ContextBuilder = StubContextBuilder(self.test_settings) builder.update_settings({"mediation.clear": True}) @@ -1425,51 +1379,6 @@ def __get_mediator_config( return builder - @mock.patch.object( - test_module, - "MediationInviteStore", - return_value=get_invite_store_mock("test-invite"), - ) - @mock.patch.object(test_module.ConnectionInvitation, "from_url") - async def test_mediator_invitation_0160(self, mock_from_url, _): - conductor = test_module.Conductor(self.__get_mediator_config("test-invite", True)) - with mock.patch.object( - test_module, "OutboundTransportManager", autospec=True - ) as mock_outbound_mgr: - mock_outbound_mgr.return_value.registered_transports = { - "test": mock.MagicMock(schemes=["http"]) - } - await conductor.setup() - - mock_conn_record = mock.MagicMock() - - with mock.patch.object( - test_module, - "ConnectionManager", - mock.MagicMock( - return_value=mock.MagicMock( - receive_invitation=mock.CoroutineMock(return_value=mock_conn_record) - ) - ), - ) as mock_mgr, mock.patch.object( - mock_conn_record, "metadata_set", mock.CoroutineMock() - ), mock.patch.object( - test_module, "upgrade_wallet_to_anoncreds_if_requested", return_value=False - ) as mock_upgrade, mock.patch.object( - BaseStorage, - "find_record", - mock.CoroutineMock( - side_effect=[ - mock.MagicMock(value="askar"), - mock.MagicMock(value=f"v{__version__}"), - ] - ), - ): - await conductor.start() - await conductor.stop() - mock_from_url.assert_called_once_with("test-invite") - mock_mgr.return_value.receive_invitation.assert_called_once() - @mock.patch.object( test_module, "MediationInviteStore", @@ -1533,69 +1442,7 @@ async def test_mediator_invitation_0434(self, mock_from_url, _): mock_mgr.return_value.receive_invitation.assert_called_once() @mock.patch.object(test_module, "MediationInviteStore") - @mock.patch.object(test_module.ConnectionInvitation, "from_url") - async def test_mediation_invitation_should_use_stored_invitation( - self, patched_from_url, patched_invite_store - ): - """ - Conductor should store the mediation invite if it differs from the stored one or - if the stored one was not used yet. - - Using a mediation invitation should clear the previously set default mediator. - """ - # given - invite_string = "test-invite" - - conductor = test_module.Conductor(self.__get_mediator_config(invite_string, True)) - with mock.patch.object( - test_module, "OutboundTransportManager", autospec=True - ) as mock_outbound_mgr: - mock_outbound_mgr.return_value.registered_transports = { - "test": mock.MagicMock(schemes=["http"]) - } - await conductor.setup() - mock_conn_record = mock.MagicMock() - mocked_store = get_invite_store_mock(invite_string) - patched_invite_store.return_value = mocked_store - - connection_manager_mock = mock.MagicMock( - receive_invitation=mock.CoroutineMock(return_value=mock_conn_record) - ) - mock_mediation_manager = mock.MagicMock( - clear_default_mediator=mock.CoroutineMock() - ) - - # when - with mock.patch.object( - test_module, "ConnectionManager", return_value=connection_manager_mock - ), mock.patch.object( - mock_conn_record, "metadata_set", mock.CoroutineMock() - ), mock.patch.object( - test_module, "MediationManager", return_value=mock_mediation_manager - ), mock.patch.object( - test_module, "upgrade_wallet_to_anoncreds_if_requested", return_value=False - ) as mock_upgrade, mock.patch.object( - BaseStorage, - "find_record", - mock.CoroutineMock( - side_effect=[ - mock.MagicMock(value="askar"), - mock.MagicMock(value=f"v{__version__}"), - ] - ), - ): - await conductor.start() - await conductor.stop() - - # then - mocked_store.get_mediation_invite_record.assert_called_with(invite_string) - - connection_manager_mock.receive_invitation.assert_called_once() - patched_from_url.assert_called_with(invite_string) - mock_mediation_manager.clear_default_mediator.assert_called_once() - - @mock.patch.object(test_module, "MediationInviteStore") - @mock.patch.object(test_module, "ConnectionManager") + @mock.patch.object(test_module, "BaseConnectionManager") async def test_mediation_invitation_should_not_create_connection_for_old_invitation( self, patched_connection_manager, patched_invite_store ): @@ -1638,44 +1485,6 @@ async def test_mediation_invitation_should_not_create_connection_for_old_invitat ) connection_manager_mock.receive_invitation.assert_not_called() - @mock.patch.object( - test_module, - "MediationInviteStore", - return_value=get_invite_store_mock("test-invite"), - ) - async def test_mediator_invitation_x(self, _): - conductor = test_module.Conductor(self.__get_mediator_config("test-invite", True)) - with mock.patch.object( - test_module, "OutboundTransportManager", autospec=True - ) as mock_outbound_mgr: - mock_outbound_mgr.return_value.registered_transports = { - "test": mock.MagicMock(schemes=["http"]) - } - await conductor.setup() - - with mock.patch.object( - test_module.ConnectionInvitation, - "from_url", - mock.MagicMock(side_effect=Exception()), - ) as mock_from_url, mock.patch.object( - test_module, "LOGGER" - ) as mock_logger, mock.patch.object( - test_module, "upgrade_wallet_to_anoncreds_if_requested", return_value=False - ) as mock_upgrade, mock.patch.object( - BaseStorage, - "find_record", - mock.CoroutineMock( - side_effect=[ - mock.MagicMock(value="askar"), - mock.MagicMock(value=f"v{__version__}"), - ] - ), - ): - await conductor.start() - await conductor.stop() - mock_from_url.assert_called_once_with("test-invite") - mock_logger.exception.assert_called_once() - async def test_setup_ledger_both_multiple_and_base(self): builder: ContextBuilder = StubContextBuilder(self.test_settings) builder.update_settings({"ledger.genesis_transactions": "..."}) diff --git a/aries_cloudagent/core/tests/test_oob_processor.py b/aries_cloudagent/core/tests/test_oob_processor.py index ef22fb2616..1469cf9560 100644 --- a/aries_cloudagent/core/tests/test_oob_processor.py +++ b/aries_cloudagent/core/tests/test_oob_processor.py @@ -8,9 +8,6 @@ from ...messaging.decorators.attach_decorator import AttachDecorator from ...messaging.decorators.service_decorator import ServiceDecorator from ...messaging.request_context import RequestContext -from ...protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) from ...protocols.out_of_band.v1_0.messages.invitation import InvitationMessage from ...protocols.out_of_band.v1_0.models.oob_record import OobRecord from ...storage.error import StorageNotFoundError @@ -41,7 +38,7 @@ async def asyncSetUp(self): save=mock.CoroutineMock(), ) self.context = RequestContext.test_context() - self.context.message = ConnectionInvitation() + self.context.message = InvitationMessage() async def test_clean_finished_oob_record_no_multi_use_no_request_attach(self): test_message = InvitationMessage() diff --git a/aries_cloudagent/protocols/connections/__init__.py b/aries_cloudagent/protocols/connections/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/definition.py b/aries_cloudagent/protocols/connections/definition.py deleted file mode 100644 index 62bddef6f5..0000000000 --- a/aries_cloudagent/protocols/connections/definition.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Version definitions for this protocol.""" - -versions = [ - { - "major_version": 1, - "minimum_minor_version": 0, - "current_minor_version": 0, - "path": "v1_0", - } -] diff --git a/aries_cloudagent/protocols/connections/v1_0/__init__.py b/aries_cloudagent/protocols/connections/v1_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/__init__.py b/aries_cloudagent/protocols/connections/v1_0/handlers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py deleted file mode 100644 index 124e9130e3..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Connect invitation handler.""" - -from .....messaging.base_handler import BaseHandler, BaseResponder, RequestContext -from ..messages.connection_invitation import ConnectionInvitation -from ..messages.problem_report import ConnectionProblemReport, ProblemReportReason - - -class ConnectionInvitationHandler(BaseHandler): - """Handler class for connection invitations.""" - - async def handle(self, context: RequestContext, responder: BaseResponder): - """Handle connection invitation. - - Args: - context: Request context - responder: Responder callback - """ - - self._logger.debug(f"ConnectionInvitationHandler called with context {context}") - assert isinstance(context.message, ConnectionInvitation) - - report = ConnectionProblemReport( - description={ - "code": ProblemReportReason.INVITATION_NOT_ACCEPTED.value, - "en": ("Connection invitations cannot be submitted via agent messaging"), - } - ) - report.assign_thread_from(context.message) - # client likely needs to be using direct responses to receive the problem report - await responder.send_reply(report) 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 deleted file mode 100644 index 042759f225..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Connection request handler.""" - -from .....connections.models.conn_record import ConnRecord -from .....messaging.base_handler import BaseHandler, BaseResponder, RequestContext -from ....coordinate_mediation.v1_0.manager import MediationManager -from ..manager import ConnectionManager, ConnectionManagerError -from ..messages.connection_request import ConnectionRequest - - -class ConnectionRequestHandler(BaseHandler): - """Handler class for connection requests.""" - - async def handle(self, context: RequestContext, responder: BaseResponder): - """Handle connection request. - - Args: - context: Request context - responder: Responder callback - """ - - self._logger.debug(f"ConnectionRequestHandler called with context {context}") - assert isinstance(context.message, ConnectionRequest) - - profile = context.profile - mgr = ConnectionManager(profile) - - mediation_id = None - if context.connection_record: - async with profile.session() as session: - mediation_metadata = await context.connection_record.metadata_get( - session, MediationManager.METADATA_KEY, {} - ) - mediation_id = mediation_metadata.get(MediationManager.METADATA_ID) - - try: - connection = await mgr.receive_request( - context.message, - context.message_receipt, - ) - - if connection.accept == ConnRecord.ACCEPT_AUTO: - response = await mgr.create_response( - connection, mediation_id=mediation_id - ) - await responder.send_reply( - response, connection_id=connection.connection_id - ) - else: - self._logger.debug("Connection request will await acceptance") - except ConnectionManagerError as e: - report, targets = mgr.manager_error_to_problem_report( - e, context.message, context.message_receipt - ) - if report and targets: - await responder.send_reply( - message=report, - target_list=targets, - ) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py deleted file mode 100644 index 61fd814362..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Connection response handler.""" - -from .....messaging.base_handler import BaseHandler, BaseResponder, RequestContext -from .....protocols.trustping.v1_0.messages.ping import Ping -from ..manager import ConnectionManager, ConnectionManagerError -from ..messages.connection_response import ConnectionResponse - - -class ConnectionResponseHandler(BaseHandler): - """Handler class for connection responses.""" - - async def handle(self, context: RequestContext, responder: BaseResponder): - """Handle connection response. - - Args: - context: Request context - responder: Responder callback - """ - self._logger.debug(f"ConnectionResponseHandler called with context {context}") - assert isinstance(context.message, ConnectionResponse) - - profile = context.profile - mgr = ConnectionManager(profile) - try: - connection = await mgr.accept_response( - context.message, context.message_receipt - ) - except ConnectionManagerError as e: - report, targets = mgr.manager_error_to_problem_report( - e, context.message, context.message_receipt - ) - if report and targets: - await responder.send_reply( - message=report, - target_list=targets, - ) - return - - # send trust ping in response - if context.settings.get("auto_ping_connection"): - (await responder.send(Ping(), connection_id=connection.connection_id),) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py deleted file mode 100644 index 8be8ec31fd..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Problem report handler for Connection Protocol.""" - -from .....connections.models.conn_record import ConnRecord -from .....messaging.base_handler import ( - BaseHandler, - BaseResponder, - HandlerException, - RequestContext, -) -from .....storage.error import StorageNotFoundError -from ..manager import ConnectionManager, ConnectionManagerError -from ..messages.problem_report import ConnectionProblemReport - - -class ConnectionProblemReportHandler(BaseHandler): - """Handler class for Connection problem report messages.""" - - async def handle(self, context: RequestContext, responder: BaseResponder): - """Handle problem report message.""" - self._logger.debug( - f"ConnectionProblemReportHandler called with context {context}" - ) - assert isinstance(context.message, ConnectionProblemReport) - - self._logger.info(f"Received problem report: {context.message.problem_code}") - profile = context.profile - mgr = ConnectionManager(profile) - try: - conn_rec = context.connection_record - if not conn_rec: - # try to find connection by thread_id/request_id - try: - async with profile.session() as session: - conn_rec = await ConnRecord.retrieve_by_request_id( - session, context.message._thread_id - ) - except StorageNotFoundError: - pass - - if conn_rec: - await mgr.receive_problem_report(conn_rec, context.message) - else: - raise HandlerException("No connection established for problem report") - except ConnectionManagerError: - # Unrecognized problem report code - self._logger.exception("Error receiving Connection problem report") diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/__init__.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py deleted file mode 100644 index d35c7c3be1..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest - -from ......messaging.request_context import RequestContext -from ......messaging.responder import MockResponder -from ......transport.inbound.receipt import MessageReceipt -from ...handlers.connection_invitation_handler import ConnectionInvitationHandler -from ...messages.connection_invitation import ConnectionInvitation -from ...messages.problem_report import ConnectionProblemReport, ProblemReportReason - - -@pytest.fixture() -def request_context() -> RequestContext: - ctx = RequestContext.test_context() - ctx.message_receipt = MessageReceipt() - yield ctx - - -class TestInvitationHandler: - @pytest.mark.asyncio - async def test_problem_report(self, request_context): - request_context.message = ConnectionInvitation() - handler = ConnectionInvitationHandler() - responder = MockResponder() - await handler.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, ConnectionProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.INVITATION_NOT_ACCEPTED.value - ) - ) - assert not target 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 deleted file mode 100644 index f1ede14976..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py +++ /dev/null @@ -1,274 +0,0 @@ -import pytest - -from aries_cloudagent.tests import mock - -from ......connections.models import connection_target -from ......connections.models.conn_record import ConnRecord -from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from ......core.profile import ProfileSession -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 ...handlers import connection_request_handler as handler -from ...manager import ConnectionManagerError -from ...messages.connection_request import ConnectionRequest -from ...messages.problem_report import ConnectionProblemReport, ProblemReportReason -from ...models.connection_detail import ConnectionDetail - - -@pytest.fixture() -async def request_context() -> RequestContext: - ctx = RequestContext.test_context() - ctx.message_receipt = MessageReceipt() - yield ctx - - -@pytest.fixture() -async def session(request_context) -> ProfileSession: - yield await request_context.session() - - -@pytest.fixture() -async def connection_record(request_context, session) -> ConnRecord: - record = ConnRecord() - request_context.connection_record = record - await record.save(session) - yield record - - -TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" -TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" -TEST_LABEL = "Label" -TEST_ENDPOINT = "http://localhost" -TEST_IMAGE_URL = "http://aries.ca/images/sample.png" - - -@pytest.fixture() -def did_doc(): - doc = DIDDoc(did=TEST_DID) - controller = TEST_DID - ident = "1" - pk_value = TEST_VERKEY - pk = PublicKey( - TEST_DID, - ident, - pk_value, - PublicKeyType.ED25519_SIG_2018, - controller, - False, - ) - doc.set(pk) - recip_keys = [pk] - router_keys = [] - service = Service( - TEST_DID, - "indy", - "IndyAgent", - recip_keys, - router_keys, - TEST_ENDPOINT, - ) - doc.set(service) - yield doc - - -class TestRequestHandler: - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_called(self, mock_conn_mgr, request_context): - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock() - request_context.message = ConnectionRequest() - 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 - ) - assert not responder.messages - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_called_with_auto_response(self, mock_conn_mgr, request_context): - mock_conn_rec = mock.MagicMock() - mock_conn_rec.accept = ConnRecord.ACCEPT_AUTO - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock( - return_value=mock_conn_rec - ) - mock_conn_mgr.return_value.create_response = mock.CoroutineMock() - request_context.message = ConnectionRequest() - 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 - ) - mock_conn_mgr.return_value.create_response.assert_called_once_with( - mock_conn_rec, mediation_id=None - ) - assert responder.messages - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_connection_record_with_mediation_metadata_auto_response( - self, mock_conn_mgr, request_context, connection_record - ): - mock_conn_rec = mock.MagicMock() - mock_conn_rec.accept = ConnRecord.ACCEPT_AUTO - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock( - return_value=mock_conn_rec - ) - mock_conn_mgr.return_value.create_response = mock.CoroutineMock() - request_context.message = ConnectionRequest() - with mock.patch.object( - connection_record, - "metadata_get", - mock.CoroutineMock(return_value={"id": "test-mediation-id"}), - ): - handler_inst = handler.ConnectionRequestHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - 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 responder.messages - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_connection_record_without_mediation_metadata( - self, mock_conn_mgr, request_context, session, connection_record - ): - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock() - request_context.message = ConnectionRequest() - storage: BaseStorage = session.inject(BaseStorage) - with mock.patch.object( - storage, - "find_record", - mock.CoroutineMock(side_effect=StorageNotFoundError), - ) as mock_storage_find_record: - 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, - ) - assert not responder.messages - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report(self, mock_conn_target, mock_conn_mgr, request_context): - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock() - mock_conn_mgr.return_value.receive_request.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, - } - ), - [mock_conn_target], - ) - ) - request_context.message = ConnectionRequest() - handler_inst = handler.ConnectionRequestHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, ConnectionProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": [mock_conn_target]} - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report_did_doc( - self, mock_conn_target, mock_conn_mgr, request_context, did_doc - ): - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock() - mock_conn_mgr.return_value.receive_request.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - mock_conn_mgr.return_value.diddoc_connection_targets = mock.MagicMock( - return_value=[mock_conn_target] - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, - } - ), - [mock_conn_target], - ) - ) - request_context.message = ConnectionRequest( - connection=ConnectionDetail(did=TEST_DID, did_doc=did_doc), - label=TEST_LABEL, - image_url=TEST_IMAGE_URL, - ) - handler_inst = handler.ConnectionRequestHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, ConnectionProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": [mock_conn_target]} - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report_did_doc_no_conn_target( - self, mock_conn_target, mock_conn_mgr, request_context, did_doc - ): - mock_conn_mgr.return_value.receive_request = mock.CoroutineMock() - mock_conn_mgr.return_value.receive_request.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - mock_conn_mgr.return_value.diddoc_connection_targets = mock.MagicMock( - side_effect=ConnectionManagerError("no targets") - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, - } - ), - None, - ) - ) - request_context.message = ConnectionRequest( - connection=ConnectionDetail(did=TEST_DID, did_doc=did_doc), - label=TEST_LABEL, - image_url=TEST_IMAGE_URL, - ) - handler_inst = handler.ConnectionRequestHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 0 # messages require a target! diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py deleted file mode 100644 index 7873613129..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py +++ /dev/null @@ -1,202 +0,0 @@ -import pytest - -from aries_cloudagent.tests import mock - -from ......connections.models import connection_target -from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from ......messaging.request_context import RequestContext -from ......messaging.responder import MockResponder -from ......protocols.trustping.v1_0.messages.ping import Ping -from ......transport.inbound.receipt import MessageReceipt -from ...handlers import connection_response_handler as handler -from ...manager import ConnectionManagerError -from ...messages.connection_response import ConnectionResponse -from ...messages.problem_report import ConnectionProblemReport, ProblemReportReason -from ...models.connection_detail import ConnectionDetail - - -@pytest.fixture() -def request_context() -> RequestContext: - ctx = RequestContext.test_context() - ctx.message_receipt = MessageReceipt() - yield ctx - - -TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" -TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" -TEST_LABEL = "Label" -TEST_ENDPOINT = "http://localhost" -TEST_IMAGE_URL = "http://aries.ca/images/sample.png" - - -@pytest.fixture() -def did_doc(): - doc = DIDDoc(did=TEST_DID) - controller = TEST_DID - ident = "1" - pk_value = TEST_VERKEY - pk = PublicKey( - TEST_DID, - ident, - pk_value, - PublicKeyType.ED25519_SIG_2018, - controller, - False, - ) - doc.set(pk) - recip_keys = [pk] - router_keys = [] - service = Service( - TEST_DID, - "indy", - "IndyAgent", - recip_keys, - router_keys, - TEST_ENDPOINT, - ) - doc.set(service) - yield doc - - -class TestResponseHandler: - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_called(self, mock_conn_mgr, request_context): - mock_conn_mgr.return_value.accept_response = mock.CoroutineMock() - request_context.message = ConnectionResponse() - handler_inst = handler.ConnectionResponseHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - mock_conn_mgr.return_value.accept_response.assert_called_once_with( - request_context.message, request_context.message_receipt - ) - assert not responder.messages - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - async def test_called_auto_ping(self, mock_conn_mgr, request_context): - request_context.update_settings({"auto_ping_connection": True}) - mock_conn_mgr.return_value.accept_response = mock.CoroutineMock() - request_context.message = ConnectionResponse() - handler_inst = handler.ConnectionResponseHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - mock_conn_mgr.return_value.accept_response.assert_called_once_with( - request_context.message, request_context.message_receipt - ) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert isinstance(result, Ping) - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report(self, mock_conn_target, mock_conn_mgr, request_context): - mock_conn_mgr.return_value.accept_response = mock.CoroutineMock() - mock_conn_mgr.return_value.accept_response.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - } - ), - [mock_conn_target], - ) - ) - request_context.message = ConnectionResponse() - handler_inst = handler.ConnectionResponseHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, ConnectionProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.RESPONSE_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": [mock_conn_target]} - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report_did_doc( - self, mock_conn_target, mock_conn_mgr, request_context, did_doc - ): - mock_conn_mgr.return_value.accept_response = mock.CoroutineMock() - mock_conn_mgr.return_value.accept_response.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - ) - mock_conn_mgr.return_value.diddoc_connection_targets = mock.MagicMock( - return_value=[mock_conn_target] - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - } - ), - [mock_conn_target], - ) - ) - request_context.message = ConnectionResponse( - connection=ConnectionDetail(did=TEST_DID, did_doc=did_doc) - ) - handler_inst = handler.ConnectionResponseHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, ConnectionProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.RESPONSE_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": [mock_conn_target]} - - @pytest.mark.asyncio - @mock.patch.object(handler, "ConnectionManager") - @mock.patch.object(connection_target, "ConnectionTarget") - async def test_problem_report_did_doc_no_conn_target( - self, mock_conn_target, mock_conn_mgr, request_context, did_doc - ): - mock_conn_mgr.return_value.accept_response = mock.CoroutineMock() - mock_conn_mgr.return_value.accept_response.side_effect = ConnectionManagerError( - error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - ) - mock_conn_mgr.return_value.diddoc_connection_targets = mock.MagicMock( - side_effect=ConnectionManagerError("no target") - ) - mock_conn_mgr.return_value.manager_error_to_problem_report = mock.MagicMock( - return_value=( - ConnectionProblemReport( - description={ - "en": "test error", - "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - } - ), - None, - ) - ) - request_context.message = ConnectionResponse( - connection=ConnectionDetail(did=TEST_DID, did_doc=did_doc) - ) - handler_inst = handler.ConnectionResponseHandler() - responder = MockResponder() - await handler_inst.handle(request_context, responder) - messages = responder.messages - assert len(messages) == 0 # need a connection target to send message diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py deleted file mode 100644 index 9d7c0bf86d..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ /dev/null @@ -1,843 +0,0 @@ -"""Classes to manage connections.""" - -import logging -import warnings -from typing import Optional, Sequence, Tuple, Union, cast - -from ....connections.base_manager import BaseConnectionManager -from ....connections.models.conn_record import ConnRecord -from ....connections.models.connection_target import ConnectionTarget -from ....core.error import BaseError -from ....core.oob_processor import OobMessageProcessor -from ....core.profile import Profile -from ....messaging.responder import BaseResponder -from ....messaging.valid import IndyDID -from ....storage.error import StorageNotFoundError -from ....transport.inbound.receipt import MessageReceipt -from ....wallet.base import BaseWallet -from ....wallet.did_method import SOV -from ....wallet.key_type import ED25519 -from ...coordinate_mediation.v1_0.manager import MediationManager -from .message_types import ARIES_PROTOCOL as CONN_PROTO -from .messages.connection_invitation import ConnectionInvitation -from .messages.connection_request import ConnectionRequest -from .messages.connection_response import ConnectionResponse -from .messages.problem_report import ConnectionProblemReport, ProblemReportReason -from .models.connection_detail import ConnectionDetail - - -class ConnectionManagerError(BaseError): - """Connection error.""" - - -class ConnectionManager(BaseConnectionManager): - """Class for managing connections.""" - - def __init__(self, profile: Profile): - """Initialize a ConnectionManager. - - Args: - profile: The profile for this connection manager - """ - self._profile = profile - self._logger = logging.getLogger(__name__) - super().__init__(self._profile) - - @property - def profile(self) -> Profile: - """Accessor for the current profile. - - Returns: - The profile for this connection manager - - """ - return self._profile - - def deprecation_warning(self): - """Log a deprecation warning.""" - warnings.warn( - "Aries RFC 0160: Connection Protocol is deprecated and support will be " - "removed in a future version; use RFC 0023: DID Exchange instead.", - DeprecationWarning, - ) - self._logger.warning( - "Aries RFC 0160: Connection Protocol is deprecated and support will be " - "removed in a future version; use RFC 0023: DID Exchange instead." - ) - - async def create_invitation( - self, - my_label: Optional[str] = None, - my_endpoint: Optional[str] = None, - auto_accept: Optional[bool] = None, - public: bool = False, - multi_use: bool = False, - alias: Optional[str] = None, - routing_keys: Optional[Sequence[str]] = None, - recipient_keys: Optional[Sequence[str]] = None, - metadata: Optional[dict] = None, - mediation_id: Optional[str] = None, - ) -> Tuple[ConnRecord, ConnectionInvitation]: - """Generate new connection invitation. - - This interaction represents an out-of-band communication channel. In the future - and in practice, these sort of invitations will be received over any number of - channels such as SMS, Email, QR Code, NFC, etc. - - Structure of an invite message: - - :: - - { - "@type": "https://didcomm.org/connections/1.0/invitation", - "label": "Alice", - "did": "did:sov:QmWbsNYhMrjHiqZDTUTEJs" - } - - Or, in the case of a peer DID: - - :: - - { - "@type": "https://didcomm.org/connections/1.0/invitation", - "label": "Alice", - "did": "did:peer:oiSqsNYhMrjHiqZDTUthsw", - "recipient_keys": ["8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K"], - "service_endpoint": "https://example.com/endpoint" - "routing_keys": ["9EH5gYEeNc3z7PYXmd53d5x6qAfCNrqQqEB4nS7Zfu6K"], - } - - Args: - my_label: label for this connection - my_endpoint: endpoint where other party can reach me - auto_accept: auto-accept a corresponding connection request - (None to use config) - public: set to create an invitation from the public DID - multi_use: set to True to create an invitation for multiple use - alias: optional alias to apply to connection for later use - routing_keys: optional list of routing keys for the invitation - recipient_keys: optional list of recipient keys for the invitation - metadata: optional metadata to include in the connection record - mediation_id: optional mediation ID for the connection - - Returns: - A tuple of the new `ConnRecord` and `ConnectionInvitation` instances - - Raises: - ConnectionManagerError: if public invitations are not enabled or - no public DID is available - - """ - self.deprecation_warning() - # 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, - ) - image_url = self.profile.context.settings.get("image_url") - invitation = None - connection = None - - invitation_mode = ConnRecord.INVITATION_MODE_ONCE - if multi_use: - invitation_mode = ConnRecord.INVITATION_MODE_MULTI - - if not my_label: - my_label = self.profile.settings.get("default_label") - - accept = ( - ConnRecord.ACCEPT_AUTO - if ( - auto_accept - or ( - auto_accept is None - and self.profile.settings.get("debug.auto_accept_requests") - ) - ) - else ConnRecord.ACCEPT_MANUAL - ) - - if recipient_keys: - # TODO: register recipient keys for relay - # TODO: check that recipient keys are in wallet - invitation_key = recipient_keys[0] # TODO first key appropriate? - else: - # Create and store new invitation key - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - invitation_signing_key = await wallet.create_signing_key(key_type=ED25519) - invitation_key = invitation_signing_key.verkey - recipient_keys = [invitation_key] - - if public: - if not self.profile.settings.get("public_invites"): - raise ConnectionManagerError("Public invitations are not enabled") - - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - public_did = await wallet.get_public_did() - if not public_did: - raise ConnectionManagerError( - "Cannot create public invitation with no public DID" - ) - - # FIXME - allow ledger instance to format public DID with prefix? - public_did_did = public_did.did - if bool(IndyDID.PATTERN.match(public_did_did)): - public_did_did = f"did:sov:{public_did.did}" - - invitation = ConnectionInvitation( - label=my_label, did=public_did_did, image_url=image_url - ) - - connection = ConnRecord( # create connection record - invitation_key=public_did.verkey, - invitation_msg_id=invitation._id, - invitation_mode=invitation_mode, - their_role=ConnRecord.Role.REQUESTER.rfc23, - state=ConnRecord.State.INVITATION.rfc23, - accept=accept, - alias=alias, - connection_protocol=CONN_PROTO, - ) - - async with self.profile.session() as session: - await connection.save(session, reason="Created new invitation") - - # Add mapping for multitenant relaying. - # Mediation of public keys is not supported yet - await self._route_manager.route_verkey(self.profile, public_did.verkey) - - else: - # Create connection record - connection = ConnRecord( - invitation_key=invitation_key, # TODO: determine correct key to use - their_role=ConnRecord.Role.REQUESTER.rfc160, - state=ConnRecord.State.INVITATION.rfc160, - accept=accept, - invitation_mode=invitation_mode, - alias=alias, - connection_protocol=CONN_PROTO, - ) - async with self.profile.session() as session: - await connection.save(session, reason="Created new invitation") - - await self._route_manager.route_invitation( - self.profile, connection, mediation_record - ) - routing_keys, routing_endpoint = await self._route_manager.routing_info( - self.profile, - mediation_record, - ) - my_endpoint = ( - routing_endpoint - or my_endpoint - or cast(str, self.profile.settings.get("default_endpoint")) - ) - - # Create connection invitation message - # Note: Need to split this into two stages - # to support inbound routing of invites - # Would want to reuse create_did_document and convert the result - invitation = ConnectionInvitation( - label=my_label, - recipient_keys=recipient_keys, - routing_keys=routing_keys, - endpoint=my_endpoint, - image_url=image_url, - ) - - async with self.profile.session() as session: - await connection.attach_invitation(session, invitation) - - if metadata: - for key, value in metadata.items(): - await connection.metadata_set(session, key, value) - - return connection, invitation - - async def receive_invitation( - self, - invitation: ConnectionInvitation, - their_public_did: Optional[str] = None, - auto_accept: Optional[bool] = None, - alias: Optional[str] = None, - mediation_id: Optional[str] = None, - ) -> ConnRecord: - """Create a new connection record to track a received invitation. - - Args: - invitation: The `ConnectionInvitation` to store - their_public_did: The public DID of the inviting party (optional) - auto_accept: Set to True to auto-accept the invitation, False to manually - accept, or None to use the default setting from the configuration - (optional) - alias: An optional alias to set on the connection record (optional) - mediation_id: The mediation ID to associate with the connection (optional) - - Returns: - The new `ConnRecord` instance representing the connection - - Raises: - ConnectionManagerError: If the invitation is missing recipient keys or an - endpoint - - """ - self.deprecation_warning() - if not invitation.did: - if not invitation.recipient_keys: - raise ConnectionManagerError( - "Invitation must contain recipient key(s)", - error_code=ProblemReportReason.MISSING_RECIPIENT_KEYS.value, - ) - if not invitation.endpoint: - raise ConnectionManagerError( - "Invitation must contain an endpoint", - error_code=ProblemReportReason.MISSING_ENDPOINT.value, - ) - accept = ( - ConnRecord.ACCEPT_AUTO - if ( - auto_accept - or ( - auto_accept is None - and self.profile.settings.get("debug.auto_accept_invites") - ) - ) - else ConnRecord.ACCEPT_MANUAL - ) - # Create connection record - connection = ConnRecord( - invitation_key=invitation.recipient_keys and invitation.recipient_keys[0], - their_label=invitation.label, - invitation_msg_id=invitation._id, - their_role=ConnRecord.Role.RESPONDER.rfc160, - state=ConnRecord.State.INVITATION.rfc160, - accept=accept, - alias=alias, - their_public_did=their_public_did, - connection_protocol=CONN_PROTO, - ) - - async with self.profile.session() as session: - await connection.save( - session, - reason="Created new connection record from invitation", - log_params={"invitation": invitation, "their_label": invitation.label}, - ) - - # Save the invitation for later processing - await connection.attach_invitation(session, invitation) - - await self._route_manager.save_mediator_for_connection( - self.profile, 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) - if responder: - await responder.send(request, connection_id=connection.connection_id) - # refetch connection for accurate state - async with self.profile.session() as session: - connection = await ConnRecord.retrieve_by_id( - session, connection.connection_id - ) - else: - self._logger.debug("Connection invitation will await acceptance") - return connection - - async def create_request( - self, - connection: ConnRecord, - my_label: Optional[str] = None, - my_endpoint: Optional[str] = None, - mediation_id: Optional[str] = None, - ) -> ConnectionRequest: - """Create a new connection request for a previously-received invitation. - - Args: - connection: The `ConnRecord` representing the invitation to accept - my_label: My label - my_endpoint: My endpoint - mediation_id: The record id for mediation - - Returns: - A new `ConnectionRequest` message to send to the other agent - - """ - self.deprecation_warning() - - mediation_records = await self._route_manager.mediation_records_for_connection( - self.profile, - connection, - mediation_id, - or_default=True, - ) - - if connection.my_did: - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.get_local_did(connection.my_did) - else: - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - # Create new DID for connection - my_info = await wallet.create_local_did(SOV, ED25519) - connection.my_did = my_info.did - - # Idempotent; if routing has already been set up, no action taken - await self._route_manager.route_connection_as_invitee( - self.profile, connection, mediation_records - ) - - # Create connection request message - if my_endpoint: - my_endpoints = [my_endpoint] - else: - my_endpoints = [] - default_endpoint = self.profile.settings.get("default_endpoint") - 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, - my_endpoints, - mediation_records=mediation_records, - ) - - if not my_label: - my_label = self.profile.settings.get("default_label") - request = ConnectionRequest( - label=my_label, - connection=ConnectionDetail(did=connection.my_did, did_doc=did_doc), - image_url=self.profile.settings.get("image_url"), - ) - request.assign_thread_id(thid=request._id, pthid=connection.invitation_msg_id) - - # Update connection state - connection.request_id = request._id - connection.state = ConnRecord.State.REQUEST.rfc160 - - async with self.profile.session() as session: - await connection.save(session, reason="Created connection request") - - return request - - async def receive_request( - self, - request: ConnectionRequest, - receipt: MessageReceipt, - ) -> ConnRecord: - """Receive and store a connection request. - - Args: - request: The `ConnectionRequest` to accept - receipt: The message receipt - - Returns: - The new or updated `ConnRecord` instance - - """ - self.deprecation_warning() - ConnRecord.log_state( - "Receiving connection request", - {"request": request}, - settings=self.profile.settings, - ) - - connection = None - connection_key = None - my_info = None - - # Determine what key will need to sign the response - if receipt.recipient_did_public: - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.get_local_did(receipt.recipient_did) - connection_key = my_info.verkey - else: - connection_key = receipt.recipient_verkey - try: - async with self.profile.session() as session: - connection = await ConnRecord.retrieve_by_invitation_key( - session=session, - invitation_key=connection_key, - their_role=ConnRecord.Role.REQUESTER.rfc160, - ) - except StorageNotFoundError: - raise ConnectionManagerError( - "No invitation found for pairwise connection " - f"in state {ConnRecord.State.INVITATION.rfc160}: " - "a prior connection request may have updated the connection state", - error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value, - ) - - invitation = None - if connection: - async with self.profile.session() as session: - invitation = await connection.retrieve_invitation(session) - connection_key = connection.invitation_key - ConnRecord.log_state( - "Found invitation", - {"invitation": invitation}, - settings=self.profile.settings, - ) - - if connection.is_multiuse_invitation: - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.create_local_did(SOV, ED25519) - - new_connection = ConnRecord( - invitation_key=connection_key, - my_did=my_info.did, - state=ConnRecord.State.REQUEST.rfc160, - accept=connection.accept, - their_role=connection.their_role, - connection_protocol=CONN_PROTO, - ) - async with self.profile.session() as session: - await new_connection.save( - session, - reason=( - "Received connection request from multi-use invitation DID" - ), - event=False, - ) - - # Transfer metadata from multi-use to new connection - # Must come after save so there's an ID to associate with metadata - async with self.profile.session() as session: - for key, value in ( - await connection.metadata_get_all(session) - ).items(): - await new_connection.metadata_set(session, key, value) - - connection = new_connection - - conn_did_doc = request.connection.did_doc - if not conn_did_doc: - raise ConnectionManagerError( - "No DIDDoc provided; cannot connect to public DID", - ) - if request.connection.did != conn_did_doc.did: - raise ConnectionManagerError( - "Connection DID does not match DIDDoc id", - error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value, - ) - await self.store_did_document(conn_did_doc) - - if connection: - connection.their_label = request.label - connection.their_did = request.connection.did - connection.state = ConnRecord.State.REQUEST.rfc160 - async with self.profile.session() as session: - # force emitting event that would be ignored for multi-use invitations - # since the record is not new, and the state was not updated - await connection.save( - session, - reason="Received connection request from invitation", - event=True, - ) - elif not self.profile.settings.get("public_invites"): - raise ConnectionManagerError("Public invitations are not enabled") - else: # request from public did - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.create_local_did(SOV, ED25519) - - async with self.profile.session() as session: - connection = await ConnRecord.retrieve_by_invitation_msg_id( - session=session, - invitation_msg_id=request._thread.pthid, - their_role=ConnRecord.Role.REQUESTER.rfc160, - ) - if not connection: - if not self.profile.settings.get("requests_through_public_did"): - raise ConnectionManagerError( - "Unsolicited connection requests to " "public DID is not enabled" - ) - connection = ConnRecord() - connection.invitation_key = connection_key - connection.my_did = my_info.did - connection.their_role = ConnRecord.Role.RESPONDER.rfc160 - connection.their_did = request.connection.did - connection.their_label = request.label - connection.accept = ( - ConnRecord.ACCEPT_AUTO - if self.profile.settings.get("debug.auto_accept_requests") - else ConnRecord.ACCEPT_MANUAL - ) - connection.state = ConnRecord.State.REQUEST.rfc160 - connection.connection_protocol = CONN_PROTO - async with self.profile.session() as session: - await connection.save( - session, reason="Received connection request from public DID" - ) - - async with self.profile.session() as session: - # Attach the connection request so it can be found and responded to - await connection.attach_request(session, request) - - # Clean associated oob record if not needed anymore - oob_processor = self.profile.inject(OobMessageProcessor) - await oob_processor.clean_finished_oob_record(self.profile, request) - - return connection - - async def create_response( - self, - connection: ConnRecord, - my_endpoint: Optional[str] = None, - mediation_id: Optional[str] = None, - ) -> ConnectionResponse: - """Create a connection response for a received connection request. - - Args: - connection: The `ConnRecord` with a pending connection request - my_endpoint: The endpoint I can be reached at - mediation_id: The record id for mediation that contains routing_keys and - service endpoint - Returns: - A tuple of the updated `ConnRecord` new `ConnectionResponse` message - - """ - self.deprecation_warning() - ConnRecord.log_state( - "Creating connection response", - {"connection_id": connection.connection_id}, - settings=self.profile.settings, - ) - - mediation_records = await self._route_manager.mediation_records_for_connection( - self.profile, connection, mediation_id - ) - - if ConnRecord.State.get(connection.state) not in ( - ConnRecord.State.REQUEST, - ConnRecord.State.RESPONSE, - ): - raise ConnectionManagerError( - "Connection is not in the request or response state" - ) - - 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) - my_info = await wallet.get_local_did(connection.my_did) - else: - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - my_info = await wallet.create_local_did(SOV, ED25519) - connection.my_did = my_info.did - - # Idempotent; if routing has already been set up, no action taken - await self._route_manager.route_connection_as_inviter( - self.profile, connection, mediation_records - ) - - # Create connection response message - if my_endpoint: - my_endpoints = [my_endpoint] - else: - my_endpoints = [] - default_endpoint = self.profile.settings.get("default_endpoint") - 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, - my_endpoints, - mediation_records=mediation_records, - ) - - response = ConnectionResponse( - connection=ConnectionDetail(did=my_info.did, did_doc=did_doc) - ) - - # Assign thread information - response.assign_thread_from(request) - response.assign_trace_from(request) - # Sign connection field using the invitation key - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - await response.sign_field("connection", connection.invitation_key, wallet) - - # Update connection state - connection.state = ConnRecord.State.RESPONSE.rfc160 - - await connection.save( - session, - reason="Created connection response", - log_params={"response": response}, - ) - - # TODO It's possible the mediation request sent here might arrive - # before the connection response. This would result in an error condition - # difficult to accommodate for without modifying handlers for trust ping - # to ensure the connection is active. - async with self.profile.session() as session: - send_mediation_request = await connection.metadata_get( - session, MediationManager.SEND_REQ_AFTER_CONNECTION - ) - if send_mediation_request: - mgr = MediationManager(self.profile) - _record, request = await mgr.prepare_request(connection.connection_id) - responder = self.profile.inject(BaseResponder) - await responder.send(request, connection_id=connection.connection_id) - - return response - - async def accept_response( - self, response: ConnectionResponse, receipt: MessageReceipt - ) -> ConnRecord: - """Accept a connection response. - - Process a ConnectionResponse message by looking up - the connection request and setting up the pairwise connection. - - Args: - response: The `ConnectionResponse` to accept - receipt: The message receipt - - Returns: - The updated `ConnRecord` representing the connection - - Raises: - ConnectionManagerError: If there is no DID associated with the - connection response - ConnectionManagerError: If the corresponding connection is not - at the request or response stage - - """ - self.deprecation_warning() - connection = None - if response._thread: - # identify the request by the thread ID - try: - async with self.profile.session() as session: - connection = await ConnRecord.retrieve_by_request_id( - session, response._thread_id - ) - except StorageNotFoundError: - pass - - if not connection and receipt.sender_did: - # identify connection by the DID they used for us - try: - async with self.profile.session() as session: - connection = await ConnRecord.retrieve_by_did( - session, receipt.sender_did, receipt.recipient_did - ) - except StorageNotFoundError: - pass - - if not connection: - raise ConnectionManagerError( - "No corresponding connection request found", - error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, - ) - - if ConnRecord.State.get(connection.state) not in ( - ConnRecord.State.REQUEST, - ConnRecord.State.RESPONSE, - ): - raise ConnectionManagerError( - f"Cannot accept connection response for connection" - f" in state: {connection.state}" - ) - - their_did = response.connection.did - conn_did_doc = response.connection.did_doc - if not conn_did_doc: - raise ConnectionManagerError( - "No DIDDoc provided; cannot connect to public DID" - ) - if their_did != conn_did_doc.did: - raise ConnectionManagerError("Connection DID does not match DIDDoc id") - # Verify connection response using connection field - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - try: - await response.verify_signed_field( - "connection", wallet, connection.invitation_key - ) - except ValueError: - raise ConnectionManagerError( - "connection field verification using invitation_key failed" - ) - await self.store_did_document(conn_did_doc) - - connection.their_did = their_did - connection.state = ConnRecord.State.RESPONSE.rfc160 - async with self.profile.session() as session: - await connection.save(session, reason="Accepted connection response") - - send_mediation_request = await connection.metadata_get( - session, MediationManager.SEND_REQ_AFTER_CONNECTION - ) - if send_mediation_request: - mgr = MediationManager(self.profile) - _record, request = await mgr.prepare_request(connection.connection_id) - responder = self.profile.inject(BaseResponder) - await responder.send(request, connection_id=connection.connection_id) - - return connection - - async def receive_problem_report( - self, - conn_rec: ConnRecord, - report: ConnectionProblemReport, - ): - """Receive problem report.""" - self.deprecation_warning() - if not report.description: - raise ConnectionManagerError("Missing description in problem report") - - if report.description.get("code") in { - reason.value for reason in ProblemReportReason - }: - self._logger.info("Problem report indicates connection is abandoned") - async with self.profile.session() as session: - await conn_rec.abandon( - session, - reason=report.description.get("en"), - ) - else: - raise ConnectionManagerError( - f"Received unrecognized problem report: {report.description}" - ) - - def manager_error_to_problem_report( - self, - e: ConnectionManagerError, - message: Union[ConnectionRequest, ConnectionResponse], - message_receipt, - ) -> tuple[ConnectionProblemReport, Sequence[ConnectionTarget]]: - """Convert ConnectionManagerError to problem report.""" - self._logger.exception("Error receiving connection request") - targets = None - report = None - if e.error_code: - report = ConnectionProblemReport( - description={"en": e.message, "code": e.error_code} - ) - report.assign_thread_from(message) - if message.connection and message.connection.did_doc: - try: - targets = self.diddoc_connection_targets( - message.connection.did_doc, - message_receipt.recipient_verkey, - ) - except ConnectionManagerError: - self._logger.exception("Error parsing DIDDoc for problem report") - - return report, targets diff --git a/aries_cloudagent/protocols/connections/v1_0/message_types.py b/aries_cloudagent/protocols/connections/v1_0/message_types.py deleted file mode 100644 index 5095e2f15a..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/message_types.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Message type identifiers for Connections.""" - -from ...didcomm_prefix import DIDCommPrefix - -SPEC_URI = ( - "https://github.com/hyperledger/aries-rfcs/tree/" - "9b0aaa39df7e8bd434126c4b33c097aae78d65bf/features/0160-connection-protocol" -) -ARIES_PROTOCOL = "connections/1.0" - -# Message types -CONNECTION_INVITATION = f"{ARIES_PROTOCOL}/invitation" -CONNECTION_REQUEST = f"{ARIES_PROTOCOL}/request" -CONNECTION_RESPONSE = f"{ARIES_PROTOCOL}/response" -PROBLEM_REPORT = f"{ARIES_PROTOCOL}/problem_report" - -PROTOCOL_PACKAGE = "aries_cloudagent.protocols.connections.v1_0" - -MESSAGE_TYPES = DIDCommPrefix.qualify_all( - { - CONNECTION_INVITATION: ( - f"{PROTOCOL_PACKAGE}.messages.connection_invitation.ConnectionInvitation" - ), - CONNECTION_REQUEST: ( - f"{PROTOCOL_PACKAGE}.messages.connection_request.ConnectionRequest" - ), - CONNECTION_RESPONSE: ( - f"{PROTOCOL_PACKAGE}.messages.connection_response.ConnectionResponse" - ), - PROBLEM_REPORT: ( - f"{PROTOCOL_PACKAGE}.messages.problem_report.ConnectionProblemReport" - ), - } -) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/__init__.py b/aries_cloudagent/protocols/connections/v1_0/messages/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/connection_invitation.py b/aries_cloudagent/protocols/connections/v1_0/messages/connection_invitation.py deleted file mode 100644 index d983eb0ede..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/connection_invitation.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Represents an invitation message for establishing connection.""" - -from typing import Optional, Sequence -from urllib.parse import parse_qs, urljoin, urlparse - -from marshmallow import EXCLUDE, ValidationError, fields, pre_load, validates_schema - -from .....did.did_key import DIDKey -from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from .....messaging.valid import ( - GENERIC_DID_EXAMPLE, - GENERIC_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, -) -from .....wallet.util import b64_to_bytes, bytes_to_b64 -from ..message_types import CONNECTION_INVITATION, PROTOCOL_PACKAGE - -HANDLER_CLASS = ( - f"{PROTOCOL_PACKAGE}.handlers" - ".connection_invitation_handler.ConnectionInvitationHandler" -) - - -class ConnectionInvitation(AgentMessage): - """Class representing a connection invitation.""" - - class Meta: - """Metadata for a connection invitation.""" - - handler_class = HANDLER_CLASS - message_type = CONNECTION_INVITATION - schema_class = "ConnectionInvitationSchema" - - def __init__( - self, - *, - label: Optional[str] = None, - did: Optional[str] = None, - recipient_keys: Sequence[str] = None, - endpoint: Optional[str] = None, - routing_keys: Sequence[str] = None, - image_url: Optional[str] = None, - **kwargs, - ): - """Initialize connection invitation object. - - Args: - label: Optional label for connection invitation - did: DID for this connection invitation - recipient_keys: List of recipient keys - endpoint: Endpoint which this agent can be reached at - routing_keys: List of routing keys - image_url: Optional image URL for connection invitation - kwargs: Additional keyword arguments for the message - """ - super().__init__(**kwargs) - self.label = label - self.did = did - self.recipient_keys = list(recipient_keys) if recipient_keys else None - self.endpoint = endpoint - self.routing_keys = list(routing_keys) if routing_keys else None - self.routing_keys = ( - [ - ( - DIDKey.from_did(key).public_key_b58 - if key.startswith("did:key:") - else key - ) - for key in self.routing_keys - ] - if self.routing_keys - else None - ) - self.image_url = image_url - - def to_url(self, base_url: Optional[str] = None) -> str: - """Convert an invitation to URL format for sharing. - - Returns: - An invite url - - """ - c_json = self.to_json() - c_i = bytes_to_b64(c_json.encode("ascii"), urlsafe=True, pad=False) - result = urljoin(base_url or self.endpoint or "", "?c_i={}".format(c_i)) - return result - - @classmethod - def from_url(cls, url: str) -> "ConnectionInvitation": - """Parse a URL-encoded invitation into a `ConnectionInvitation` message. - - Args: - url: Url to decode - - Returns: - A `ConnectionInvitation` object. - - """ - parts = urlparse(url) - query = parse_qs(parts.query) - if "c_i" in query: - c_i = b64_to_bytes(query["c_i"][0], urlsafe=True) - return cls.from_json(c_i) - return None - - -class ConnectionInvitationSchema(AgentMessageSchema): - """Connection invitation schema class.""" - - class Meta: - """Connection invitation schema metadata.""" - - model_class = ConnectionInvitation - unknown = EXCLUDE - - label = fields.Str( - required=False, - metadata={ - "description": "Optional label for connection invitation", - "example": "Bob", - }, - ) - did = fields.Str( - required=False, - validate=GENERIC_DID_VALIDATE, - metadata={ - "description": "DID for connection invitation", - "example": GENERIC_DID_EXAMPLE, - }, - ) - recipient_keys = fields.List( - fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, - }, - ), - data_key="recipientKeys", - required=False, - metadata={"description": "List of recipient keys"}, - ) - endpoint = fields.Str( - data_key="serviceEndpoint", - required=False, - metadata={ - "description": "Service endpoint at which to reach this agent", - "example": "http://192.168.56.101:8020", - }, - ) - routing_keys = fields.List( - fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, - }, - ), - data_key="routingKeys", - required=False, - metadata={"description": "List of routing keys"}, - ) - image_url = fields.URL( - data_key="imageUrl", - required=False, - allow_none=True, - metadata={ - "description": "Optional image URL for connection invitation", - "example": "http://192.168.56.101/img/logo.jpg", - }, - ) - - @pre_load - def transform_routing_keys(self, data, **kwargs): - """Transform routingKeys from did:key refs, if necessary.""" - routing_keys = data.get("routingKeys") - if routing_keys: - data["routingKeys"] = [ - ( - DIDKey.from_did(key).public_key_b58 - if key.startswith("did:key:") - else key - ) - for key in routing_keys - ] - return data - - @validates_schema - def validate_fields(self, data, **kwargs): - """Validate schema fields. - - Args: - data: The data to validate - kwargs: Additional keyword arguments - - Raises: - ValidationError: If any of the fields do not validate - - """ - if data.get("did"): - if data.get("recipient_keys"): - raise ValidationError("Fields are incompatible", ("did", "recipientKeys")) - if data.get("endpoint"): - raise ValidationError( - "Fields are incompatible", ("did", "serviceEndpoint") - ) - elif not data.get("recipient_keys") or not data.get("endpoint"): - raise ValidationError( - "Missing required field(s)", ("did", "recipientKeys", "serviceEndpoint") - ) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/connection_request.py b/aries_cloudagent/protocols/connections/v1_0/messages/connection_request.py deleted file mode 100644 index d1a6940be5..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/connection_request.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Represents a connection request message.""" - -from typing import Optional - -from marshmallow import EXCLUDE, fields - -from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from ..message_types import CONNECTION_REQUEST, PROTOCOL_PACKAGE -from ..models.connection_detail import ConnectionDetail, ConnectionDetailSchema - -HANDLER_CLASS = ( - f"{PROTOCOL_PACKAGE}.handlers.connection_request_handler.ConnectionRequestHandler" -) - - -class ConnectionRequest(AgentMessage): - """Class representing a connection request.""" - - class Meta: - """Metadata for a connection request.""" - - handler_class = HANDLER_CLASS - message_type = CONNECTION_REQUEST - schema_class = "ConnectionRequestSchema" - - def __init__( - self, - *, - connection: Optional[ConnectionDetail] = None, - label: Optional[str] = None, - image_url: Optional[str] = None, - **kwargs, - ): - """Initialize connection request object. - - Args: - connection (ConnectionDetail): Connection details object - label: Label for this connection request - image_url: Optional image URL for this connection request - kwargs: Additional keyword arguments for the message - - """ - super().__init__(**kwargs) - self.connection = connection - self.label = label - self.image_url = image_url - - -class ConnectionRequestSchema(AgentMessageSchema): - """Connection request schema class.""" - - class Meta: - """Connection request schema metadata.""" - - model_class = ConnectionRequest - unknown = EXCLUDE - - connection = fields.Nested(ConnectionDetailSchema, required=True) - label = fields.Str( - required=True, - metadata={ - "description": "Label for connection request", - "example": "Request to connect with Bob", - }, - ) - image_url = fields.Str( - data_key="imageUrl", - required=False, - allow_none=True, - metadata={ - "description": "Optional image URL for connection request", - "example": "http://192.168.56.101/img/logo.jpg", - }, - ) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/connection_response.py b/aries_cloudagent/protocols/connections/v1_0/messages/connection_response.py deleted file mode 100644 index 8bf11eae21..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/connection_response.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Represents a connection response message.""" - -from typing import Optional - -from marshmallow import EXCLUDE, fields - -from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from ..message_types import CONNECTION_RESPONSE, PROTOCOL_PACKAGE -from ..models.connection_detail import ConnectionDetail, ConnectionDetailSchema - -HANDLER_CLASS = ( - f"{PROTOCOL_PACKAGE}.handlers." - "connection_response_handler.ConnectionResponseHandler" -) - - -class ConnectionResponse(AgentMessage): - """Class representing a connection response.""" - - class Meta: - """Metadata for a connection response.""" - - handler_class = HANDLER_CLASS - schema_class = "ConnectionResponseSchema" - message_type = CONNECTION_RESPONSE - - def __init__(self, *, connection: Optional[ConnectionDetail] = None, **kwargs): - """Initialize connection response object. - - Args: - connection: Connection details object - kwargs: Additional keyword arguments for the message - - """ - super().__init__(**kwargs) - self.connection = connection - - -class ConnectionResponseSchema(AgentMessageSchema): - """Connection response schema class.""" - - class Meta: - """Connection response schema metadata.""" - - model_class = ConnectionResponse - signed_fields = ("connection",) - unknown = EXCLUDE - - connection = fields.Nested(ConnectionDetailSchema, required=True) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/problem_report.py b/aries_cloudagent/protocols/connections/v1_0/messages/problem_report.py deleted file mode 100644 index 2e8eebd923..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/problem_report.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Represents a connection problem report message.""" - -import logging -from enum import Enum -from typing import Optional - -from marshmallow import EXCLUDE, ValidationError, validates_schema - -from ....problem_report.v1_0.message import ProblemReport, ProblemReportSchema -from ..message_types import PROBLEM_REPORT - -HANDLER_CLASS = ( - "aries_cloudagent.protocols.connections.v1_0.handlers." - "problem_report_handler.ConnectionProblemReportHandler" -) - -LOGGER = logging.getLogger(__name__) - - -class ProblemReportReason(Enum): - """Supported reason codes.""" - - INVITATION_NOT_ACCEPTED = "invitation_not_accepted" - REQUEST_NOT_ACCEPTED = "request_not_accepted" - REQUEST_PROCESSING_ERROR = "request_processing_error" - RESPONSE_NOT_ACCEPTED = "response_not_accepted" - RESPONSE_PROCESSING_ERROR = "response_processing_error" - MISSING_RECIPIENT_KEYS = "invitation_missing_recipient_keys" - MISSING_ENDPOINT = "invitation_missing_endpoint" - - -class ConnectionProblemReport(ProblemReport): - """Base class representing a connection problem report message.""" - - class Meta: - """Connection problem report metadata.""" - - handler_class = HANDLER_CLASS - message_type = PROBLEM_REPORT - schema_class = "ConnectionProblemReportSchema" - - def __init__( - self, - *, - problem_code: Optional[str] = None, - explain: Optional[str] = None, - **kwargs, - ): - """Initialize a ProblemReport message instance. - - Args: - problem_code: The standard error identifier - explain: The localized error explanation - kwargs: Additional keyword arguments - """ - super().__init__(**kwargs) - self.explain = explain - self.problem_code = problem_code - - -class ConnectionProblemReportSchema(ProblemReportSchema): - """Schema for ConnectionProblemReport base class.""" - - class Meta: - """Metadata for connection problem report schema.""" - - model_class = ConnectionProblemReport - unknown = EXCLUDE - - @validates_schema - def validate_fields(self, data, **kwargs): - """Validate schema fields.""" - - if not data.get("description", {}).get("code", ""): - raise ValidationError("Value for description.code must be present") - elif data.get("description", {}).get("code", "") not in [ - prr.value for prr in ProblemReportReason - ]: - locales = list(data.get("description").keys()) - locales.remove("code") - LOGGER.warning( - "Unexpected error code received.\n" - f"Code: {data.get('description').get('code')}, " - f"Description: {data.get('description').get(locales[0])}" - ) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/__init__.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py deleted file mode 100644 index a957e81de5..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py +++ /dev/null @@ -1,118 +0,0 @@ -from unittest import TestCase, mock - -from ......messaging.models.base import BaseModelError -from .....didcomm_prefix import DIDCommPrefix -from ...message_types import CONNECTION_INVITATION -from ..connection_invitation import ConnectionInvitation - - -class TestConnectionInvitation(TestCase): - label = "Label" - did = "did:sov:QmWbsNYhMrjHiqZDTUTEJs" - endpoint_url = "https://example.com/endpoint" - endpoint_did = "did:sov:A2wBhNYhMrjHiqZDTUYH7u" - image_url = "https://example.com/image.jpg" - key = "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K" - - def test_init(self): - connection_invitation = ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url - ) - assert connection_invitation.label == self.label - assert connection_invitation.recipient_keys == [self.key] - assert connection_invitation.endpoint == self.endpoint_url - - connection_invitation = ConnectionInvitation(label=self.label, did=self.did) - assert connection_invitation.did == self.did - - def test_type(self): - connection_invitation = ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url - ) - - assert connection_invitation._type == DIDCommPrefix.qualify_current( - CONNECTION_INVITATION - ) - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_invitation.ConnectionInvitationSchema.load" - ) - def test_deserialize(self, mock_connection_invitation_schema_load): - obj = {"obj": "obj"} - - connection_invitation = ConnectionInvitation.deserialize(obj) - mock_connection_invitation_schema_load.assert_called_once_with(obj) - - assert ( - connection_invitation is mock_connection_invitation_schema_load.return_value - ) - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_invitation.ConnectionInvitationSchema.dump" - ) - def test_serialize(self, mock_connection_invitation_schema_dump): - connection_invitation = ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url - ) - - connection_invitation_dict = connection_invitation.serialize() - mock_connection_invitation_schema_dump.assert_called_once_with( - connection_invitation - ) - - assert ( - connection_invitation_dict - is mock_connection_invitation_schema_dump.return_value - ) - - def test_url_round_trip(self): - connection_invitation = ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url - ) - url = connection_invitation.to_url() - assert isinstance(url, str) - invitation = ConnectionInvitation.from_url(url) - assert isinstance(invitation, ConnectionInvitation) - - def test_from_no_url(self): - url = "http://aries.ca/no_ci" - assert ConnectionInvitation.from_url(url) is None - - -class TestConnectionInvitationSchema(TestCase): - connection_invitation = ConnectionInvitation( - label="label", did="did:sov:QmWbsNYhMrjHiqZDTUTEJs" - ) - - def test_make_model(self): - data = self.connection_invitation.serialize() - model_instance = ConnectionInvitation.deserialize(data) - assert isinstance(model_instance, ConnectionInvitation) - - def test_make_model_invalid(self): - x_conns = [ - ConnectionInvitation( - label="did-and-recip-keys", - did="did:sov:QmWbsNYhMrjHiqZDTUTEJs", - recipient_keys=["8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K"], - ), - ConnectionInvitation( - label="did-and-endpoint", - did="did:sov:QmWbsNYhMrjHiqZDTUTEJs", - endpoint="https://example.com/endpoint", - ), - ConnectionInvitation( - label="no-did-no-recip-keys", - endpoint="https://example.com/endpoint", - ), - ConnectionInvitation( - label="no-did-no-endpoint", - recipient_keys=["8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K"], - ), - ] - for x_conn in x_conns: - data = x_conn.serialize() - with self.assertRaises(BaseModelError): - ConnectionInvitation.deserialize(data) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_request.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_request.py deleted file mode 100644 index d120e769ea..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_request.py +++ /dev/null @@ -1,119 +0,0 @@ -from unittest import IsolatedAsyncioTestCase, TestCase, mock - -from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from .....didcomm_prefix import DIDCommPrefix -from ...message_types import CONNECTION_REQUEST -from ...models.connection_detail import ConnectionDetail -from ..connection_request import ConnectionRequest - - -class TestConfig: - test_seed = "testseed000000000000000000000001" - test_did = "55GkHamhTU1ZbTbV2ab9DE" - test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" - test_label = "Label" - test_endpoint = "http://localhost" - - def make_did_doc(self): - doc = DIDDoc(did=self.test_did) - controller = self.test_did - ident = "1" - pk_value = self.test_verkey - pk = PublicKey( - self.test_did, - ident, - pk_value, - PublicKeyType.ED25519_SIG_2018, - controller, - False, - ) - doc.set(pk) - recip_keys = [pk] - router_keys = [] - service = Service( - self.test_did, - "indy", - "IndyAgent", - recip_keys, - router_keys, - self.test_endpoint, - ) - doc.set(service) - return doc - - -class TestConnectionRequest(TestCase, TestConfig): - def setUp(self): - self.connection_request = ConnectionRequest( - connection=ConnectionDetail(did=self.test_did, did_doc=self.make_did_doc()), - label=self.test_label, - ) - - def test_init(self): - """Test initialization.""" - assert self.connection_request.label == self.test_label - assert self.connection_request.connection.did == self.test_did - # assert self.connection_request.verkey == self.verkey - - def test_type(self): - """Test type.""" - assert self.connection_request._type == DIDCommPrefix.qualify_current( - CONNECTION_REQUEST - ) - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_request.ConnectionRequestSchema.load" - ) - def test_deserialize(self, mock_connection_request_schema_load): - """ - Test deserialization. - """ - obj = {"obj": "obj"} - - connection_request = ConnectionRequest.deserialize(obj) - mock_connection_request_schema_load.assert_called_once_with(obj) - - assert connection_request is mock_connection_request_schema_load.return_value - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_request.ConnectionRequestSchema.dump" - ) - def test_serialize(self, mock_connection_request_schema_dump): - """ - Test serialization. - """ - connection_request_dict = self.connection_request.serialize() - mock_connection_request_schema_dump.assert_called_once_with( - self.connection_request - ) - - assert connection_request_dict is mock_connection_request_schema_dump.return_value - - -class TestConnectionRequestSchema(IsolatedAsyncioTestCase, TestConfig): - """Test connection request schema.""" - - async def test_make_model(self): - connection_request = ConnectionRequest( - connection=ConnectionDetail(did=self.test_did, did_doc=self.make_did_doc()), - label=self.test_label, - ) - data = connection_request.serialize() - model_instance = ConnectionRequest.deserialize(data) - assert type(model_instance) is type(connection_request) - - async def test_make_model_conn_detail_interpolate_authn_service(self): - did_doc_dict = self.make_did_doc().serialize() - del did_doc_dict["authentication"] - del did_doc_dict["service"] - did_doc = DIDDoc.deserialize(did_doc_dict) - - connection_request = ConnectionRequest( - connection=ConnectionDetail(did=self.test_did, did_doc=did_doc), - label=self.test_label, - ) - data = connection_request.serialize() - model_instance = ConnectionRequest.deserialize(data) - assert type(model_instance) is type(connection_request) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py deleted file mode 100644 index 1b1af0442b..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py +++ /dev/null @@ -1,104 +0,0 @@ -from unittest import IsolatedAsyncioTestCase, TestCase, mock - -from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from ......core.in_memory import InMemoryProfile -from ......wallet.key_type import ED25519 -from .....didcomm_prefix import DIDCommPrefix -from ...message_types import CONNECTION_RESPONSE -from ...models.connection_detail import ConnectionDetail -from ..connection_response import ConnectionResponse - - -class TestConfig: - test_seed = "testseed000000000000000000000001" - test_did = "55GkHamhTU1ZbTbV2ab9DE" - test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" - test_endpoint = "http://localhost" - - def make_did_doc(self): - doc = DIDDoc(did=self.test_did) - controller = self.test_did - ident = "1" - pk_value = self.test_verkey - pk = PublicKey( - self.test_did, - ident, - pk_value, - PublicKeyType.ED25519_SIG_2018, - controller, - False, - ) - doc.set(pk) - recip_keys = [pk] - routing_keys = [] - service = Service( - self.test_did, - "indy", - "IndyAgent", - recip_keys, - routing_keys, - self.test_endpoint, - ) - doc.set(service) - return doc - - -class TestConnectionResponse(TestCase, TestConfig): - def setUp(self): - self.connection_response = ConnectionResponse( - connection=ConnectionDetail(did=self.test_did, did_doc=self.make_did_doc()) - ) - - def test_init(self): - assert self.connection_response.connection.did == self.test_did - - def test_type(self): - assert self.connection_response._type == DIDCommPrefix.qualify_current( - CONNECTION_RESPONSE - ) - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_response.ConnectionResponseSchema.load" - ) - def test_deserialize(self, mock_connection_response_schema_load): - """ - Test deserialization. - """ - obj = {"obj": "obj"} - - connection_response = ConnectionResponse.deserialize(obj) - mock_connection_response_schema_load.assert_called_once_with(obj) - - assert connection_response is mock_connection_response_schema_load.return_value - - @mock.patch( - "aries_cloudagent.protocols.connections.v1_0.messages." - "connection_response.ConnectionResponseSchema.dump" - ) - def test_serialize(self, mock_connection_response_schema_dump): - """ - Test serialization. - """ - connection_response_dict = self.connection_response.serialize() - mock_connection_response_schema_dump.assert_called_once_with( - self.connection_response - ) - - assert ( - connection_response_dict is mock_connection_response_schema_dump.return_value - ) - - -class TestConnectionResponseSchema(IsolatedAsyncioTestCase, TestConfig): - async def test_make_model(self): - connection_response = ConnectionResponse( - connection=ConnectionDetail(did=self.test_did, did_doc=self.make_did_doc()) - ) - session = InMemoryProfile.test_session() - wallet = session.wallet - key_info = await wallet.create_signing_key(ED25519) - await connection_response.sign_field("connection", key_info.verkey, wallet) - data = connection_response.serialize() - model_instance = ConnectionResponse.deserialize(data) - assert type(model_instance) is type(connection_response) diff --git a/aries_cloudagent/protocols/connections/v1_0/models/__init__.py b/aries_cloudagent/protocols/connections/v1_0/models/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py b/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py deleted file mode 100644 index 6029cb1614..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py +++ /dev/null @@ -1,114 +0,0 @@ -"""An object for containing the connection request/response DID information.""" - -from typing import Optional - -from marshmallow import EXCLUDE, fields - -from .....connections.models.diddoc import DIDDoc -from .....messaging.models.base import BaseModel, BaseModelSchema -from .....messaging.valid import INDY_DID_EXAMPLE, INDY_DID_VALIDATE - - -class DIDDocWrapper(fields.Field): - """Field that loads and serializes DIDDoc.""" - - def _serialize(self, value: DIDDoc, attr, obj, **kwargs): - """Serialize the DIDDoc. - - Args: - value: The value to serialize - attr: The attribute being serialized - obj: The object being serialized - kwargs: Additional keyword arguments - - Returns: - The serialized DIDDoc - - """ - return value.serialize(normalize_routing_keys=True) - - def _deserialize(self, value, attr=None, data=None, **kwargs): - """Deserialize a value into a DIDDoc. - - Args: - value: The value to deserialize - attr: The attribute being deserialized - data: The full data being deserialized - kwargs: Additional keyword arguments - - Returns: - The deserialized value - - """ - return DIDDoc.deserialize(value) - - -class ConnectionDetail(BaseModel): - """Class representing the details of a connection.""" - - class Meta: - """ConnectionDetail metadata.""" - - schema_class = "ConnectionDetailSchema" - - def __init__( - self, *, did: Optional[str] = None, did_doc: Optional[DIDDoc] = None, **kwargs - ): - """Initialize a ConnectionDetail instance. - - Args: - did: DID for the connection detail - did_doc: DIDDoc for connection detail - kwargs: Additional keyword arguments - - """ - super().__init__(**kwargs) - self._did = did - self._did_doc = did_doc - - @property - def did(self) -> str: - """Accessor for the connection DID. - - Returns: - The DID for this connection - - """ - return self._did - - @property - def did_doc(self) -> DIDDoc: - """Accessor for the connection DID Document. - - Returns: - The DIDDoc for this connection - - """ - return self._did_doc - - -class ConnectionDetailSchema(BaseModelSchema): - """ConnectionDetail schema.""" - - class Meta: - """ConnectionDetailSchema metadata.""" - - model_class = ConnectionDetail - unknown = EXCLUDE - - did = fields.Str( - data_key="DID", - required=False, - validate=INDY_DID_VALIDATE, - metadata={ - "description": "DID for connection detail", - "example": INDY_DID_EXAMPLE, - }, - ) - did_doc = DIDDocWrapper( - data_key="DIDDoc", - required=False, - metadata={ - "description": "DID document for connection detail", - }, - ) diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/__init__.py b/aries_cloudagent/protocols/connections/v1_0/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py deleted file mode 100644 index 746f638311..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ /dev/null @@ -1,1221 +0,0 @@ -from unittest import IsolatedAsyncioTestCase - -from aries_cloudagent.tests import mock - -from .....cache.base import BaseCache -from .....cache.in_memory import InMemoryCache -from .....connections.models.conn_record import ConnRecord -from .....connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from .....core.in_memory import InMemoryProfile -from .....core.oob_processor import OobMessageProcessor -from .....messaging.responder import BaseResponder, MockResponder -from .....multitenant.base import BaseMultitenantManager -from .....multitenant.manager import MultitenantManager -from .....resolver.default.legacy_peer import LegacyPeerDIDResolver -from .....resolver.did_resolver import DIDResolver -from .....storage.error import StorageNotFoundError -from .....transport.inbound.receipt import MessageReceipt -from .....wallet.base import DIDInfo -from .....wallet.did_method import SOV, DIDMethods -from .....wallet.in_memory import InMemoryWallet -from .....wallet.key_type import ED25519 -from ....coordinate_mediation.v1_0.manager import MediationManager -from ....coordinate_mediation.v1_0.messages.mediate_request import MediationRequest -from ....coordinate_mediation.v1_0.models.mediation_record import MediationRecord -from ....coordinate_mediation.v1_0.route_manager import RouteManager -from ..manager import ConnectionManager, ConnectionManagerError -from ..messages.connection_invitation import ConnectionInvitation -from ..messages.connection_request import ConnectionRequest -from ..messages.connection_response import ConnectionResponse -from ..models.connection_detail import ConnectionDetail - - -class TestConnectionManager(IsolatedAsyncioTestCase): - def make_did_doc(self, did, verkey): - doc = DIDDoc(did=did) - controller = did - ident = "1" - pk_value = verkey - pk = PublicKey( - did, ident, pk_value, PublicKeyType.ED25519_SIG_2018, controller, False - ) - doc.set(pk) - recip_keys = [pk] - router_keys = [] - service = Service( - did, "indy", "IndyAgent", recip_keys, router_keys, self.test_endpoint - ) - doc.set(service) - return doc - - async def asyncSetUp(self): - self.test_seed = "testseed000000000000000000000001" - self.test_did = "55GkHamhTU1ZbTbV2ab9DE" - self.test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" - self.test_endpoint = "http://localhost" - - self.test_target_did = "GbuDUYXaUZRfHD2jeDuQuP" - self.test_target_verkey = "9WCgWKUaAJj3VWxxtzvvMQN3AoFxoBtBDo9ntwJnVVCC" - - self.responder = MockResponder() - - self.oob_mock = mock.MagicMock( - clean_finished_oob_record=mock.CoroutineMock(return_value=None) - ) - self.route_manager = mock.MagicMock(RouteManager) - self.route_manager.routing_info = mock.CoroutineMock( - return_value=([], self.test_endpoint) - ) - self.route_manager.mediation_record_if_id = mock.CoroutineMock(return_value=None) - self.resolver = DIDResolver() - self.resolver.register_resolver(LegacyPeerDIDResolver()) - - self.profile = InMemoryProfile.test_profile( - { - "default_endpoint": "http://aries.ca/endpoint", - "default_label": "This guy", - "additional_endpoints": ["http://aries.ca/another-endpoint"], - "debug.auto_accept_invites": True, - "debug.auto_accept_requests": True, - }, - bind={ - BaseResponder: self.responder, - BaseCache: InMemoryCache(), - OobMessageProcessor: self.oob_mock, - RouteManager: self.route_manager, - DIDMethods: DIDMethods(), - DIDResolver: self.resolver, - }, - ) - self.context = self.profile.context - - self.multitenant_mgr = mock.MagicMock(MultitenantManager, autospec=True) - self.context.injector.bind_instance(BaseMultitenantManager, self.multitenant_mgr) - - self.test_mediator_routing_keys = ["3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRR"] - self.test_mediator_conn_id = "mediator-conn-id" - self.test_mediator_endpoint = "http://mediator.example.com" - - self.manager = ConnectionManager(self.profile) - assert self.manager.profile - - async def test_create_invitation_non_multi_use_invitation_fails_on_reuse(self): - connect_record, connect_invite = await self.manager.create_invitation() - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - requestA = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_target_did, - did_doc=self.make_did_doc(self.test_target_did, self.test_target_verkey), - ), - label="SameInviteRequestA", - ) - - await self.manager.receive_request(requestA, receipt) - - requestB = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_did, - did_doc=self.make_did_doc(self.test_did, self.test_verkey), - ), - label="SameInviteRequestB", - ) - - # requestB fails because the invitation was not set to multi-use - with self.assertRaises(ConnectionManagerError): - await self.manager.receive_request(requestB, receipt) - - async def test_create_invitation_public(self): - self.context.update_settings({"public_invites": True}) - - self.route_manager.route_verkey = mock.CoroutineMock() - with 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=SOV, - key_type=ED25519, - ) - connect_record, connect_invite = await self.manager.create_invitation( - public=True, my_endpoint="testendpoint" - ) - - assert connect_record - assert connect_invite.did.endswith(self.test_did) - self.route_manager.route_verkey.assert_called_once_with( - self.profile, self.test_verkey - ) - - async def test_create_invitation_public_no_public_invites(self): - self.context.update_settings({"public_invites": False}) - - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation(public=True, my_endpoint="testendpoint") - - async def test_create_invitation_public_no_public_did(self): - self.context.update_settings({"public_invites": True}) - - with mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = None - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation( - public=True, my_endpoint="testendpoint" - ) - - async def test_create_invitation_multi_use(self): - connect_record, connect_invite = await self.manager.create_invitation( - my_endpoint="testendpoint", multi_use=True - ) - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - requestA = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_target_did, - did_doc=self.make_did_doc(self.test_target_did, self.test_target_verkey), - ), - label="SameInviteRequestA", - ) - - await self.manager.receive_request(requestA, receipt) - - requestB = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_did, - did_doc=self.make_did_doc(self.test_did, self.test_verkey), - ), - label="SameInviteRequestB", - ) - - await self.manager.receive_request(requestB, receipt) - - async def test_create_invitation_recipient_routing_endpoint(self): - async with self.profile.session() as session: - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - connect_record, connect_invite = await self.manager.create_invitation( - my_endpoint=self.test_endpoint, - recipient_keys=[self.test_verkey], - routing_keys=[self.test_verkey], - ) - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - requestA = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_target_did, - did_doc=self.make_did_doc( - self.test_target_did, self.test_target_verkey - ), - ), - label="InviteRequestA", - ) - - await self.manager.receive_request(requestA, receipt) - - async def test_create_invitation_metadata_assigned(self): - async with self.profile.session() as session: - record, invite = await self.manager.create_invitation( - metadata={"hello": "world"} - ) - - assert await record.metadata_get_all(session) == {"hello": "world"} - - async def test_create_invitation_multi_use_metadata_transfers_to_connection(self): - async with self.profile.session() as session: - connect_record, _ = await self.manager.create_invitation( - my_endpoint="testendpoint", multi_use=True, metadata={"test": "value"} - ) - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - request = ConnectionRequest( - connection=ConnectionDetail( - did=self.test_target_did, - did_doc=self.make_did_doc( - self.test_target_did, self.test_target_verkey - ), - ), - label="request", - ) - - new_conn_rec = await self.manager.receive_request(request, receipt) - assert new_conn_rec != connect_record - 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 = 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, - 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 mock.patch.object( - MediationManager, - "get_default_mediator", - ) as mock_get_default_mediator: - _, invite = await self.manager.create_invitation( - routing_keys=[self.test_verkey], - my_endpoint=self.test_endpoint, - mediation_id=mediation_record.mediation_id, - ) - assert invite.routing_keys == self.test_mediator_routing_keys - assert invite.endpoint == self.test_mediator_endpoint - mock_get_default_mediator.assert_not_called() - - async def test_create_invitation_mediation_using_default(self): - self.route_manager.routing_info = 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, - 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 mock.patch.object( - self.route_manager, - "mediation_record_if_id", - mock.CoroutineMock(return_value=mediation_record), - ): - _, 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 - self.route_manager.routing_info.assert_awaited_once_with( - self.profile, mediation_record - ) - - async def test_receive_invitation(self): - (_, connect_invite) = await self.manager.create_invitation( - my_endpoint="testendpoint" - ) - - invitee_record = await self.manager.receive_invitation(connect_invite) - assert ConnRecord.State.get(invitee_record.state) is ConnRecord.State.REQUEST - - async def test_receive_invitation_no_auto_accept(self): - (_, connect_invite) = await self.manager.create_invitation( - my_endpoint="testendpoint" - ) - - invitee_record = await self.manager.receive_invitation( - connect_invite, auto_accept=False - ) - assert ConnRecord.State.get(invitee_record.state) is ConnRecord.State.INVITATION - - async def test_receive_invitation_bad_invitation(self): - x_invites = [ - ConnectionInvitation(), - ConnectionInvitation( - recipient_keys=["3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"] - ), - ] - - for x_invite in x_invites: - with self.assertRaises(ConnectionManagerError): - await self.manager.receive_invitation(x_invite) - - async def test_receive_invitation_with_did(self): - """Test invitation received with a public DID instead of service info.""" - invite = ConnectionInvitation(did=self.test_did) - invitee_record = await self.manager.receive_invitation(invite) - assert ConnRecord.State.get(invitee_record.state) is ConnRecord.State.REQUEST - - async def test_receive_invitation_mediation_passes_id_when_auto_accept(self): - with mock.patch.object(ConnectionManager, "create_request") as create_request: - record, connect_invite = await self.manager.create_invitation( - my_endpoint="testendpoint" - ) - - invitee_record = await self.manager.receive_invitation( - connect_invite, mediation_id="test-mediation-id", auto_accept=True - ) - create_request.assert_called_once_with( - invitee_record, mediation_id="test-mediation-id" - ) - - async def test_create_request(self): - conn_req = await self.manager.create_request( - ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) - ) - assert conn_req - - async def test_create_request_my_endpoint(self): - conn_req = await self.manager.create_request( - ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ), - my_endpoint="http://testendpoint.com/endpoint", - ) - assert conn_req - - async def test_create_request_my_did(self): - async with self.profile.session() as session: - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - conn_req = await self.manager.create_request( - ConnRecord( - invitation_key=self.test_verkey, - my_did=self.test_did, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) - ) - assert conn_req - - 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 mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did, mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, mock.patch.object( - self.route_manager, - "mediation_records_for_connection", - mock.CoroutineMock(return_value=[mediation_record]), - ): - mock_wallet_create_local_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - await self.manager.create_request( - ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ), - my_endpoint=self.test_endpoint, - ) - create_did_document.assert_called_once_with( - self.manager, - mock_wallet_create_local_did.return_value, - [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): - mediation_record = MediationRecord( - mediation_id="test_medation_id", - 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, - ) - - record = ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - ) - - # Ensure the path with new did creation is hit - record.my_did = None - - with mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, mock.patch.object( - InMemoryWallet, "create_local_did" - ) as create_local_did, mock.patch.object( - self.route_manager, - "mediation_records_for_connection", - mock.CoroutineMock(return_value=[mediation_record]), - ): - did_info = DIDInfo( - did=self.test_did, - verkey=self.test_verkey, - metadata={}, - method=SOV, - key_type=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(SOV, ED25519) - create_did_document.assert_called_once_with( - self.manager, - did_info, - [self.test_endpoint], - mediation_records=[mediation_record], - ) - - async def test_create_request_default_mediator(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", - ) - - # Ensure the path with new did creation is hit - record.my_did = None - - with mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, mock.patch.object( - InMemoryWallet, "create_local_did" - ) as create_local_did, mock.patch.object( - self.route_manager, - "mediation_records_for_connection", - mock.CoroutineMock(return_value=[mediation_record]), - ): - did_info = DIDInfo( - did=self.test_did, - verkey=self.test_verkey, - metadata={}, - method=SOV, - key_type=ED25519, - ) - create_local_did.return_value = did_info - await self.manager.create_request( - record, - my_endpoint=self.test_endpoint, - ) - create_local_did.assert_called_once_with(SOV, ED25519) - create_did_document.assert_called_once_with( - self.manager, - did_info, - [self.test_endpoint], - mediation_records=[mediation_record], - ) - - async def test_receive_request_public_did_oob_invite(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": True}) - with mock.patch.object( - ConnRecord, "connection_id", autospec=True - ), mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( - self.manager, "store_did_document", mock.CoroutineMock() - ): - mock_conn_retrieve_by_invitation_msg_id.return_value = ConnRecord() - conn_rec = await self.manager.receive_request(mock_request, receipt) - assert conn_rec - - self.oob_mock.clean_finished_oob_record.assert_called_once_with( - self.profile, mock_request - ) - - async def test_receive_request_public_did_unsolicited_fails(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": True}) - with self.assertRaises(ConnectionManagerError), mock.patch.object( - ConnRecord, "connection_id", autospec=True - ), mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( - self.manager, "store_did_document", mock.CoroutineMock() - ): - mock_conn_retrieve_by_invitation_msg_id.return_value = None - conn_rec = await self.manager.receive_request(mock_request, receipt) - - async def test_receive_request_public_did_conn_invite(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - mock_connection_record = mock.MagicMock() - mock_connection_record.save = mock.CoroutineMock() - mock_connection_record.attach_request = mock.CoroutineMock() - - self.context.update_settings({"public_invites": True}) - with mock.patch.object( - ConnRecord, "connection_id", autospec=True - ), mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnRecord, - "retrieve_by_invitation_msg_id", - mock.CoroutineMock(return_value=mock_connection_record), - ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( - self.manager, "store_did_document", mock.CoroutineMock() - ): - conn_rec = await self.manager.receive_request(mock_request, receipt) - assert conn_rec - - async def test_receive_request_public_did_unsolicited(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": True}) - self.context.update_settings({"requests_through_public_did": True}) - with mock.patch.object( - ConnRecord, "connection_id", autospec=True - ), mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( - self.manager, "store_did_document", mock.CoroutineMock() - ): - mock_conn_retrieve_by_invitation_msg_id.return_value = None - conn_rec = await self.manager.receive_request(mock_request, receipt) - assert conn_rec - - async def test_receive_request_public_did_no_did_doc(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = None - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": True}) - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - with self.assertRaises(ConnectionManagerError): - await self.manager.receive_request(mock_request, receipt) - - async def test_receive_request_public_did_wrong_did(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = "dummy" - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": True}) - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ): - with self.assertRaises(ConnectionManagerError): - await self.manager.receive_request(mock_request, receipt) - - async def test_receive_request_public_did_no_public_invites(self): - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) - async with self.profile.session() as session: - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings({"public_invites": False}) - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object(self.manager, "store_did_document", mock.CoroutineMock()): - with self.assertRaises(ConnectionManagerError): - await self.manager.receive_request(mock_request, receipt) - - async def test_receive_request_public_did_no_auto_accept(self): - async with self.profile.session() as session: - mock_request = mock.MagicMock() - mock_request.connection = mock.MagicMock() - mock_request.connection.did = self.test_did - mock_request.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_request.connection.did_doc.did = self.test_did - - receipt = MessageReceipt( - recipient_did=self.test_did, recipient_did_public=True - ) - await session.wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=None, - did=self.test_did, - ) - - self.context.update_settings( - {"public_invites": True, "debug.auto_accept_requests": False} - ) - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "attach_request", autospec=True - ) as mock_conn_attach_request, mock.patch.object( - ConnRecord, "retrieve_by_id", autospec=True - ) as mock_conn_retrieve_by_id, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnRecord, "retrieve_by_invitation_msg_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_invitation_msg_id, mock.patch.object( - self.manager, "store_did_document", mock.CoroutineMock() - ): - mock_conn_retrieve_by_invitation_msg_id.return_value = ConnRecord() - conn_rec = await self.manager.receive_request(mock_request, receipt) - assert conn_rec - - messages = self.responder.messages - assert not messages - - async def test_create_response(self): - conn_rec = ConnRecord(state=ConnRecord.State.REQUEST.rfc160) - - with mock.patch.object( - ConnRecord, "log_state", autospec=True - ) as mock_conn_log_state, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ) as mock_conn_retrieve_request, mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_save, mock.patch.object( - ConnectionResponse, "sign_field", autospec=True - ) as mock_sign, mock.patch.object(conn_rec, "metadata_get", mock.CoroutineMock()): - await self.manager.create_response(conn_rec, "http://10.20.30.40:5060/") - - async def test_create_response_multitenant(self): - self.context.update_settings( - {"wallet.id": "test_wallet", "multitenant.enabled": True} - ) - - mediation_record = MediationRecord( - mediation_id="test_medation_id", - 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 mock.patch.object(ConnRecord, "log_state", autospec=True), mock.patch.object( - ConnRecord, "save", autospec=True - ), mock.patch.object( - ConnRecord, "metadata_get", mock.CoroutineMock(return_value=False) - ), mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ), mock.patch.object( - ConnectionResponse, "sign_field", autospec=True - ), mock.patch.object( - InMemoryWallet, "create_local_did", autospec=True - ) as mock_wallet_create_local_did, mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, mock.patch.object( - self.route_manager, - "mediation_records_for_connection", - mock.CoroutineMock(return_value=[mediation_record]), - ): - mock_wallet_create_local_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - await self.manager.create_response( - ConnRecord( - state=ConnRecord.State.REQUEST, - ), - my_endpoint=self.test_endpoint, - ) - create_did_document.assert_called_once_with( - self.manager, - mock_wallet_create_local_did.return_value, - [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): - await self.manager.create_response( - ConnRecord( - invitation_key=self.test_verkey, - their_label="Hello", - their_role=ConnRecord.Role.RESPONDER.rfc160, - alias="Bob", - state=ConnRecord.State.ABANDONED.rfc160, - ) - ) - - async def test_create_response_mediation(self): - mediation_record = MediationRecord( - mediation_id="test_medation_id", - 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, - ) - - 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, - ) - - # Ensure the path with new did creation is hit - record.my_did = None - - with mock.patch.object(ConnRecord, "log_state", autospec=True), mock.patch.object( - ConnRecord, "save", autospec=True - ), mock.patch.object( - record, "metadata_get", mock.CoroutineMock(return_value=False) - ), mock.patch.object( - ConnectionManager, "create_did_document", autospec=True - ) as create_did_document, mock.patch.object( - InMemoryWallet, "create_local_did" - ) as create_local_did, mock.patch.object( - self.route_manager, - "mediation_records_for_connection", - mock.CoroutineMock(return_value=[mediation_record]), - ), mock.patch.object( - record, "retrieve_request", autospec=True - ), mock.patch.object(ConnectionResponse, "sign_field", autospec=True): - did_info = DIDInfo( - did=self.test_did, - verkey=self.test_verkey, - metadata={}, - method=SOV, - key_type=ED25519, - ) - create_local_did.return_value = did_info - await self.manager.create_response( - record, - mediation_id=mediation_record.mediation_id, - my_endpoint=self.test_endpoint, - ) - create_local_did.assert_called_once_with(SOV, ED25519) - create_did_document.assert_called_once_with( - self.manager, - did_info, - [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( - state=ConnRecord.State.REQUEST.rfc160, - ) - conn_rec.my_did = None - - with mock.patch.object( - ConnRecord, "log_state", autospec=True - ) as mock_conn_log_state, mock.patch.object( - ConnRecord, "retrieve_request", autospec=True - ) as mock_conn_retrieve_request, mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_save, mock.patch.object( - ConnectionResponse, "sign_field", autospec=True - ) as mock_sign, mock.patch.object( - conn_rec, "metadata_get", mock.CoroutineMock(return_value=True) - ): - await self.manager.create_response(conn_rec) - - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, MediationRequest) - assert target["connection_id"] == conn_rec.connection_id - - async def test_accept_response_find_by_thread_id(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - mock_response.verify_signed_field = mock.CoroutineMock(return_value="sig_verkey") - receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id, mock.patch.object( - MediationManager, "get_default_mediator", mock.CoroutineMock() - ), mock.patch.object(self.manager, "store_did_document", mock.CoroutineMock()): - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - save=mock.CoroutineMock(), - metadata_get=mock.CoroutineMock(), - connection_id="test-conn-id", - invitation_key="test-invitation-key", - ) - conn_rec = await self.manager.accept_response(mock_response, receipt) - assert conn_rec.their_did == self.test_target_did - assert ConnRecord.State.get(conn_rec.state) is ConnRecord.State.RESPONSE - - async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - mock_response.verify_signed_field = mock.CoroutineMock(return_value="sig_verkey") - - receipt = MessageReceipt(sender_did=self.test_target_did) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id, mock.patch.object( - ConnRecord, "retrieve_by_did", mock.CoroutineMock() - ) as mock_conn_retrieve_by_did, mock.patch.object( - MediationManager, "get_default_mediator", mock.CoroutineMock() - ), mock.patch.object(self.manager, "store_did_document", mock.CoroutineMock()): - mock_conn_retrieve_by_req_id.side_effect = StorageNotFoundError() - mock_conn_retrieve_by_did.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - save=mock.CoroutineMock(), - metadata_get=mock.CoroutineMock(return_value=False), - connection_id="test-conn-id", - invitation_key="test-invitation-id", - ) - - conn_rec = await self.manager.accept_response(mock_response, receipt) - assert conn_rec.their_did == self.test_target_did - assert ConnRecord.State.get(conn_rec.state) is ConnRecord.State.RESPONSE - - assert not self.responder.messages - - async def test_accept_response_not_found_by_thread_id_nor_receipt_sender_did(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - - receipt = MessageReceipt(sender_did=self.test_target_did) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id, mock.patch.object( - ConnRecord, "retrieve_by_did", mock.CoroutineMock() - ) as mock_conn_retrieve_by_did: - mock_conn_retrieve_by_req_id.side_effect = StorageNotFoundError() - mock_conn_retrieve_by_did.side_effect = StorageNotFoundError() - - with self.assertRaises(ConnectionManagerError): - await self.manager.accept_response(mock_response, receipt) - - async def test_accept_response_find_by_thread_id_bad_state(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - - receipt = MessageReceipt(sender_did=self.test_target_did) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id: - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - state=ConnRecord.State.ABANDONED.rfc23 - ) - - with self.assertRaises(ConnectionManagerError): - await self.manager.accept_response(mock_response, receipt) - - async def test_accept_response_find_by_thread_id_no_connection_did_doc(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = None - - receipt = MessageReceipt(sender_did=self.test_target_did) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id: - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - ) - - with self.assertRaises(ConnectionManagerError): - await self.manager.accept_response(mock_response, receipt) - - async def test_accept_response_find_by_thread_id_did_mismatch(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_did - - receipt = MessageReceipt(sender_did=self.test_target_did) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id: - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - ) - - with self.assertRaises(ConnectionManagerError): - await self.manager.accept_response(mock_response, receipt) - - async def test_accept_response_verify_invitation_key_sign_failure(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - mock_response.verify_signed_field = mock.CoroutineMock(side_effect=ValueError) - receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id, mock.patch.object( - MediationManager, "get_default_mediator", mock.CoroutineMock() - ): - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - save=mock.CoroutineMock(), - metadata_get=mock.CoroutineMock(), - connection_id="test-conn-id", - invitation_key="test-invitation-key", - ) - with self.assertRaises(ConnectionManagerError): - await self.manager.accept_response(mock_response, receipt) - - async def test_accept_response_auto_send_mediation_request(self): - mock_response = mock.MagicMock() - mock_response._thread = mock.MagicMock() - mock_response.connection = mock.MagicMock() - mock_response.connection.did = self.test_target_did - mock_response.connection.did_doc = mock.MagicMock(spec=DIDDoc) - mock_response.connection.did_doc.did = self.test_target_did - mock_response.verify_signed_field = mock.CoroutineMock(return_value="sig_verkey") - receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) - - with mock.patch.object( - ConnRecord, "save", autospec=True - ) as mock_conn_rec_save, mock.patch.object( - ConnRecord, "retrieve_by_request_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_req_id, mock.patch.object( - MediationManager, "get_default_mediator", mock.CoroutineMock() - ), mock.patch.object(self.manager, "store_did_document", mock.CoroutineMock()): - mock_conn_retrieve_by_req_id.return_value = mock.MagicMock( - did=self.test_target_did, - did_doc=mock.MagicMock(did=self.test_target_did), - state=ConnRecord.State.RESPONSE.rfc23, - save=mock.CoroutineMock(), - metadata_get=mock.CoroutineMock(return_value=True), - connection_id="test-conn-id", - invitation_key="test-invitation-key", - ) - conn_rec = await self.manager.accept_response(mock_response, receipt) - assert conn_rec.their_did == self.test_target_did - assert ConnRecord.State.get(conn_rec.state) is ConnRecord.State.RESPONSE - - assert len(self.responder.messages) == 1 - message, target = self.responder.messages[0] - assert isinstance(message, MediationRequest) - assert target["connection_id"] == conn_rec.connection_id diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py deleted file mode 100644 index 83fdaf10b9..0000000000 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py +++ /dev/null @@ -1,800 +0,0 @@ -import json -from unittest import IsolatedAsyncioTestCase -from unittest.mock import ANY - -from aries_cloudagent.tests import mock - -from .....admin.request_context import AdminRequestContext -from .....cache.base import BaseCache -from .....cache.in_memory import InMemoryCache -from .....connections.models.conn_record import ConnRecord -from .....core.in_memory import InMemoryProfile -from .....storage.error import StorageNotFoundError -from .. import routes as test_module - - -class TestConnectionRoutes(IsolatedAsyncioTestCase): - async def asyncSetUp(self): - self.session_inject = {} - profile = InMemoryProfile.test_profile( - settings={ - "admin.admin_api_key": "secret-key", - } - ) - self.context = AdminRequestContext.test_context(self.session_inject, profile) - self.request_dict = { - "context": self.context, - "outbound_message_router": mock.CoroutineMock(), - } - self.request = mock.MagicMock( - app={}, - match_info={}, - query={}, - __getitem__=lambda _, k: self.request_dict[k], - headers={"x-api-key": "secret-key"}, - ) - - async def test_connections_list(self): - self.request.query = { - "invitation_id": "dummy", # exercise tag filter assignment - "their_role": ConnRecord.Role.REQUESTER.rfc160, - "connection_protocol": "connections/1.0", - "invitation_key": "some-invitation-key", - "their_public_did": "a_public_did", - "invitation_msg_id": "dummy_msg", - } - - STATE_COMPLETED = ConnRecord.State.COMPLETED - STATE_INVITATION = ConnRecord.State.INVITATION - STATE_ABANDONED = ConnRecord.State.ABANDONED - ROLE_REQUESTER = ConnRecord.Role.REQUESTER - with mock.patch.object(test_module, "ConnRecord", autospec=True) as mock_conn_rec: - mock_conn_rec.query = mock.CoroutineMock() - mock_conn_rec.Role = ConnRecord.Role - mock_conn_rec.State = mock.MagicMock( - COMPLETED=STATE_COMPLETED, - INVITATION=STATE_INVITATION, - ABANDONED=STATE_ABANDONED, - get=mock.MagicMock( - side_effect=[ - ConnRecord.State.ABANDONED, - ConnRecord.State.COMPLETED, - ConnRecord.State.INVITATION, - ] - ), - ) - conns = [ # in ascending order here - mock.MagicMock( - serialize=mock.MagicMock( - return_value={ - "state": ConnRecord.State.COMPLETED.rfc23, - "created_at": "1234567890", - } - ) - ), - mock.MagicMock( - serialize=mock.MagicMock( - return_value={ - "state": ConnRecord.State.INVITATION.rfc23, - "created_at": "1234567890", - } - ) - ), - mock.MagicMock( - serialize=mock.MagicMock( - return_value={ - "state": ConnRecord.State.ABANDONED.rfc23, - "created_at": "1234567890", - } - ) - ), - ] - mock_conn_rec.query.return_value = [conns[2], conns[0], conns[1]] # jumbled - - with mock.patch.object(test_module.web, "json_response") as mock_response: - await test_module.connections_list(self.request) - mock_conn_rec.query.assert_called_once_with( - ANY, - { - "invitation_id": "dummy", - "invitation_key": "some-invitation-key", - "their_public_did": "a_public_did", - "invitation_msg_id": "dummy_msg", - }, - limit=100, - offset=0, - post_filter_positive={ - "their_role": list(ConnRecord.Role.REQUESTER.value), - "connection_protocol": "connections/1.0", - }, - alt=True, - ) - mock_response.assert_called_once_with( - { - "results": [ - { - k: c.serialize.return_value[k] - for k in ["state", "created_at"] - } - for c in conns - ] - } # sorted - ) - - async def test_connections_list_x(self): - self.request.query = { - "their_role": ConnRecord.Role.REQUESTER.rfc160, - "alias": "my connection", - "state": ConnRecord.State.COMPLETED.rfc23, - } - - STATE_COMPLETED = ConnRecord.State.COMPLETED - ROLE_REQUESTER = ConnRecord.Role.REQUESTER - with mock.patch.object(test_module, "ConnRecord", autospec=True) as mock_conn_rec: - mock_conn_rec.Role = mock.MagicMock(return_value=ROLE_REQUESTER) - mock_conn_rec.State = mock.MagicMock( - COMPLETED=STATE_COMPLETED, - get=mock.MagicMock(return_value=ConnRecord.State.COMPLETED), - ) - mock_conn_rec.query = mock.CoroutineMock( - side_effect=test_module.StorageError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_list(self.request) - - async def test_connections_retrieve(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock(return_value={"hello": "world"}) - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - - await test_module.connections_retrieve(self.request) - mock_response.assert_called_once_with({"hello": "world"}) - - async def test_connections_endpoints(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr_cls, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr_cls.return_value = mock.MagicMock( - get_endpoints=mock.CoroutineMock( - return_value=("localhost:8080", "1.2.3.4:8081") - ) - ) - await test_module.connections_endpoints(self.request) - mock_response.assert_called_once_with( - { - "my_endpoint": "localhost:8080", - "their_endpoint": "1.2.3.4:8081", - } - ) - - async def test_connections_endpoints_x(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr_cls, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr_cls.return_value = mock.MagicMock( - get_endpoints=mock.CoroutineMock(side_effect=StorageNotFoundError()) - ) - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_endpoints(self.request) - - mock_conn_mgr_cls.return_value = mock.MagicMock( - get_endpoints=mock.CoroutineMock(side_effect=test_module.WalletError()) - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_endpoints(self.request) - - async def test_connections_metadata(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - mock_conn_rec, "metadata_get_all", mock.CoroutineMock() - ) as mock_metadata_get_all, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_metadata_get_all.return_value = {"hello": "world"} - - await test_module.connections_metadata(self.request) - mock_metadata_get_all.assert_called_once() - mock_response.assert_called_once_with({"results": {"hello": "world"}}) - - async def test_connections_metadata_get_single(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - self.request.query = {"key": "test"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - mock_conn_rec, "metadata_get_all", mock.CoroutineMock() - ) as mock_metadata_get_all, mock.patch.object( - mock_conn_rec, "metadata_get", mock.CoroutineMock() - ) as mock_metadata_get, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_metadata_get.return_value = {"test": "value"} - - await test_module.connections_metadata(self.request) - mock_metadata_get.assert_called_once() - mock_response.assert_called_once_with({"results": {"test": "value"}}) - - async def test_connections_metadata_x(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - mock_conn_rec, "metadata_get_all", mock.CoroutineMock() - ) as mock_metadata_get_all, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_metadata_get_all.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_metadata(self.request) - - mock_metadata_get_all.side_effect = test_module.BaseModelError() - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_metadata(self.request) - - async def test_connections_metadata_set(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - self.request.json = mock.CoroutineMock( - return_value={"metadata": {"hello": "world"}} - ) - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - mock_conn_rec, "metadata_get_all", mock.CoroutineMock() - ) as mock_metadata_get_all, mock.patch.object( - mock_conn_rec, "metadata_set", mock.CoroutineMock() - ) as mock_metadata_set, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_metadata_get_all.return_value = {"hello": "world"} - - await test_module.connections_metadata_set(self.request) - mock_metadata_set.assert_called_once() - mock_response.assert_called_once_with({"results": {"hello": "world"}}) - - async def test_connections_metadata_set_x(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - self.request.json = mock.CoroutineMock( - return_value={"metadata": {"hello": "world"}} - ) - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - mock_conn_rec, "metadata_get_all", mock.CoroutineMock() - ) as mock_metadata_get_all, mock.patch.object( - mock_conn_rec, "metadata_set", mock.CoroutineMock() - ) as mock_metadata_set, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_metadata_set.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_metadata_set(self.request) - - mock_metadata_set.side_effect = test_module.BaseModelError() - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_metadata_set(self.request) - - async def test_connections_retrieve_not_found(self): - self.request.match_info = {"conn_id": "dummy"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_retrieve(self.request) - - async def test_connections_retrieve_x(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock(side_effect=test_module.BaseModelError()) - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_retrieve(self.request) - - async def test_connections_create_invitation(self): - self.context.update_settings({"public_invites": True}) - body = { - "recipient_keys": ["test"], - "routing_keys": ["test"], - "service_endpoint": "http://example.com", - "metadata": {"hello": "world"}, - "mediation_id": "some-id", - } - self.request.json = mock.CoroutineMock(return_value=body) - self.request.query = { - "auto_accept": "true", - "alias": "alias", - "public": "true", - "multi_use": "true", - } - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr.return_value.create_invitation = mock.CoroutineMock( - return_value=( - mock.MagicMock( # connection record - connection_id="dummy", alias="conn-alias" - ), - mock.MagicMock( # invitation - serialize=mock.MagicMock(return_value={"a": "value"}), - to_url=mock.MagicMock(return_value="http://endpoint.ca"), - ), - ) - ) - - await test_module.connections_create_invitation(self.request) - mock_conn_mgr.return_value.create_invitation.assert_called_once_with( - **{ - key: json.loads(value) if key != "alias" else value - for key, value in self.request.query.items() - }, - my_label=None, - recipient_keys=body["recipient_keys"], - routing_keys=body["routing_keys"], - my_endpoint=body["service_endpoint"], - metadata=body["metadata"], - mediation_id="some-id", - ) - mock_response.assert_called_once_with( - { - "connection_id": "dummy", - "invitation": {"a": "value"}, - "invitation_url": "http://endpoint.ca", - "alias": "conn-alias", - } - ) - - async def test_connections_create_invitation_x(self): - self.context.update_settings({"public_invites": True}) - self.request.json = mock.CoroutineMock() - self.request.query = { - "auto_accept": "true", - "alias": "alias", - "public": "true", - "multi_use": "true", - } - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.create_invitation = mock.CoroutineMock( - side_effect=test_module.ConnectionManagerError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_create_invitation(self.request) - - async def test_connections_create_invitation_x_bad_mediation_id(self): - self.context.update_settings({"public_invites": True}) - body = { - "recipient_keys": ["test"], - "routing_keys": ["test"], - "service_endpoint": "http://example.com", - "metadata": {"hello": "world"}, - "mediation_id": "some-id", - } - self.request.json = mock.CoroutineMock(return_value=body) - self.request.query = { - "auto_accept": "true", - "alias": "alias", - "public": "true", - "multi_use": "true", - } - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.create_invitation = mock.CoroutineMock( - side_effect=StorageNotFoundError() - ) - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_create_invitation(self.request) - - async def test_connections_create_invitation_public_forbidden(self): - self.context.update_settings({"public_invites": False}) - self.request.json = mock.CoroutineMock() - self.request.query = { - "auto_accept": "true", - "alias": "alias", - "public": "true", - "multi_use": "true", - } - - with self.assertRaises(test_module.web.HTTPForbidden): - await test_module.connections_create_invitation(self.request) - - async def test_connections_receive_invitation(self): - self.request.json = mock.CoroutineMock() - self.request.query = { - "auto_accept": "true", - "alias": "alias", - } - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - - with mock.patch.object( - test_module.ConnectionInvitation, "deserialize", autospec=True - ) as mock_inv_deser, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr.return_value.receive_invitation = mock.CoroutineMock( - return_value=mock_conn_rec - ) - - await test_module.connections_receive_invitation(self.request) - mock_response.assert_called_once_with(mock_conn_rec.serialize.return_value) - - async def test_connections_receive_invitation_bad(self): - self.request.json = mock.CoroutineMock() - self.request.query = { - "auto_accept": "true", - "alias": "alias", - } - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - - with mock.patch.object( - test_module.ConnectionInvitation, "deserialize", autospec=True - ) as mock_inv_deser, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_inv_deser.side_effect = test_module.BaseModelError() - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_receive_invitation(self.request) - - async def test_connections_receive_invitation_forbidden(self): - self.context.update_settings({"admin.no_receive_invites": True}) - - with self.assertRaises(test_module.web.HTTPForbidden): - await test_module.connections_receive_invitation(self.request) - - async def test_connections_receive_invitation_x_bad_mediation_id(self): - self.request.json = mock.CoroutineMock() - self.request.query = { - "auto_accept": "true", - "alias": "alias", - "mediation_id": "some-id", - } - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - - with mock.patch.object( - test_module.ConnectionInvitation, "deserialize", autospec=True - ) as mock_inv_deser, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.receive_invitation = mock.CoroutineMock( - side_effect=StorageNotFoundError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_receive_invitation(self.request) - - async def test_connections_accept_invitation(self): - self.request.match_info = {"conn_id": "dummy"} - self.request.query = { - "my_label": "label", - "my_endpoint": "http://endpoint.ca", - } - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_conn_mgr.return_value.create_request = mock.CoroutineMock() - - await test_module.connections_accept_invitation(self.request) - mock_response.assert_called_once_with(mock_conn_rec.serialize.return_value) - - async def test_connections_accept_invitation_not_found(self): - self.request.match_info = {"conn_id": "dummy"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_accept_invitation(self.request) - - async def test_connections_accept_invitation_x(self): - self.request.match_info = {"conn_id": "dummy"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.create_request = mock.CoroutineMock( - side_effect=test_module.ConnectionManagerError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_accept_invitation(self.request) - - async def test_connections_accept_invitation_x_bad_mediation_id(self): - self.request.match_info = {"conn_id": "dummy"} - self.request.query["mediation_id"] = "some-id" - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.create_request = mock.CoroutineMock( - side_effect=StorageNotFoundError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_accept_invitation(self.request) - - async def test_connections_accept_request(self): - self.request.match_info = {"conn_id": "dummy"} - self.request.query = { - "my_endpoint": "http://endpoint.ca", - } - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - mock_conn_mgr.return_value.create_response = mock.CoroutineMock() - - await test_module.connections_accept_request(self.request) - mock_response.assert_called_once_with(mock_conn_rec.serialize.return_value) - - async def test_connections_accept_request_not_found(self): - self.request.match_info = {"conn_id": "dummy"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_accept_request(self.request) - - async def test_connections_accept_request_x(self): - self.request.match_info = {"conn_id": "dummy"} - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr.return_value.create_response = mock.CoroutineMock( - side_effect=test_module.ConnectionManagerError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_accept_request(self.request) - - async def test_connections_remove(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - mock_conn_rec.delete_record = mock.CoroutineMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - - await test_module.connections_remove(self.request) - mock_response.assert_called_once_with({}) - - async def test_connections_remove_cache_key(self): - cache = InMemoryCache() - profile = self.context.profile - await cache.set("conn_rec_state::dummy", "active") - profile.context.injector.bind_instance(BaseCache, cache) - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock() - mock_conn_rec.delete_record = mock.CoroutineMock() - assert (await cache.get("conn_rec_state::dummy")) == "active" - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - - await test_module.connections_remove(self.request) - mock_response.assert_called_once_with({}) - assert not (await cache.get("conn_rec_state::dummy")) - - async def test_connections_remove_not_found(self): - self.request.match_info = {"conn_id": "dummy"} - - mock_conn_rec = mock.MagicMock() - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.side_effect = StorageNotFoundError() - - with self.assertRaises(test_module.web.HTTPNotFound): - await test_module.connections_remove(self.request) - - async def test_connections_remove_x(self): - self.request.match_info = {"conn_id": "dummy"} - mock_conn_rec = mock.MagicMock( - delete_record=mock.CoroutineMock(side_effect=test_module.StorageError()) - ) - - with mock.patch.object( - test_module.ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_rec_retrieve_by_id: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_remove(self.request) - - async def test_connections_create_static(self): - self.request.json = mock.CoroutineMock( - return_value={ - "my_seed": "my_seed", - "my_did": "my_did", - "their_seed": "their_seed", - "their_did": "their_did", - "their_verkey": "their_verkey", - "their_endpoint": "their_endpoint", - "their_role": "their_role", - "alias": "alias", - } - ) - self.request.query = { - "auto_accept": "true", - "alias": "alias", - } - self.request.match_info = {"conn_id": "dummy"} - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - mock_my_info = mock.MagicMock() - mock_my_info.did = "my_did" - mock_my_info.verkey = "my_verkey" - mock_their_info = mock.MagicMock() - mock_their_info.did = "their_did" - mock_their_info.verkey = "their_verkey" - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, mock.patch.object( - test_module.web, "json_response" - ) as mock_response: - mock_conn_mgr.return_value.create_static_connection = mock.CoroutineMock( - return_value=(mock_my_info, mock_their_info, mock_conn_rec) - ) - - await test_module.connections_create_static(self.request) - mock_response.assert_called_once_with( - { - "my_did": mock_my_info.did, - "my_verkey": mock_my_info.verkey, - "their_did": mock_their_info.did, - "their_verkey": mock_their_info.verkey, - "my_endpoint": self.context.settings.get("default_endpoint"), - "record": mock_conn_rec.serialize.return_value, - } - ) - - async def test_connections_create_static_x(self): - self.request.json = mock.CoroutineMock( - return_value={ - "my_seed": "my_seed", - "my_did": "my_did", - "their_seed": "their_seed", - "their_did": "their_did", - "their_verkey": "their_verkey", - "their_endpoint": "their_endpoint", - "their_role": "their_role", - "alias": "alias", - } - ) - self.request.query = { - "auto_accept": "true", - "alias": "alias", - } - self.request.match_info = {"conn_id": "dummy"} - - mock_conn_rec = mock.MagicMock() - mock_conn_rec.serialize = mock.MagicMock() - mock_my_info = mock.MagicMock() - mock_my_info.did = "my_did" - mock_my_info.verkey = "my_verkey" - mock_their_info = mock.MagicMock() - mock_their_info.did = "their_did" - mock_their_info.verkey = "their_verkey" - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr: - mock_conn_mgr.return_value.create_static_connection = mock.CoroutineMock( - side_effect=test_module.WalletError() - ) - - with self.assertRaises(test_module.web.HTTPBadRequest): - await test_module.connections_create_static(self.request) - - async def test_register(self): - mock_app = mock.MagicMock() - mock_app.add_routes = mock.MagicMock() - - await test_module.register(mock_app) - mock_app.add_routes.assert_called_once() - - async def test_post_process_routes(self): - mock_app = mock.MagicMock(_state={"swagger_dict": {}}) - test_module.post_process_routes(mock_app) - assert "tags" in mock_app._state["swagger_dict"] diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index c3d515fecc..248777bc76 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -17,7 +17,7 @@ from ....messaging.models.openapi import OpenAPISchema from ....messaging.valid import UUID4_EXAMPLE from ....storage.error import StorageError, StorageNotFoundError -from ...connections.v1_0.routes import ConnectionsConnIdMatchInfoSchema +from ....connections.routes import ConnectionsConnIdMatchInfoSchema from ...routing.v1_0.models.route_record import RouteRecord, RouteRecordSchema from .manager import MediationManager, MediationManagerError from .message_types import SPEC_URI diff --git a/aries_cloudagent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py b/aries_cloudagent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py index 132d192f59..587457faf8 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py +++ b/aries_cloudagent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py @@ -6,7 +6,7 @@ HandlerException, RequestContext, ) -from ....connections.v1_0.manager import ConnectionManager, ConnectionManagerError +from ....out_of_band.v1_0.manager import OutOfBandManager, OutOfBandManagerError from ....problem_report.v1_0.message import ProblemReport from ..messages.forward_invitation import ForwardInvitation @@ -26,11 +26,11 @@ async def handle(self, context: RequestContext, responder: BaseResponder): # Store invitation profile = context.profile - connection_mgr = ConnectionManager(profile) + mgr = OutOfBandManager(profile) try: - await connection_mgr.receive_invitation(context.message.invitation) - except ConnectionManagerError as e: + await mgr.receive_invitation(context.message.invitation) + except OutOfBandManagerError as e: self._logger.exception("Error receiving forward connection invitation") await responder.send_reply( ProblemReport( diff --git a/aries_cloudagent/protocols/introduction/v0_1/handlers/invitation_request_handler.py b/aries_cloudagent/protocols/introduction/v0_1/handlers/invitation_request_handler.py index e66d7a4cef..13ceb06e41 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/handlers/invitation_request_handler.py +++ b/aries_cloudagent/protocols/introduction/v0_1/handlers/invitation_request_handler.py @@ -6,7 +6,8 @@ HandlerException, RequestContext, ) -from ....connections.v1_0.manager import ConnectionManager +from ....out_of_band.v1_0.manager import OutOfBandManager +from ....out_of_band.v1_0.messages.invitation import HSProto from ..messages.invitation import Invitation as IntroInvitation from ..messages.invitation_request import InvitationRequest as IntroInvitationRequest @@ -29,9 +30,14 @@ async def handle(self, context: RequestContext, responder: BaseResponder): if context.settings.get("auto_accept_intro_invitation_requests"): # Create a new connection invitation and send it back in an IntroInvitation profile = context.profile - connection_mgr = ConnectionManager(profile) - _connection, invite = await connection_mgr.create_invitation() - response = IntroInvitation(invitation=invite, message=context.message.message) + mgr = OutOfBandManager(profile) + invite = await mgr.create_invitation( + use_did_method="did:peer:4", + hs_protos=[HSProto.DIDEX_1_1], + ) + response = IntroInvitation( + invitation=invite.invitation, message=context.message.message + ) response.assign_thread_from(context.message) response.assign_trace_from(context.message) await responder.send_reply(response) diff --git a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py index e7cdbdf97a..2a740e9db9 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py +++ b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py @@ -1,14 +1,15 @@ from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service from aries_cloudagent.tests import mock from ......connections.models.conn_record import ConnRecord from ......messaging.base_handler import HandlerException from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder -from ......protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) from ...messages.forward_invitation import ForwardInvitation from .. import forward_invitation_handler as test_module @@ -25,14 +26,17 @@ async def asyncSetUp(self): self.context = RequestContext.test_context() self.context.connection_ready = True + service = Service( + did=TEST_DID, + recipient_keys=[TEST_VERKEY], + service_endpoint=TEST_ENDPOINT, + routing_keys=[TEST_ROUTE_VERKEY], + ) self.context.message = ForwardInvitation( - invitation=ConnectionInvitation( + invitation=InvitationMessage( label=TEST_LABEL, - did=TEST_DID, - recipient_keys=[TEST_VERKEY], - endpoint=TEST_ENDPOINT, - routing_keys=[TEST_ROUTE_VERKEY], image_url=TEST_IMAGE_URL, + services=[service], ), message="Hello World", ) @@ -42,7 +46,7 @@ async def test_handle(self): responder = MockResponder() with mock.patch.object( - test_module, "ConnectionManager", autospec=True + test_module, "OutOfBandManager", autospec=True ) as mock_mgr: mock_mgr.return_value.receive_invitation = mock.CoroutineMock( return_value=ConnRecord(connection_id="dummy") @@ -56,10 +60,10 @@ async def test_handle_x(self): responder = MockResponder() with mock.patch.object( - test_module, "ConnectionManager", autospec=True + test_module, "OutOfBandManager", autospec=True ) as mock_mgr: mock_mgr.return_value.receive_invitation = mock.CoroutineMock( - side_effect=test_module.ConnectionManagerError("oops") + side_effect=test_module.OutOfBandManagerError("oops") ) await handler.handle(self.context, responder) diff --git a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py index ff97bbce29..90acb68c36 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py +++ b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py @@ -1,13 +1,14 @@ from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service from aries_cloudagent.tests import mock from ......messaging.base_handler import HandlerException from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder -from ......protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) from ...messages.invitation import Invitation from .. import invitation_handler as test_module @@ -23,13 +24,16 @@ class TestInvitationHandler(IsolatedAsyncioTestCase): async def asyncSetUp(self): self.context = RequestContext.test_context() self.context.connection_ready = True + service = Service( + did=TEST_DID, + recipient_keys=[TEST_VERKEY], + service_endpoint=TEST_ENDPOINT, + routing_keys=[TEST_ROUTE_VERKEY], + ) self.context.message = Invitation( - invitation=ConnectionInvitation( + invitation=InvitationMessage( label=TEST_LABEL, - did=TEST_DID, - recipient_keys=[TEST_VERKEY], - endpoint=TEST_ENDPOINT, - routing_keys=[TEST_ROUTE_VERKEY], + services=[service], image_url=TEST_IMAGE_URL, ), message="Hello World", diff --git a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py index 759882fe30..a25df1000f 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py +++ b/aries_cloudagent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py @@ -1,13 +1,15 @@ from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service +from aries_cloudagent.protocols.out_of_band.v1_0.models.invitation import InvitationRecord from aries_cloudagent.tests import mock from ......messaging.base_handler import HandlerException from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder -from ......protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) from ...messages.invitation import Invitation from ...messages.invitation_request import InvitationRequest from .. import invitation_request_handler as test_module @@ -37,7 +39,7 @@ async def test_handle(self): inv_req = InvitationRequest(responder=responder, message="Hello") with mock.patch.object( - test_module, "ConnectionManager", autospec=True + test_module, "OutOfBandManager", autospec=True ) as mock_mgr: await handler.handle(self.context, responder) @@ -45,26 +47,30 @@ async def test_handle_auto_accept(self): handler = test_module.InvitationRequestHandler() self.context.update_settings({"auto_accept_intro_invitation_requests": True}) - conn_invitation = ConnectionInvitation( - label=TEST_LABEL, + service = Service( did=TEST_DID, recipient_keys=[TEST_VERKEY], - endpoint=TEST_ENDPOINT, + service_endpoint=TEST_ENDPOINT, routing_keys=[TEST_ROUTE_VERKEY], + ) + conn_invitation = InvitationMessage( + label=TEST_LABEL, image_url=TEST_IMAGE_URL, + services=[service], ) mock_conn_rec = mock.MagicMock(connection_id="dummy") + invite_rec = InvitationRecord() responder = MockResponder() with mock.patch.object( - test_module, "ConnectionManager", autospec=True + test_module, "OutOfBandManager", autospec=True ) as mock_mgr: mock_mgr.return_value.create_invitation = mock.CoroutineMock( - return_value=(mock_conn_rec, conn_invitation) + return_value=invite_rec ) await handler.handle(self.context, responder) - mock_mgr.return_value.create_invitation.assert_called_once_with() + mock_mgr.return_value.create_invitation.assert_called_once() messages = responder.messages assert len(messages) == 1 diff --git a/aries_cloudagent/protocols/introduction/v0_1/messages/forward_invitation.py b/aries_cloudagent/protocols/introduction/v0_1/messages/forward_invitation.py index 5a8399c282..bd29ca0983 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/messages/forward_invitation.py +++ b/aries_cloudagent/protocols/introduction/v0_1/messages/forward_invitation.py @@ -5,9 +5,9 @@ from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from .....protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, - ConnectionInvitationSchema, +from ....out_of_band.v1_0.messages.invitation import ( + InvitationMessage, + InvitationMessageSchema, ) from ..message_types import FORWARD_INVITATION, PROTOCOL_PACKAGE @@ -29,7 +29,7 @@ class Meta: def __init__( self, *, - invitation: Optional[ConnectionInvitation] = None, + invitation: Optional[InvitationMessage] = None, message: Optional[str] = None, **kwargs, ): @@ -53,7 +53,7 @@ class Meta: model_class = ForwardInvitation - invitation = fields.Nested(ConnectionInvitationSchema(), required=True) + invitation = fields.Nested(InvitationMessageSchema(), required=True) message = fields.Str( required=False, allow_none=True, diff --git a/aries_cloudagent/protocols/introduction/v0_1/messages/invitation.py b/aries_cloudagent/protocols/introduction/v0_1/messages/invitation.py index 1deb28dbb5..9ad1cc7d4b 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/messages/invitation.py +++ b/aries_cloudagent/protocols/introduction/v0_1/messages/invitation.py @@ -4,11 +4,12 @@ from marshmallow import EXCLUDE, fields -from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from ....connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, - ConnectionInvitationSchema, +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, + InvitationMessageSchema, ) + +from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import INVITATION, PROTOCOL_PACKAGE HANDLER_CLASS = f"{PROTOCOL_PACKAGE}.handlers.invitation_handler.InvitationHandler" @@ -27,7 +28,7 @@ class Meta: def __init__( self, *, - invitation: Optional[ConnectionInvitation] = None, + invitation: Optional[InvitationMessage] = None, message: Optional[str] = None, **kwargs, ): @@ -52,7 +53,7 @@ class Meta: model_class = Invitation unknown = EXCLUDE - invitation = fields.Nested(ConnectionInvitationSchema(), required=True) + invitation = fields.Nested(InvitationMessageSchema(), required=True) message = fields.Str( required=False, allow_none=True, diff --git a/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py b/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py index 65e999ee43..0806d558b0 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py +++ b/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py @@ -1,6 +1,12 @@ from unittest import IsolatedAsyncioTestCase, TestCase, mock -from .....connections.v1_0.messages.connection_invitation import ConnectionInvitation +from aries_cloudagent.did.did_key import DIDKey +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service +from aries_cloudagent.wallet.key_type import ED25519 + from .....didcomm_prefix import DIDCommPrefix from ...message_types import FORWARD_INVITATION, PROTOCOL_PACKAGE from ..forward_invitation import ForwardInvitation @@ -12,13 +18,17 @@ class TestConfig: endpoint_url = "https://example.com/endpoint" endpoint_did = "did:sov:A2wBhNYhMrjHiqZDTUYH7u" key = "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K" + did_key = DIDKey.from_public_key_b58(key, ED25519) test_message = "test message" class TestForwardInvitation(TestCase, TestConfig): def setUp(self): - self.connection_invitation = ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url + self.service = Service( + recipient_keys=[self.key], service_endpoint=self.endpoint_url + ) + self.connection_invitation = InvitationMessage( + label=self.label, services=[self.service] ) self.invitation = ForwardInvitation( invitation=self.connection_invitation, message=self.test_message @@ -64,9 +74,17 @@ class TestForwardInvitationSchema(IsolatedAsyncioTestCase, TestConfig): """Test forward invitation schema.""" async def test_make_model(self): + service = Service( + _id="asdf", + _type="did-communication", + recipient_keys=[self.did_key.did], + service_endpoint=self.endpoint_url, + ) invitation = ForwardInvitation( - invitation=ConnectionInvitation( - label=self.label, recipient_keys=[self.key], endpoint=self.endpoint_url + invitation=InvitationMessage( + label=self.label, + services=[service], + handshake_protocols=["didexchange/1.1"], ), message=self.test_message, ) diff --git a/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_invitation.py b/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_invitation.py index 9d5ae8e226..18b7649a90 100644 --- a/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_invitation.py +++ b/aries_cloudagent/protocols/introduction/v0_1/messages/tests/test_invitation.py @@ -1,6 +1,12 @@ from unittest import IsolatedAsyncioTestCase, mock -from .....connections.v1_0.messages.connection_invitation import ConnectionInvitation +from aries_cloudagent.did.did_key import DIDKey +from aries_cloudagent.protocols.out_of_band.v1_0.messages.invitation import ( + InvitationMessage, +) +from aries_cloudagent.protocols.out_of_band.v1_0.messages.service import Service +from aries_cloudagent.wallet.key_type import ED25519 + from ...message_types import PROTOCOL_PACKAGE from ..invitation import Invitation as IntroInvitation @@ -13,11 +19,19 @@ def setUp(self): self.endpoint_url = "https://example.com/endpoint" self.endpoint_did = "did:sov:A2wBhNYhMrjHiqZDTUYH7u" self.key = "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K" + self.did_key = DIDKey.from_public_key_b58(self.key, ED25519) self.test_message = "test message" - self.conn_invi_msg = ConnectionInvitation( + self.service = Service( + _id="asdf", + _type="did-communication", + recipient_keys=[self.did_key.did], + service_endpoint=self.endpoint_url, + ) + self.conn_invi_msg = InvitationMessage( label=self.label, - did=self.test_did, + services=[self.service], + handshake_protocols=["didexchange/1.1"], ) self.intro_invitation = IntroInvitation( invitation=self.conn_invi_msg, 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 08b982c4a2..4fe2a6a042 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -29,8 +29,6 @@ from ....wallet.did_info import INVITATION_REUSE_KEY, DIDInfo from ....wallet.did_method import PEER2, PEER4 from ....wallet.key_type import ED25519 -from ...connections.v1_0.manager import ConnectionManager -from ...connections.v1_0.messages.connection_invitation import ConnectionInvitation from ...coordinate_mediation.v1_0.models.mediation_record import MediationRecord from ...didcomm_prefix import DIDCommPrefix from ...didexchange.v1_0.manager import DIDXManager @@ -1102,35 +1100,6 @@ async def _perform_handshake( protocol=protocol.name, ) break - # 0160 Connection - elif protocol is HSProto.RFC160: - service.recipient_keys = [ - DIDKey.from_did(key).public_key_b58 - for key in service.recipient_keys or [] - ] - service.routing_keys = [ - DIDKey.from_did(key).public_key_b58 for key in service.routing_keys - ] or [] - msg_type = DIDCommPrefix.qualify_current(protocol.name) + "/invitation" - connection_invitation = ConnectionInvitation.deserialize( - { - "@id": invitation._id, - "@type": msg_type, - "label": invitation.label, - "recipientKeys": service.recipient_keys, - "serviceEndpoint": service.service_endpoint, - "routingKeys": service.routing_keys, - } - ) - conn_mgr = ConnectionManager(self.profile) - conn_record = await conn_mgr.receive_invitation( - invitation=connection_invitation, - their_public_did=public_did, - auto_accept=auto_accept, - alias=alias, - mediation_id=mediation_id, - ) - break if not conn_record: raise OutOfBandManagerError( diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py index d02673361f..f4169dfdb1 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py @@ -13,7 +13,6 @@ ) from .....messaging.valid import DIDValidation from .....wallet.util import b64_to_bytes, bytes_to_b64 -from ....connections.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO from ....didcomm_prefix import DIDCommPrefix from ....didexchange.v1_0.message_types import ARIES_PROTOCOL as DIDEX_1_1 from ....didexchange.v1_0.message_types import DIDEX_1_0 @@ -32,7 +31,7 @@ class HSProto(Enum): """Handshake protocol enum for invitation message.""" RFC160 = HSProtoSpec( - CONN_PROTO, + "connections/1.0", {"connection", "connections", "conn", "conns", "rfc160", "160", "old"}, ) RFC23 = HSProtoSpec( diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py index dd359b0e2a..0af48584e7 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py @@ -5,7 +5,6 @@ from ......did.did_key import DIDKey from ......messaging.models.base import BaseModelError from ......wallet.key_type import ED25519 -from .....connections.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO from .....didcomm_prefix import DIDCommPrefix from .....didexchange.v1_0.message_types import ARIES_PROTOCOL as DIDEX_1_1 from .....didexchange.v1_0.message_types import DIDEX_1_0 @@ -25,7 +24,10 @@ class TestHSProto(TestCase): def test_get(self): assert HSProto.get(HSProto.RFC160) is HSProto.RFC160 assert HSProto.get("Old") is HSProto.RFC160 - assert HSProto.get(DIDCommPrefix.qualify_current(CONN_PROTO)) is HSProto.RFC160 + assert ( + HSProto.get(DIDCommPrefix.qualify_current("connections/1.0")) + is HSProto.RFC160 + ) assert HSProto.get(DIDEX_1_0) is HSProto.RFC23 assert HSProto.get("did-exchange") is HSProto.RFC23 assert HSProto.get("RFC-23") is HSProto.RFC23 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 074bec5d9f..ad169d50a9 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 @@ -69,7 +69,6 @@ from .....wallet.did_method import SOV from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import ED25519 -from ....connections.v1_0.messages.connection_invitation import ConnectionInvitation from ....didcomm_prefix import DIDCommPrefix from ....issue_credential.v1_0.message_types import CREDENTIAL_OFFER from ....issue_credential.v1_0.models.credential_exchange import V10CredentialExchange @@ -1315,61 +1314,6 @@ async def test_receive_invitation_didx_services_with_service_block(self): await self.manager.receive_invitation(oob_invitation) - async def test_receive_invitation_connection_protocol(self): - self.profile.context.update_settings({"public_invites": True}) - - mock_conn = mock.MagicMock(connection_id="dummy-connection") - - with mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as conn_mgr_cls, mock.patch.object( - ConnRecord, "retrieve_by_id", mock.CoroutineMock() - ) as mock_conn_retrieve_by_id: - conn_mgr_cls.return_value = mock.MagicMock( - receive_invitation=mock.CoroutineMock(return_value=mock_conn) - ) - mock_conn_retrieve_by_id.return_value = mock_conn - oob_invitation = InvitationMessage( - handshake_protocols=[ - pfx.qualify(HSProto.RFC160.name) for pfx in DIDCommPrefix - ], - label="test", - _id="test123", - services=[ - OobService( - recipient_keys=[ - DIDKey.from_public_key_b58( - "9WCgWKUaAJj3VWxxtzvvMQN3AoFxoBtBDo9ntwJnVVCC", - ED25519, - ).did - ], - routing_keys=[], - service_endpoint="http://localhost", - ) - ], - requests_attach=[], - ) - oob_record = await self.manager.receive_invitation(oob_invitation) - conn_mgr_cls.return_value.receive_invitation.assert_called_once_with( - invitation=ANY, - their_public_did=None, - auto_accept=None, - alias=None, - mediation_id=None, - ) - _, kwargs = conn_mgr_cls.return_value.receive_invitation.call_args - invitation = kwargs["invitation"] - assert isinstance(invitation, ConnectionInvitation) - - assert invitation.endpoint == "http://localhost" - assert invitation.recipient_keys == [ - "9WCgWKUaAJj3VWxxtzvvMQN3AoFxoBtBDo9ntwJnVVCC" - ] - assert not invitation.routing_keys - - assert oob_record.state == "deleted" - assert oob_record._previous_state == OobRecord.STATE_DONE - async def test_receive_invitation_services_with_neither_service_blocks_nor_dids( self, ): diff --git a/aries_cloudagent/protocols/routing/v1_0/handlers/forward_handler.py b/aries_cloudagent/protocols/routing/v1_0/handlers/forward_handler.py index cc12fad775..78cbbd6114 100644 --- a/aries_cloudagent/protocols/routing/v1_0/handlers/forward_handler.py +++ b/aries_cloudagent/protocols/routing/v1_0/handlers/forward_handler.py @@ -8,7 +8,7 @@ HandlerException, RequestContext, ) -from .....protocols.connections.v1_0.manager import ConnectionManager +from .....connections.base_manager import BaseConnectionManager from ..manager import RoutingManager, RoutingManagerError from ..messages.forward import Forward @@ -39,7 +39,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): return # load connection - connection_mgr = ConnectionManager(context.profile) + connection_mgr = BaseConnectionManager(context.profile) connection_targets = await connection_mgr.get_connection_targets( connection_id=recipient.connection_id ) diff --git a/aries_cloudagent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py b/aries_cloudagent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py index 972479f025..3ed8d0e0b4 100644 --- a/aries_cloudagent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py +++ b/aries_cloudagent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py @@ -31,7 +31,7 @@ async def test_handle(self): with mock.patch.object( test_module, "RoutingManager", autospec=True ) as mock_mgr, mock.patch.object( - test_module, "ConnectionManager", autospec=True + test_module, "BaseConnectionManager", autospec=True ) as mock_connection_mgr, mock.patch.object( self.context.profile, "notify", autospec=True ) as mock_notify: diff --git a/aries_cloudagent/utils/endorsement_setup.py b/aries_cloudagent/utils/endorsement_setup.py index ca02d9e4d1..6c1e588097 100644 --- a/aries_cloudagent/utils/endorsement_setup.py +++ b/aries_cloudagent/utils/endorsement_setup.py @@ -4,10 +4,6 @@ from ..connections.models.conn_record import ConnRecord from ..core.profile import Profile -from ..protocols.connections.v1_0.manager import ConnectionManager -from ..protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) from ..protocols.endorse_transaction.v1_0.manager import TransactionManager from ..protocols.endorse_transaction.v1_0.transaction_jobs import TransactionJob from ..protocols.endorse_transaction.v1_0.util import ( @@ -62,19 +58,7 @@ async def attempt_auto_author_with_endorser_setup(profile: Profile): session, oob_record.connection_id ) else: - invite = ConnectionInvitation.from_url(endorser_invitation) - if invite: - conn_mgr = ConnectionManager(profile) - conn_record = await conn_mgr.receive_invitation( - invitation=invite, - auto_accept=True, - alias=endorser_alias, - ) - else: - raise Exception( - "Failed to establish endorser connection, invalid " - "invitation format." - ) + raise ValueError("Invalid OOB invitation url") # configure the connection role and info (don't need to wait for the connection) transaction_mgr = TransactionManager(profile) diff --git a/scenarios/examples/mediation/example.py b/scenarios/examples/mediation/example.py index 9a7851c04f..f4c84fdc51 100644 --- a/scenarios/examples/mediation/example.py +++ b/scenarios/examples/mediation/example.py @@ -9,7 +9,6 @@ from acapy_controller import Controller from acapy_controller.logging import logging_to_stdout from acapy_controller.protocols import ( - connection, didexchange, request_mediation_v1, trustping, @@ -35,7 +34,7 @@ async def main(): ab, ba = await didexchange(alice, bob) await trustping(alice, ab) - ab, ba = await connection(alice, bob) + ab, ba = await didexchange(alice, bob) await trustping(alice, ab) diff --git a/scenarios/examples/simple/example.py b/scenarios/examples/simple/example.py index 20830dfcc9..a41994e569 100644 --- a/scenarios/examples/simple/example.py +++ b/scenarios/examples/simple/example.py @@ -8,7 +8,7 @@ from acapy_controller import Controller from acapy_controller.logging import logging_to_stdout -from acapy_controller.protocols import connection, didexchange +from acapy_controller.protocols import didexchange ALICE = getenv("ALICE", "http://alice:3001") BOB = getenv("BOB", "http://bob:3001") @@ -17,7 +17,6 @@ async def main(): """Test Controller protocols.""" async with Controller(base_url=ALICE) as alice, Controller(base_url=BOB) as bob: - await connection(alice, bob) await didexchange(alice, bob)