diff --git a/acapy_agent/config/argparse.py b/acapy_agent/config/argparse.py index bf911a2a5a..f667aec4d4 100644 --- a/acapy_agent/config/argparse.py +++ b/acapy_agent/config/argparse.py @@ -295,15 +295,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", @@ -441,8 +432,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: @@ -1463,24 +1452,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/acapy_agent/config/default_context.py b/acapy_agent/config/default_context.py index 136c79791d..f405a5e807 100644 --- a/acapy_agent/config/default_context.py +++ b/acapy_agent/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.""" @@ -135,6 +142,7 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("acapy_agent.ledger") + plugin_registry.register_plugin("acapy_agent.connections") plugin_registry.register_plugin("acapy_agent.messaging.jsonld") plugin_registry.register_plugin("acapy_agent.resolver") plugin_registry.register_plugin("acapy_agent.settings") diff --git a/acapy_agent/config/logging/configurator.py b/acapy_agent/config/logging/configurator.py index 11763dca08..680fb92c2e 100644 --- a/acapy_agent/config/logging/configurator.py +++ b/acapy_agent/config/logging/configurator.py @@ -17,7 +17,7 @@ from typing import Optional import yaml -from pythonjsonlogger import jsonlogger +from pythonjsonlogger.json import JsonFormatter from ...config.settings import Settings from ...version import __version__ @@ -221,9 +221,7 @@ def _configure_multitenant_logging(cls, log_config_path, log_level, log_file): ) timed_file_handler.addFilter(log_filter) # By default this will be set up. - timed_file_handler.setFormatter( - jsonlogger.JsonFormatter(LOG_FORMAT_FILE_ALIAS_PATTERN) - ) + timed_file_handler.setFormatter(JsonFormatter(LOG_FORMAT_FILE_ALIAS_PATTERN)) logging.root.handlers.append(timed_file_handler) else: @@ -234,7 +232,7 @@ def _configure_multitenant_logging(cls, log_config_path, log_level, log_file): # Set Json formatter for rotated file handler which cannot be set with # config file. # By default this will be set up. - handler.setFormatter(jsonlogger.JsonFormatter(log_formater)) + handler.setFormatter(JsonFormatter(log_formater)) # Add context filter to handlers handler.addFilter(log_filter) diff --git a/acapy_agent/connections/base_manager.py b/acapy_agent/connections/base_manager.py index 549a394a19..cd06b8f89f 100644 --- a/acapy_agent/connections/base_manager.py +++ b/acapy_agent/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) + assert invitation.services, "Schema requires services in 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/acapy_agent/connections/models/conn_record.py b/acapy_agent/connections/models/conn_record.py index e27be92c4f..2f751ffd83 100644 --- a/acapy_agent/connections/models/conn_record.py +++ b/acapy_agent/connections/models/conn_record.py @@ -15,16 +15,6 @@ RAW_ED25519_2018_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/acapy_agent/connections/models/tests/test_conn_record.py b/acapy_agent/connections/models/tests/test_conn_record.py index 9f17054148..cc15ddb0d3 100644 --- a/acapy_agent/connections/models/tests/test_conn_record.py +++ b/acapy_agent/connections/models/tests/test_conn_record.py @@ -1,15 +1,14 @@ from unittest import IsolatedAsyncioTestCase -from ....protocols.connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, -) -from ....protocols.connections.v1_0.messages.connection_request import ConnectionRequest -from ....protocols.connections.v1_0.models.connection_detail import ConnectionDetail +from ....did.did_key import DIDKey +from ....protocols.didexchange.v1_0.messages.request import DIDXRequest +from ....protocols.out_of_band.v1_0.messages.invitation import InvitationMessage +from ....protocols.out_of_band.v1_0.messages.service import Service from ....storage.base import BaseStorage from ....storage.error import StorageNotFoundError from ....utils.testing import create_test_profile +from ....wallet.key_type import ED25519 from ..conn_record import ConnRecord -from ..diddoc.diddoc import DIDDoc class TestConnRecord(IsolatedAsyncioTestCase): @@ -32,6 +31,7 @@ async def asyncSetUp(self): self.test_did_peer_4_short_b = ( "did:peer:4zQmQ4dEtoGcivpiH6gtWwhWJY2ENVWuZifb62uzR76HGPPw" ) + self.test_did_key = DIDKey.from_public_key_b58(self.test_verkey, ED25519) self.test_conn_record = ConnRecord( my_did=self.test_did, @@ -369,14 +369,20 @@ async def test_attach_retrieve_invitation(self): ) await record.save(session) - invi = ConnectionInvitation( + service = Service( + _id="asdf", + _type="did-communication", + recipient_keys=[self.test_did_key.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(session, invi) retrieved = await record.retrieve_invitation(session) - assert isinstance(retrieved, ConnectionInvitation) + assert isinstance(retrieved, InvitationMessage) async def test_attach_retrieve_request(self): async with self.profile.session() as session: @@ -386,15 +392,13 @@ async def test_attach_retrieve_request(self): ) await record.save(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(session, req) retrieved = await record.retrieve_request(session) - assert isinstance(retrieved, ConnectionRequest) + assert isinstance(retrieved, DIDXRequest) async def test_attach_request_abstain_on_alien_deco(self): async with self.profile.session() as session: @@ -404,23 +408,21 @@ async def test_attach_request_abstain_on_alien_deco(self): ) await record.save(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(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(session, alien_req) alien_ser = alien_req.serialize() assert "~alien" not in alien_ser @@ -451,11 +453,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): async with self.profile.session() as session: diff --git a/acapy_agent/protocols/connections/v1_0/routes.py b/acapy_agent/connections/routes.py similarity index 55% rename from acapy_agent/protocols/connections/v1_0/routes.py rename to acapy_agent/connections/routes.py index 63344e5749..a0edc407ea 100644 --- a/acapy_agent/protocols/connections/v1_0/routes.py +++ b/acapy_agent/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 @@ RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, RAW_ED25519_2018_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 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=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Recipient public key", - "example": RAW_ED25519_2018_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=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Routing key", - "example": RAW_ED25519_2018_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.""" @@ -538,7 +374,7 @@ async def connections_endpoints(request: web.BaseRequest): connection_id = request.match_info["conn_id"] profile = context.profile - connection_mgr = ConnectionManager(profile) + connection_mgr = BaseConnectionManager(profile) try: endpoints = await connection_mgr.get_endpoints(connection_id) except StorageNotFoundError as err: @@ -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="") @@ -859,7 +485,7 @@ async def connections_create_static(request: web.BaseRequest): body = await request.json() profile = context.profile - connection_mgr = ConnectionManager(profile) + connection_mgr = BaseConnectionManager(profile) try: ( my_info, @@ -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/acapy_agent/connections/tests/test_base_manager.py b/acapy_agent/connections/tests/test_base_manager.py index 12aa05d8a7..5d4ff0cd6b 100644 --- a/acapy_agent/connections/tests/test_base_manager.py +++ b/acapy_agent/connections/tests/test_base_manager.py @@ -1,9 +1,10 @@ """Test connections base manager.""" +import secrets from unittest import IsolatedAsyncioTestCase from unittest.mock import call -import pytest +import base58 from pydid import DID, DIDDocument, DIDDocumentBuilder from pydid.doc.builder import ServiceBuilder from pydid.verification_method import ( @@ -12,6 +13,7 @@ Ed25519VerificationKey2020, JsonWebKey2020, ) +import pytest from ...cache.base import BaseCache from ...cache.in_memory import InMemoryCache @@ -25,17 +27,16 @@ 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, ) from ...protocols.discovery.v2_0.manager import V20DiscoveryMgr +from ...protocols.out_of_band.v1_0.messages.invitation import InvitationMessage +from ...protocols.out_of_band.v1_0.messages.service import Service as OOBService 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 ...tests import mock @@ -44,7 +45,7 @@ from ...utils.testing import create_test_profile from ...wallet.askar import AskarWallet from ...wallet.base import BaseWallet, DIDInfo -from ...wallet.did_method import SOV, DIDMethods +from ...wallet.did_method import DIDMethods, SOV from ...wallet.error import WalletNotFoundError from ...wallet.key_type import ED25519, KeyTypes from ...wallet.util import b58_to_bytes, bytes_to_b64 @@ -89,6 +90,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 = await create_test_profile( { @@ -120,114 +122,15 @@ async def asyncSetUp(self): self.test_mediator_endpoint = "http://mediator.example.com" 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, - ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + info = await wallet.create_local_did(method=SOV, key_type=ED25519) - await self.manager.create_did_document( - did_info=did_info, - svc_endpoints=[self.test_endpoint], - ) + self.did = info.did + self.verkey = info.verkey - 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 + assert self.manager._profile async def test_did_key_storage(self): await self.manager.add_key_for_did( @@ -246,164 +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: - wallet = session.inject(BaseWallet) - self.profile.context.injector.bind_instance(DIDResolver, DIDResolver([])) - - await 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: - wallet = session.inject(BaseWallet) - 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 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_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_connection_targets_conn_invitation_btcr_resolver(self): - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - 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, + 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() - 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, + 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 ) - did_doc = builder.build() + mock_resolve_invitation.assert_called() - 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 wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=did_doc.id, - metadata=None, - ) + 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)]) - 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), - ) + 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: @@ -430,79 +252,73 @@ 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) - await 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: - wallet = session.inject(BaseWallet) - 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 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: @@ -520,49 +336,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 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), - ) + endpoint, recips, routes = await self.manager.resolve_invitation(did_doc.id) + assert endpoint == self.test_endpoint + assert recips == [self.test_target_verkey] + assert routes == [] - 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 - - 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: @@ -581,49 +367,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 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: @@ -644,49 +400,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 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: wallet = session.inject(BaseWallet) builder = DIDDocumentBuilder("did:btcr:x705-jznz-q3nl-srs") @@ -706,170 +432,15 @@ 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) - await 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: - wallet = session.inject(BaseWallet) - self.context.injector.bind_instance(DIDResolver, DIDResolver([])) - await 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: - wallet = session.inject(BaseWallet) - 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 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: - wallet = session.inject(BaseWallet) - 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 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: @@ -1255,66 +826,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: - wallet = session.inject(BaseWallet) - local_did = await 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: wallet = session.inject(BaseWallet) @@ -1332,11 +843,15 @@ 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], + 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], + ) + invitation = InvitationMessage( + services=[service], label="label", ) mock_conn = mock.MagicMock( @@ -1345,7 +860,7 @@ async def test_get_connection_targets_retrieve_connection(self): 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 ( @@ -1365,10 +880,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.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys + assert target.endpoint == service.service_endpoint + assert target.label == invitation.label + 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): @@ -1474,7 +989,7 @@ async def test_get_connection_targets_no_conn_or_id(self): with self.assertRaises(ValueError): await self.manager.get_connection_targets() - async def test_get_conn_targets_conn_invitation_no_cache(self): + async def test_get_conn_targets_invitation_no_cache(self): async with self.profile.session() as session: wallet = session.inject(BaseWallet) self.context.injector.clear_binding(BaseCache) @@ -1491,11 +1006,15 @@ 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], + 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], + ) + invitation = InvitationMessage( + services=[service], label="label", ) mock_conn = mock.MagicMock( @@ -1504,7 +1023,7 @@ async def test_get_conn_targets_conn_invitation_no_cache(self): 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), ) targets = await self.manager.get_connection_targets( @@ -1514,10 +1033,10 @@ 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.label == conn_invite.label - assert target.recipient_keys == conn_invite.recipient_keys - assert target.routing_keys == conn_invite.routing_keys + assert target.endpoint == service.service_endpoint + assert target.label == invitation.label + 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): @@ -1526,7 +1045,7 @@ async def test_create_static_connection(self): self.context.injector.bind_instance(BaseMultitenantManager, self.multitenant_mgr) _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/acapy_agent/core/conductor.py b/acapy_agent/core/conductor.py index ae62d3c94a..eea46cd716 100644 --- a/acapy_agent/core/conductor.py +++ b/acapy_agent/core/conductor.py @@ -29,6 +29,7 @@ from ..config.logging import LoggingConfigurator from ..config.provider import ClassProvider from ..config.wallet import wallet_config +from ..connections.base_manager import BaseConnectionManager, BaseConnectionManagerError from ..core.profile import Profile from ..indy.verifier import IndyVerifier from ..ledger.base import BaseLedger @@ -42,13 +43,6 @@ 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 ..protocols.coordinate_mediation.mediation_invite_store import MediationInviteStore from ..protocols.coordinate_mediation.v1_0.manager import MediationManager from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager @@ -120,6 +114,7 @@ def __init__(self, context_builder: ContextBuilder) -> None: @property def context(self) -> InjectionContext: """Accessor for the injection context.""" + assert self.root_profile, "root_profile is not set" return self.root_profile.context async def setup(self): @@ -273,7 +268,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", @@ -284,6 +279,7 @@ async def setup(self): async def start(self) -> None: """Start the agent.""" + assert self.root_profile, "root_profile is not set" context = self.root_profile.context await self.check_for_valid_wallet_type(self.root_profile) @@ -393,9 +389,9 @@ 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( + _, _, test_conn = await mgr.create_static_connection( my_seed=hashlib.sha256(b"aries-protocol-test-subject").digest(), their_seed=hashlib.sha256(b"aries-protocol-test-suite").digest(), their_endpoint=their_endpoint, @@ -449,29 +445,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 +461,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 +678,13 @@ 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: + assert self.dispatcher, "dispatcher is not set" 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/acapy_agent/core/tests/test_conductor.py b/acapy_agent/core/tests/test_conductor.py index 6b66d1fa73..042cd1974d 100644 --- a/acapy_agent/core/tests/test_conductor.py +++ b/acapy_agent/core/tests/test_conductor.py @@ -1,8 +1,8 @@ -from io import StringIO from unittest import IsolatedAsyncioTestCase import pytest +from ...connections.base_manager import BaseConnectionManager from ...admin.base_server import BaseAdminServer from ...askar.profile import AskarProfileManager from ...config.base_context import ContextBuilder @@ -103,6 +103,7 @@ async def build_context(self) -> InjectionContext: InboundTransportManager, mock.MagicMock(InboundTransportManager, autospec=True), ) + context.injector.bind_instance(BaseConnectionManager, mock.MagicMock()) return context @@ -667,6 +668,7 @@ async def test_outbound_message_handler_with_target(self): } await conductor.setup() + assert conductor.root_profile bus = conductor.root_profile.inject(EventBus) payload = "{}" @@ -704,15 +706,16 @@ async def test_outbound_message_handler_with_connection(self): mock.patch.object( test_module, "OutboundTransportManager", autospec=True ) as mock_outbound_mgr, - mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as conn_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) + assert conductor.root_profile bus = conductor.root_profile.inject(EventBus) payload = "{}" @@ -728,13 +731,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 @@ -820,10 +820,12 @@ async def test_handle_nots(self): await conductor.setup() + assert conductor.root_profile 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, @@ -832,7 +834,7 @@ async def test_handle_nots(self): # 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() @@ -941,10 +943,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, @@ -955,7 +956,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" ) @@ -1046,7 +1047,7 @@ async def test_admin_startx(self): mock.patch.object(admin, "start", autospec=True) as admin_start, mock.patch.object(admin, "stop", autospec=True) as admin_stop, mock.patch.object(test_module, "OutOfBandManager") as oob_mgr, - mock.patch.object(test_module, "ConnectionManager") as conn_mgr, + mock.patch.object(test_module, "BaseConnectionManager") as conn_mgr, ): admin_start.side_effect = KeyError("trouble") oob_mgr.return_value.create_invitation = mock.CoroutineMock( @@ -1100,7 +1101,7 @@ async def test_start_static(self): DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), ), ), - mock.patch.object(test_module, "ConnectionManager") as mock_mgr, + mock.patch.object(test_module, "BaseConnectionManager") as mock_mgr, mock.patch.object( test_module, "OutboundTransportManager", autospec=True ) as mock_outbound_mgr, @@ -1110,7 +1111,9 @@ async def test_start_static(self): } await conductor.setup() - mock_mgr.return_value.create_static_connection = mock.CoroutineMock() + mock_mgr.return_value.create_static_connection = mock.CoroutineMock( + return_value=(None, None, mock.MagicMock()) + ) await conductor.start() mock_mgr.return_value.create_static_connection.assert_awaited_once() @@ -1130,7 +1133,7 @@ async def test_start_x_in(self): DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), ), ), - mock.patch.object(test_module, "ConnectionManager") as mock_mgr, + mock.patch.object(test_module, "BaseConnectionManager") as mock_mgr, mock.patch.object(test_module, "InboundTransportManager") as mock_intx_mgr, mock.patch.object( test_module, "OutboundTransportManager", autospec=True @@ -1164,7 +1167,7 @@ async def test_start_x_out_a(self): DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), ), ), - mock.patch.object(test_module, "ConnectionManager") as mock_mgr, + mock.patch.object(test_module, "BaseConnectionManager") as mock_mgr, mock.patch.object(test_module, "OutboundTransportManager") as mock_outx_mgr, ): mock_outx_mgr.return_value = mock.MagicMock( @@ -1192,7 +1195,7 @@ async def test_start_x_out_b(self): DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), ), ), - mock.patch.object(test_module, "ConnectionManager") as mock_mgr, + mock.patch.object(test_module, "BaseConnectionManager") as mock_mgr, mock.patch.object(test_module, "OutboundTransportManager") as mock_outx_mgr, ): mock_outx_mgr.return_value = mock.MagicMock( @@ -1297,48 +1300,6 @@ async def test_dispatch_complete_fatal_x(self): conductor.dispatch_complete(message, mock_task) mock_notify.assert_called_once_with() - @pytest.mark.filterwarnings("ignore:Aries RFC 0160.*:DeprecationWarning") - 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) - - test_profile = await create_test_profile(None, await builder.build_context()) - - with ( - mock.patch.object( - test_module, - "wallet_config", - return_value=( - test_profile, - DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), - ), - ), - mock.patch("sys.stdout", new=StringIO()) as captured, - 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() - - 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}) @@ -1582,61 +1543,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, _): - builder = self.__get_mediator_config("test-invite", True) - conductor = test_module.Conductor(builder) - test_profile = await create_test_profile(None, await builder.build_context()) - - with ( - mock.patch.object( - test_module, - "wallet_config", - return_value=( - test_profile, - DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), - ), - ), - 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, - ), - ): - 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", @@ -1709,73 +1615,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" - - builder = self.__get_mediator_config("test-invite", True) - conductor = test_module.Conductor(builder) - test_profile = await create_test_profile(None, await builder.build_context()) - - with ( - mock.patch.object( - test_module, - "wallet_config", - return_value=( - test_profile, - DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), - ), - ), - 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 - ), - ): - 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 ): @@ -1817,47 +1657,6 @@ async def test_mediation_invitation_should_not_create_connection_for_old_invitat invite_store_mock.get_mediation_invite_record.assert_called_with(invite_string) 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, _): - builder = self.__get_mediator_config("test-invite", True) - conductor = test_module.Conductor(builder) - test_profile = await create_test_profile(None, await builder.build_context()) - - with ( - mock.patch.object( - test_module, - "wallet_config", - return_value=( - test_profile, - DIDInfo("did", "verkey", metadata={}, method=SOV, key_type=ED25519), - ), - ), - 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, - ): - 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/acapy_agent/core/tests/test_oob_processor.py b/acapy_agent/core/tests/test_oob_processor.py index 4b3e85bedb..6221e7cafb 100644 --- a/acapy_agent/core/tests/test_oob_processor.py +++ b/acapy_agent/core/tests/test_oob_processor.py @@ -6,9 +6,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 @@ -40,7 +37,7 @@ async def asyncSetUp(self): save=mock.CoroutineMock(), ) self.context = RequestContext.test_context(self.profile) - 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/acapy_agent/messaging/jsonld/tests/test_routes.py b/acapy_agent/messaging/jsonld/tests/test_routes.py index a98d0818f2..b7b1799d7f 100644 --- a/acapy_agent/messaging/jsonld/tests/test_routes.py +++ b/acapy_agent/messaging/jsonld/tests/test_routes.py @@ -303,6 +303,10 @@ async def asyncSetUp(self): headers={"x-api-key": "secret-key"}, ) + async def asyncTearDown(self): + # Ensure the event loop is closed + await self.profile.close() + async def test_verify_credential(self): POSTED_REQUEST = { # posted json "verkey": ( diff --git a/acapy_agent/messaging/models/base.py b/acapy_agent/messaging/models/base.py index d9b17231a3..7bed94aa95 100644 --- a/acapy_agent/messaging/models/base.py +++ b/acapy_agent/messaging/models/base.py @@ -240,7 +240,7 @@ def serialize( if as_string else schema.dump(self) ) - except (AttributeError, ValidationError) as err: + except (AttributeError, ValidationError, TypeError) as err: LOGGER.exception(f"{self.__class__.__name__} message serialization error:") raise BaseModelError( f"{self.__class__.__name__} schema validation failed" @@ -321,7 +321,6 @@ class Meta: model_class = None skip_values = [None] - ordered = True def __init__(self, *args, **kwargs): """Initialize BaseModelSchema. diff --git a/acapy_agent/messaging/models/paginated_query.py b/acapy_agent/messaging/models/paginated_query.py index c6e6e99795..a929baedc8 100644 --- a/acapy_agent/messaging/models/paginated_query.py +++ b/acapy_agent/messaging/models/paginated_query.py @@ -4,6 +4,7 @@ from aiohttp.web import BaseRequest from marshmallow import fields +from marshmallow.validate import Range from ...messaging.models.openapi import OpenAPISchema from ...storage.base import DEFAULT_PAGE_SIZE, MAXIMUM_PAGE_SIZE @@ -15,21 +16,14 @@ class PaginatedQuerySchema(OpenAPISchema): limit = fields.Int( required=False, load_default=DEFAULT_PAGE_SIZE, - validate=lambda x: x > 0 and x <= MAXIMUM_PAGE_SIZE, + validate=Range(min=1, max=MAXIMUM_PAGE_SIZE), metadata={"description": "Number of results to return", "example": 50}, - error_messages={ - "validator_failed": ( - "Value must be greater than 0 and " - f"less than or equal to {MAXIMUM_PAGE_SIZE}" - ) - }, ) offset = fields.Int( required=False, load_default=0, - validate=lambda x: x >= 0, + validate=Range(min=0), metadata={"description": "Offset for pagination", "example": 0}, - error_messages={"validator_failed": "Value must be 0 or greater"}, ) diff --git a/acapy_agent/messaging/models/tests/test_paginated_query.py b/acapy_agent/messaging/models/tests/test_paginated_query.py index 188c496ef8..5ac59f9669 100644 --- a/acapy_agent/messaging/models/tests/test_paginated_query.py +++ b/acapy_agent/messaging/models/tests/test_paginated_query.py @@ -38,7 +38,7 @@ def test_paginated_query_schema_limit_validation(): with pytest.raises(ValidationError) as exc_info: schema.load({"limit": 0}) assert ( - f"Value must be greater than 0 and less than or equal to {MAXIMUM_PAGE_SIZE}" + f"Must be greater than or equal to 1 and less than or equal to {MAXIMUM_PAGE_SIZE}" in str(exc_info.value) ) @@ -46,7 +46,7 @@ def test_paginated_query_schema_limit_validation(): with pytest.raises(ValidationError) as exc_info: schema.load({"limit": MAXIMUM_PAGE_SIZE + 1}) assert ( - f"Value must be greater than 0 and less than or equal to {MAXIMUM_PAGE_SIZE}" + f"Must be greater than or equal to 1 and less than or equal to {MAXIMUM_PAGE_SIZE}" in str(exc_info.value) ) @@ -64,4 +64,4 @@ def test_paginated_query_schema_offset_validation(): # Invalid offset (less than 0) with pytest.raises(ValidationError) as exc_info: schema.load({"offset": -1}) - assert "Value must be 0 or greater" in str(exc_info.value) + assert "Must be greater than or equal to 0." in str(exc_info.value) diff --git a/acapy_agent/protocols/connections/__init__.py b/acapy_agent/protocols/connections/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/definition.py b/acapy_agent/protocols/connections/definition.py deleted file mode 100644 index 62bddef6f5..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/__init__.py b/acapy_agent/protocols/connections/v1_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/handlers/__init__.py b/acapy_agent/protocols/connections/v1_0/handlers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/handlers/connection_invitation_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/connection_invitation_handler.py deleted file mode 100644 index 124e9130e3..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/handlers/connection_request_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/connection_request_handler.py deleted file mode 100644 index 042759f225..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/handlers/connection_response_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/connection_response_handler.py deleted file mode 100644 index 61fd814362..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/handlers/problem_report_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/problem_report_handler.py deleted file mode 100644 index 8be8ec31fd..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/handlers/tests/__init__.py b/acapy_agent/protocols/connections/v1_0/handlers/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py deleted file mode 100644 index 5c51c04f61..0000000000 --- a/acapy_agent/protocols/connections/v1_0/handlers/tests/test_invitation_handler.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from ......messaging.request_context import RequestContext -from ......messaging.responder import MockResponder -from ......transport.inbound.receipt import MessageReceipt -from ......utils.testing import create_test_profile -from ...handlers.connection_invitation_handler import ConnectionInvitationHandler -from ...messages.connection_invitation import ConnectionInvitation -from ...messages.problem_report import ConnectionProblemReport, ProblemReportReason - - -@pytest.fixture() -async def request_context(): - ctx = RequestContext.test_context(await create_test_profile()) - 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/acapy_agent/protocols/connections/v1_0/handlers/tests/test_request_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/tests/test_request_handler.py deleted file mode 100644 index 57b7dc76ed..0000000000 --- a/acapy_agent/protocols/connections/v1_0/handlers/tests/test_request_handler.py +++ /dev/null @@ -1,274 +0,0 @@ -import pytest - -from acapy_agent.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 ......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 ......utils.testing import create_test_profile -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(): - ctx = RequestContext.test_context(await create_test_profile()) - ctx.message_receipt = MessageReceipt() - yield ctx - - -@pytest.fixture() -async def session(request_context): - yield await request_context.session() - - -@pytest.fixture() -async def connection_record(request_context, session): - 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), - ): - 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/acapy_agent/protocols/connections/v1_0/handlers/tests/test_response_handler.py b/acapy_agent/protocols/connections/v1_0/handlers/tests/test_response_handler.py deleted file mode 100644 index 414a8e0d39..0000000000 --- a/acapy_agent/protocols/connections/v1_0/handlers/tests/test_response_handler.py +++ /dev/null @@ -1,203 +0,0 @@ -import pytest - -from acapy_agent.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 ......utils.testing import create_test_profile -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() -async def request_context(): - ctx = RequestContext.test_context(await create_test_profile()) - 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, _ = 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/acapy_agent/protocols/connections/v1_0/manager.py b/acapy_agent/protocols/connections/v1_0/manager.py deleted file mode 100644 index 3767c76998..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/message_types.py b/acapy_agent/protocols/connections/v1_0/message_types.py deleted file mode 100644 index 9ee29cd36b..0000000000 --- a/acapy_agent/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 = "acapy_agent.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/acapy_agent/protocols/connections/v1_0/messages/__init__.py b/acapy_agent/protocols/connections/v1_0/messages/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py b/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py deleted file mode 100644 index 51e9f5f3c2..0000000000 --- a/acapy_agent/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, - RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, - RAW_ED25519_2018_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=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Recipient public key", - "example": RAW_ED25519_2018_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=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, - metadata={ - "description": "Routing key", - "example": RAW_ED25519_2018_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/acapy_agent/protocols/connections/v1_0/messages/connection_request.py b/acapy_agent/protocols/connections/v1_0/messages/connection_request.py deleted file mode 100644 index d1a6940be5..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/messages/connection_response.py b/acapy_agent/protocols/connections/v1_0/messages/connection_response.py deleted file mode 100644 index aeb9512314..0000000000 --- a/acapy_agent/protocols/connections/v1_0/messages/connection_response.py +++ /dev/null @@ -1,48 +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/acapy_agent/protocols/connections/v1_0/messages/problem_report.py b/acapy_agent/protocols/connections/v1_0/messages/problem_report.py deleted file mode 100644 index 9520424131..0000000000 --- a/acapy_agent/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 = ( - "acapy_agent.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/acapy_agent/protocols/connections/v1_0/messages/tests/__init__.py b/acapy_agent/protocols/connections/v1_0/messages/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py b/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py deleted file mode 100644 index 0149bd0224..0000000000 --- a/acapy_agent/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( - "acapy_agent.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( - "acapy_agent.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/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_request.py b/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_request.py deleted file mode 100644 index f5ea7e4433..0000000000 --- a/acapy_agent/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( - "acapy_agent.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( - "acapy_agent.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/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_response.py b/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_response.py deleted file mode 100644 index d3f08ad618..0000000000 --- a/acapy_agent/protocols/connections/v1_0/messages/tests/test_connection_response.py +++ /dev/null @@ -1,106 +0,0 @@ -from unittest import IsolatedAsyncioTestCase, TestCase, mock - -from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service -from ......utils.testing import create_test_profile -from ......wallet.base import BaseWallet -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( - "acapy_agent.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( - "acapy_agent.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()) - ) - self.profile = await create_test_profile() - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - 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/acapy_agent/protocols/connections/v1_0/models/__init__.py b/acapy_agent/protocols/connections/v1_0/models/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/models/connection_detail.py b/acapy_agent/protocols/connections/v1_0/models/connection_detail.py deleted file mode 100644 index 6029cb1614..0000000000 --- a/acapy_agent/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/acapy_agent/protocols/connections/v1_0/tests/__init__.py b/acapy_agent/protocols/connections/v1_0/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acapy_agent/protocols/connections/v1_0/tests/test_manager.py b/acapy_agent/protocols/connections/v1_0/tests/test_manager.py deleted file mode 100644 index eb2c5b7f6d..0000000000 --- a/acapy_agent/protocols/connections/v1_0/tests/test_manager.py +++ /dev/null @@ -1,1241 +0,0 @@ -from unittest import IsolatedAsyncioTestCase - -import pytest - -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.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 .....tests import mock -from .....transport.inbound.receipt import MessageReceipt -from .....utils.testing import create_test_profile -from .....wallet.askar import AskarWallet -from .....wallet.base import BaseWallet, DIDInfo -from .....wallet.did_method import SOV, DIDMethods -from .....wallet.key_type import ED25519, KeyTypes -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 - - -@pytest.mark.filterwarnings("ignore:Aries RFC 0160.*:DeprecationWarning") -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(OobMessageProcessor, autospec=True) - self.oob_mock.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 = await create_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, - }, - ) - - self.profile.context.injector.bind_instance(BaseResponder, self.responder) - self.profile.context.injector.bind_instance(BaseCache, InMemoryCache()) - self.profile.context.injector.bind_instance(OobMessageProcessor, self.oob_mock) - self.profile.context.injector.bind_instance(RouteManager, self.route_manager) - self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) - self.profile.context.injector.bind_instance(DIDResolver, self.resolver) - self.profile.context.injector.bind_instance(KeyTypes, KeyTypes()) - 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, _ = await self.manager.create_invitation() - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - request_a = 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(request_a, receipt) - - request_b = 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(request_b, 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( - AskarWallet, "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( - AskarWallet, "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, _ = await self.manager.create_invitation( - my_endpoint="testendpoint", multi_use=True - ) - - receipt = MessageReceipt(recipient_verkey=connect_record.invitation_key) - - request_a = 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(request_a, receipt) - - request_b = 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(request_b, receipt) - - async def test_create_invitation_recipient_routing_endpoint(self): - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - await wallet.create_local_did( - method=SOV, - key_type=ED25519, - seed=self.test_seed, - did=self.test_did, - metadata=None, - ) - connect_record, _ = 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) - - request_a = 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(request_a, receipt) - - async def test_create_invitation_metadata_assigned(self): - async with self.profile.session() as session: - record, _ = 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: - _, 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: - wallet = session.inject(BaseWallet) - await 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( - AskarWallet, "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_mediation_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(AskarWallet, "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(AskarWallet, "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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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 - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - mock.patch.object(ConnRecord, "retrieve_request", autospec=True), - mock.patch.object( - ConnRecord, - "retrieve_by_invitation_msg_id", - mock.CoroutineMock(return_value=mock_connection_record), - ), - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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: - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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 - ) - wallet = session.inject(BaseWallet) - await 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), - mock.patch.object(ConnRecord, "attach_request", autospec=True), - mock.patch.object(ConnRecord, "retrieve_by_id", autospec=True), - 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), - mock.patch.object(ConnRecord, "retrieve_request", autospec=True), - mock.patch.object(ConnRecord, "save", autospec=True), - mock.patch.object(ConnectionResponse, "sign_field", autospec=True), - 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_mediation_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( - AskarWallet, "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_mediation_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(AskarWallet, "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), - mock.patch.object(ConnRecord, "retrieve_request", autospec=True), - mock.patch.object(ConnRecord, "save", autospec=True), - mock.patch.object(ConnectionResponse, "sign_field", autospec=True), - 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), - 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), - 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), - 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), - 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), - 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), - 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), - 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), - 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/acapy_agent/protocols/connections/v1_0/tests/test_routes.py b/acapy_agent/protocols/connections/v1_0/tests/test_routes.py deleted file mode 100644 index 917c795c5f..0000000000 --- a/acapy_agent/protocols/connections/v1_0/tests/test_routes.py +++ /dev/null @@ -1,828 +0,0 @@ -import json -from unittest import IsolatedAsyncioTestCase -from unittest.mock import ANY - -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 .....storage.error import StorageNotFoundError -from .....tests import mock -from .....utils.testing import create_test_profile -from .. import routes as test_module - - -class TestConnectionRoutes(IsolatedAsyncioTestCase): - async def asyncSetUp(self): - self.session_inject = {} - self.profile = await create_test_profile( - settings={ - "admin.admin_api_key": "secret-key", - } - ) - self.context = AdminRequestContext.test_context(self.session_inject, self.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 - 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"} - - 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"} - - with ( - mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr_cls, - mock.patch.object(test_module.web, "json_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()), - 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"), - ): - 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()), - mock.patch.object( - mock_conn_rec, "metadata_set", mock.CoroutineMock() - ) as mock_metadata_set, - mock.patch.object(test_module.web, "json_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 - ), - 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), - ): - 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 - ), - 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() - ), - 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() - ), - 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() - ), - mock.patch.object( - test_module, "ConnectionManager", autospec=True - ) as mock_conn_mgr, - mock.patch.object(test_module.web, "json_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"} - - 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/acapy_agent/protocols/coordinate_mediation/v1_0/routes.py b/acapy_agent/protocols/coordinate_mediation/v1_0/routes.py index c3d515fecc..248777bc76 100644 --- a/acapy_agent/protocols/coordinate_mediation/v1_0/routes.py +++ b/acapy_agent/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/acapy_agent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py b/acapy_agent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py index 132d192f59..c5d4aecad5 100644 --- a/acapy_agent/protocols/introduction/v0_1/handlers/forward_invitation_handler.py +++ b/acapy_agent/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,12 @@ async def handle(self, context: RequestContext, responder: BaseResponder): # Store invitation profile = context.profile - connection_mgr = ConnectionManager(profile) + mgr = OutOfBandManager(profile) + assert context.message.invitation, "Schema requires invitation in message" 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/acapy_agent/protocols/introduction/v0_1/handlers/invitation_request_handler.py b/acapy_agent/protocols/introduction/v0_1/handlers/invitation_request_handler.py index e66d7a4cef..13ceb06e41 100644 --- a/acapy_agent/protocols/introduction/v0_1/handlers/invitation_request_handler.py +++ b/acapy_agent/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/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py index 92bc063e05..c05f614d03 100644 --- a/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py +++ b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_forward_invitation_handler.py @@ -4,9 +4,7 @@ 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 .....out_of_band.v1_0.messages.invitation import InvitationMessage, Service from ......tests import mock from ......utils.testing import create_test_profile from ...messages.forward_invitation import ForwardInvitation @@ -25,14 +23,17 @@ async def asyncSetUp(self): self.context = RequestContext.test_context(await create_test_profile()) 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 +43,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 +57,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/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py index 918988898e..e051a30b35 100644 --- a/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py +++ b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_handler.py @@ -3,9 +3,7 @@ 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 .....out_of_band.v1_0.messages.invitation import InvitationMessage, Service from ......tests import mock from ......utils.testing import create_test_profile from ...messages.invitation import Invitation @@ -23,14 +21,17 @@ class TestInvitationHandler(IsolatedAsyncioTestCase): async def asyncSetUp(self): self.context = RequestContext.test_context(await create_test_profile()) 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], image_url=TEST_IMAGE_URL, + services=[service], ), message="Hello World", ) diff --git a/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py index 0513ea4879..1da593efb9 100644 --- a/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py +++ b/acapy_agent/protocols/introduction/v0_1/handlers/tests/test_invitation_request_handler.py @@ -3,9 +3,8 @@ 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 .....out_of_band.v1_0.messages.invitation import InvitationMessage, Service +from .....out_of_band.v1_0.models.invitation import InvitationRecord from ......tests import mock from ......utils.testing import create_test_profile from ...messages.invitation import Invitation @@ -35,33 +34,37 @@ async def test_handle(self): responder = MockResponder() - with mock.patch.object(test_module, "ConnectionManager", autospec=True): + with mock.patch.object(test_module, "OutOfBandManager", autospec=True): await handler.handle(self.context, responder) 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, + service_endpoint=TEST_ENDPOINT, recipient_keys=[TEST_VERKEY], - 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/acapy_agent/protocols/introduction/v0_1/messages/forward_invitation.py b/acapy_agent/protocols/introduction/v0_1/messages/forward_invitation.py index 5a8399c282..bd29ca0983 100644 --- a/acapy_agent/protocols/introduction/v0_1/messages/forward_invitation.py +++ b/acapy_agent/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/acapy_agent/protocols/introduction/v0_1/messages/invitation.py b/acapy_agent/protocols/introduction/v0_1/messages/invitation.py index 1deb28dbb5..178dcfaa43 100644 --- a/acapy_agent/protocols/introduction/v0_1/messages/invitation.py +++ b/acapy_agent/protocols/introduction/v0_1/messages/invitation.py @@ -5,9 +5,9 @@ from marshmallow import EXCLUDE, fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema -from ....connections.v1_0.messages.connection_invitation import ( - ConnectionInvitation, - ConnectionInvitationSchema, +from ....out_of_band.v1_0.messages.invitation import ( + InvitationMessage, + InvitationMessageSchema, ) from ..message_types import INVITATION, PROTOCOL_PACKAGE @@ -27,7 +27,7 @@ class Meta: def __init__( self, *, - invitation: Optional[ConnectionInvitation] = None, + invitation: Optional[InvitationMessage] = None, message: Optional[str] = None, **kwargs, ): @@ -52,7 +52,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/acapy_agent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py b/acapy_agent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py index 07a16ea3d4..9f4c3852b0 100644 --- a/acapy_agent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py +++ b/acapy_agent/protocols/introduction/v0_1/messages/tests/test_forward_invitation.py @@ -1,7 +1,9 @@ from unittest import IsolatedAsyncioTestCase, TestCase, mock -from .....connections.v1_0.messages.connection_invitation import ConnectionInvitation +from ......did.did_key import DIDKey +from ......wallet.key_type import ED25519 from .....didcomm_prefix import DIDCommPrefix +from .....out_of_band.v1_0.messages.invitation import InvitationMessage, Service from ...message_types import FORWARD_INVITATION, PROTOCOL_PACKAGE from ..forward_invitation import ForwardInvitation @@ -12,13 +14,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 +70,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/acapy_agent/protocols/introduction/v0_1/messages/tests/test_invitation.py b/acapy_agent/protocols/introduction/v0_1/messages/tests/test_invitation.py index 9d5ae8e226..8bdec655ff 100644 --- a/acapy_agent/protocols/introduction/v0_1/messages/tests/test_invitation.py +++ b/acapy_agent/protocols/introduction/v0_1/messages/tests/test_invitation.py @@ -1,6 +1,8 @@ from unittest import IsolatedAsyncioTestCase, mock -from .....connections.v1_0.messages.connection_invitation import ConnectionInvitation +from ......did.did_key import DIDKey +from ......wallet.key_type import ED25519 +from .....out_of_band.v1_0.messages.invitation import InvitationMessage, Service from ...message_types import PROTOCOL_PACKAGE from ..invitation import Invitation as IntroInvitation @@ -13,11 +15,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/acapy_agent/protocols/out_of_band/v1_0/manager.py b/acapy_agent/protocols/out_of_band/v1_0/manager.py index 5b0bbf90ec..ad7cf506e4 100644 --- a/acapy_agent/protocols/out_of_band/v1_0/manager.py +++ b/acapy_agent/protocols/out_of_band/v1_0/manager.py @@ -7,11 +7,6 @@ from uuid_utils import uuid4 -from acapy_agent.protocols.coordinate_mediation.v1_0.route_manager import ( - RouteManager, -) -from acapy_agent.wallet.error import WalletNotFoundError - from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError @@ -26,12 +21,12 @@ from ....storage.error import StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet -from ....wallet.did_info import INVITATION_REUSE_KEY, DIDInfo +from ....wallet.did_info import DIDInfo, INVITATION_REUSE_KEY from ....wallet.did_method import PEER2, PEER4 +from ....wallet.error import WalletNotFoundError 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 ...coordinate_mediation.v1_0.route_manager import RouteManager from ...didcomm_prefix import DIDCommPrefix from ...didexchange.v1_0.manager import DIDXManager from ...issue_credential.v1_0.models.credential_exchange import V10CredentialExchange @@ -1102,36 +1097,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( f"Unable to create connection. Could not perform handshake using any of " diff --git a/acapy_agent/protocols/out_of_band/v1_0/messages/invitation.py b/acapy_agent/protocols/out_of_band/v1_0/messages/invitation.py index d02673361f..f4169dfdb1 100644 --- a/acapy_agent/protocols/out_of_band/v1_0/messages/invitation.py +++ b/acapy_agent/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/acapy_agent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py b/acapy_agent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py index dd359b0e2a..0af48584e7 100644 --- a/acapy_agent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py +++ b/acapy_agent/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/acapy_agent/protocols/out_of_band/v1_0/tests/test_manager.py b/acapy_agent/protocols/out_of_band/v1_0/tests/test_manager.py index 1e5b41ba7e..da4e43e43a 100644 --- a/acapy_agent/protocols/out_of_band/v1_0/tests/test_manager.py +++ b/acapy_agent/protocols/out_of_band/v1_0/tests/test_manager.py @@ -1,13 +1,14 @@ """Test OOB Manager.""" import base64 -import json from copy import deepcopy from datetime import datetime, timedelta, timezone +import json from typing import List from unittest import IsolatedAsyncioTestCase from unittest.mock import ANY +from .. import manager as test_module from .....connections.models.conn_record import ConnRecord from .....connections.models.connection_target import ConnectionTarget from .....connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service @@ -20,64 +21,58 @@ from .....messaging.util import datetime_now, datetime_to_str, str_to_epoch from .....multitenant.base import BaseMultitenantManager from .....multitenant.manager import MultitenantManager -from .....protocols.coordinate_mediation.v1_0.manager import MediationManager -from .....protocols.coordinate_mediation.v1_0.models.mediation_record import ( - MediationRecord, -) -from .....protocols.coordinate_mediation.v1_0.route_manager import RouteManager -from .....protocols.didexchange.v1_0.manager import DIDXManager -from .....protocols.issue_credential.v1_0.messages.credential_offer import ( +from .....storage.error import StorageNotFoundError +from .....tests import mock +from .....transport.inbound.receipt import MessageReceipt +from .....utils.testing import create_test_profile +from .....wallet.askar import AskarWallet +from .....wallet.did_info import DIDInfo, KeyInfo +from .....wallet.did_method import SOV +from .....wallet.key_type import ED25519 +from .....wallet.util import pad +from ....coordinate_mediation.v1_0.manager import MediationManager +from ....coordinate_mediation.v1_0.models.mediation_record import MediationRecord +from ....coordinate_mediation.v1_0.route_manager import RouteManager +from ....didcomm_prefix import DIDCommPrefix +from ....didexchange.v1_0.manager import DIDXManager +from ....issue_credential.v1_0.message_types import CREDENTIAL_OFFER +from ....issue_credential.v1_0.messages.credential_offer import ( CredentialOffer as V10CredOffer, ) -from .....protocols.issue_credential.v1_0.messages.inner.credential_preview import ( +from ....issue_credential.v1_0.messages.inner.credential_preview import ( CredAttrSpec as V10CredAttrSpec, ) -from .....protocols.issue_credential.v1_0.messages.inner.credential_preview import ( +from ....issue_credential.v1_0.messages.inner.credential_preview import ( CredentialPreview as V10CredentialPreview, ) -from .....protocols.issue_credential.v1_0.tests import INDY_OFFER -from .....protocols.issue_credential.v2_0.message_types import ( +from ....issue_credential.v1_0.models.credential_exchange import V10CredentialExchange +from ....issue_credential.v1_0.tests import INDY_OFFER +from ....issue_credential.v2_0.message_types import ( ATTACHMENT_FORMAT as V20_CRED_ATTACH_FORMAT, ) -from .....protocols.issue_credential.v2_0.message_types import CRED_20_OFFER -from .....protocols.issue_credential.v2_0.messages.cred_format import V20CredFormat -from .....protocols.issue_credential.v2_0.messages.cred_offer import V20CredOffer -from .....protocols.issue_credential.v2_0.messages.inner.cred_preview import ( +from ....issue_credential.v2_0.message_types import CRED_20_OFFER +from ....issue_credential.v2_0.messages.cred_format import V20CredFormat +from ....issue_credential.v2_0.messages.cred_offer import V20CredOffer +from ....issue_credential.v2_0.messages.inner.cred_preview import ( V20CredAttrSpec, V20CredPreview, ) -from .....protocols.present_proof.v1_0.message_types import ( +from ....present_proof.v1_0.message_types import ( ATTACH_DECO_IDS as V10_PRES_ATTACH_FORMAT, ) -from .....protocols.present_proof.v1_0.message_types import PRESENTATION_REQUEST -from .....protocols.present_proof.v1_0.messages.presentation_request import ( - PresentationRequest, -) -from .....protocols.present_proof.v2_0.message_types import ( +from ....present_proof.v1_0.message_types import PRESENTATION_REQUEST +from ....present_proof.v1_0.messages.presentation_request import PresentationRequest +from ....present_proof.v2_0.message_types import ( ATTACHMENT_FORMAT as V20_PRES_ATTACH_FORMAT, ) -from .....protocols.present_proof.v2_0.message_types import PRES_20_REQUEST -from .....protocols.present_proof.v2_0.messages.pres_format import V20PresFormat -from .....protocols.present_proof.v2_0.messages.pres_request import V20PresRequest -from .....storage.error import StorageNotFoundError -from .....tests import mock -from .....transport.inbound.receipt import MessageReceipt -from .....utils.testing import create_test_profile -from .....wallet.askar import AskarWallet -from .....wallet.did_info import DIDInfo, KeyInfo -from .....wallet.did_method import SOV -from .....wallet.key_type import ED25519 -from .....wallet.util import pad -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 -from .. import manager as test_module +from ....present_proof.v2_0.message_types import PRES_20_REQUEST +from ....present_proof.v2_0.messages.pres_format import V20PresFormat +from ....present_proof.v2_0.messages.pres_request import V20PresRequest from ..manager import ( - REUSE_ACCEPTED_WEBHOOK_TOPIC, - REUSE_WEBHOOK_TOPIC, OutOfBandManager, OutOfBandManagerError, + REUSE_ACCEPTED_WEBHOOK_TOPIC, + REUSE_WEBHOOK_TOPIC, ) from ..messages.invitation import HSProto, InvitationMessage from ..messages.invitation import Service as OobService @@ -1348,64 +1343,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/acapy_agent/protocols/routing/v1_0/handlers/forward_handler.py b/acapy_agent/protocols/routing/v1_0/handlers/forward_handler.py index cc12fad775..78cbbd6114 100644 --- a/acapy_agent/protocols/routing/v1_0/handlers/forward_handler.py +++ b/acapy_agent/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/acapy_agent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py b/acapy_agent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py index 73c642c489..c80eb6085a 100644 --- a/acapy_agent/protocols/routing/v1_0/handlers/tests/test_forward_handler.py +++ b/acapy_agent/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 diff --git a/acapy_agent/utils/endorsement_setup.py b/acapy_agent/utils/endorsement_setup.py index 0b733a706b..24b29cf0db 100644 --- a/acapy_agent/utils/endorsement_setup.py +++ b/acapy_agent/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 ( @@ -66,18 +62,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 EndorsementSetupError( - "Failed to establish endorser connection, invalid invitation format." - ) + raise EndorsementSetupError("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/acapy_agent/utils/repeat.py b/acapy_agent/utils/repeat.py index fd10a29802..dcde28ff5e 100644 --- a/acapy_agent/utils/repeat.py +++ b/acapy_agent/utils/repeat.py @@ -3,8 +3,6 @@ import asyncio from typing import Optional -import async_timeout - class RepeatAttempt: """Represents the current iteration in a repeat sequence.""" @@ -47,7 +45,7 @@ def next_interval(self) -> float: def timeout(self, interval: Optional[float] = None): """Create a context manager for timing out an attempt.""" - return async_timeout.timeout(self.next_interval if interval is None else interval) + return asyncio.timeout(self.next_interval if interval is None else interval) def __repr__(self) -> str: """Format as a string for debugging.""" diff --git a/poetry.lock b/poetry.lock index 65fc4b0e53..e01ef81828 100644 --- a/poetry.lock +++ b/poetry.lock @@ -124,21 +124,21 @@ speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiohttp-apispec-acapy" -version = "3.0.2" +version = "3.0.3" description = "Build and document REST APIs with aiohttp and apispec" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-apispec-acapy-3.0.2.tar.gz", hash = "sha256:9e6946a48cb70d3f7097f51e2ce7ba8bee32fce9d654454fe300930bfa8ce542"}, - {file = "aiohttp_apispec_acapy-3.0.2-py3-none-any.whl", hash = "sha256:93ea532afb3876685d185cc1cfe51d6d08e597cf04f79d16898a23ac4842b742"}, + {file = "aiohttp_apispec_acapy-3.0.3-py3-none-any.whl", hash = "sha256:9a5d335c22975da1bbde49ddc04c138ee285d7c38354e88b43babef2eec0bc54"}, + {file = "aiohttp_apispec_acapy-3.0.3.tar.gz", hash = "sha256:8cec5f2601f8c2d7d53dd4aebab3975a596d86ea3a1a362eb3b1adadc11662b3"}, ] [package.dependencies] aiohttp = ">=3.9.4,<4.0" -apispec = ">=6.6.1,<6.7.0" -jinja2 = ">=3.1.3,<3.2.0" -webargs = ">=8.4.0,<8.5.0" +apispec = ">=6.6.1" +jinja2 = ">=3.1.3" +webargs = ">=8.4.0" [[package]] name = "aiohttp-cors" @@ -210,22 +210,22 @@ files = [ [[package]] name = "apispec" -version = "6.6.1" +version = "6.8.1" description = "A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "apispec-6.6.1-py3-none-any.whl", hash = "sha256:6460315cb38ac6a2ff42d9e2b8dc0435c37d4428d3abeda96ff97b5dc8eb6b94"}, - {file = "apispec-6.6.1.tar.gz", hash = "sha256:f5caa47cee75fe03b9c50b5594048b4c052eeca2c212e0dac12dbb6175d9a659"}, + {file = "apispec-6.8.1-py3-none-any.whl", hash = "sha256:eacba00df745efc9adb2a45cf992300e87938582077e101fb26b78ecf4320beb"}, + {file = "apispec-6.8.1.tar.gz", hash = "sha256:f4916cbb7be156963b18f5929a0e42bd2349135834b680a81b12432bcfaa9a39"}, ] [package.dependencies] packaging = ">=21.3" [package.extras] -dev = ["apispec[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["apispec[marshmallow]", "pyyaml (==6.0.1)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-rtd-theme (==2.0.0)"] +dev = ["apispec[tests]", "pre-commit (>=3.5,<5.0)", "tox"] +docs = ["apispec[marshmallow]", "pyyaml (==6.0.2)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-rtd-theme (==3.0.2)"] marshmallow = ["marshmallow (>=3.18.0)"] tests = ["apispec[marshmallow,yaml]", "openapi-spec-validator (==0.7.1)", "pytest"] yaml = ["PyYAML (>=3.10)"] @@ -247,18 +247,6 @@ files = [ [package.dependencies] cached-property = ">=1.5.2,<1.6.0" -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "attrs" version = "24.3.0" @@ -342,14 +330,14 @@ files = [ [[package]] name = "cachetools" -version = "5.5.0" +version = "5.5.1" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, - {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, + {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, + {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, ] [[package]] @@ -577,11 +565,11 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} [[package]] name = "configargparse" @@ -879,16 +867,19 @@ files = [ [[package]] name = "deepmerge" -version = "0.3.0" -description = "a toolset to deeply merge python dictionaries." +version = "2.0" +description = "A toolset for deeply merging Python dictionaries." optional = false -python-versions = ">=3" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "deepmerge-0.3.0-py2.py3-none-any.whl", hash = "sha256:87166dbe9ba1a3348a45c9d4ada6778f518d41afc0b85aa017ea3041facc3f9c"}, - {file = "deepmerge-0.3.0.tar.gz", hash = "sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd"}, + {file = "deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00"}, + {file = "deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20"}, ] +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pyupgrade", "twine", "validate-pyproject[all]"] + [[package]] name = "did-peer-2" version = "0.1.2" @@ -1046,14 +1037,14 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "5.1.0" +version = "5.2.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" optional = false python-versions = "<4,>=3.8" groups = ["main"] files = [ - {file = "eth_utils-5.1.0-py3-none-any.whl", hash = "sha256:a99f1f01b51206620904c5af47fac65abc143aebd0a76bdec860381c5a3230f8"}, - {file = "eth_utils-5.1.0.tar.gz", hash = "sha256:84c6314b9cf1fcd526107464bbf487e3f87097a2e753360d5ed319f7d42e3f20"}, + {file = "eth_utils-5.2.0-py3-none-any.whl", hash = "sha256:4d43eeb6720e89a042ad5b28d4b2111630ae764f444b85cbafb708d7f076da10"}, + {file = "eth_utils-5.2.0.tar.gz", hash = "sha256:17e474eb654df6e18f20797b22c6caabb77415a996b3ba0f3cc8df3437463134"}, ] [package.dependencies] @@ -1063,8 +1054,8 @@ eth-typing = ">=5.0.0" toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} [package.extras] -dev = ["build (>=0.9.0)", "bump-my-version (>=0.19.0)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +dev = ["build (>=0.9.0)", "bump-my-version (>=0.19.0)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=24,<25)"] test = ["hypothesis (>=4.43.0)", "mypy (==1.10.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] @@ -1084,19 +1075,19 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.16.1" +version = "3.17.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] @@ -1252,14 +1243,14 @@ files = [ [[package]] name = "identify" -version = "2.6.5" +version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"}, - {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"}, + {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, + {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, ] [package.extras] @@ -1579,84 +1570,85 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] name = "marshmallow" -version = "3.25.1" +version = "3.26.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210"}, - {file = "marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a"}, + {file = "marshmallow-3.26.0-py3-none-any.whl", hash = "sha256:1287bca04e6a5f4094822ac153c03da5e214a0a60bcd557b140f3e66991b8ca1"}, + {file = "marshmallow-3.26.0.tar.gz", hash = "sha256:eb36762a1cc76d7abf831e18a3a1b26d3d481bbc74581b8e532a3d3a8115e1cb"}, ] [package.dependencies] @@ -1835,14 +1827,14 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -1981,14 +1973,14 @@ files = [ [[package]] name = "portalocker" -version = "2.10.1" +version = "3.1.1" description = "Wraps the portalocker recipe for easy usage" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"}, - {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"}, + {file = "portalocker-3.1.1-py3-none-any.whl", hash = "sha256:80e984e24de292ff258a5bea0e4f3f778fff84c0ae1275dbaebc4658de4aacb3"}, + {file = "portalocker-3.1.1.tar.gz", hash = "sha256:ec20f6dda2ad9ce89fa399a5f31f4f1495f515958f0cb7ca6543cef7bb5a749e"}, ] [package.dependencies] @@ -1997,18 +1989,18 @@ pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} [package.extras] docs = ["sphinx (>=1.7.1)"] redis = ["redis"] -tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-rerunfailures (>=15.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] [[package]] name = "pre-commit" -version = "4.0.1" +version = "4.1.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, - {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, + {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, + {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, ] [package.dependencies] @@ -2322,13 +2314,13 @@ files = [ [[package]] name = "pydevd-pycharm" -version = "243.23654.74" +version = "251.17181.23" description = "PyCharm Debugger (used in PyCharm and PyDev)" optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "pydevd_pycharm-243.23654.74.tar.gz", hash = "sha256:1a988533ddd617da8d3dd032c49e0234860abab04fa589a92fd2b14e4092b591"}, + {file = "pydevd_pycharm-251.17181.23.tar.gz", hash = "sha256:5ba17e4e13e41661d6d4b790bab9e404d268e120d04dd682952d6bf156a6cdfc"}, ] [[package]] @@ -2544,16 +2536,19 @@ six = ">=1.5" [[package]] name = "python-json-logger" -version = "2.0.7" -description = "A python library adding a json log formatter" +version = "3.2.1" +description = "JSON Log Formatter for the Python Logging Package" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, - {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, + {file = "python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090"}, + {file = "python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008"}, ] +[package.extras] +dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "msgspec-python313-pre", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] + [[package]] name = "pywin32" version = "308" @@ -2648,26 +2643,24 @@ files = [ [[package]] name = "qrcode" -version = "6.1" +version = "8.0" description = "QR Code image generator" optional = false -python-versions = "*" +python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"}, - {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"}, + {file = "qrcode-8.0-py3-none-any.whl", hash = "sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1"}, + {file = "qrcode-8.0.tar.gz", hash = "sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347"}, ] [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -pillow = {version = "*", optional = true, markers = "extra == \"pil\""} -six = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +pillow = {version = ">=9.1.0", optional = true, markers = "extra == \"pil\" or extra == \"all\""} [package.extras] -dev = ["mock", "pytest", "tox"] -maintainer = ["zest.releaser[recommended]"] -pil = ["pillow"] -test = ["mock", "pytest", "pytest-cov"] +all = ["pillow (>=9.1.0)", "pypng"] +pil = ["pillow (>=9.1.0)"] +png = ["pypng"] [[package]] name = "requests" @@ -3106,26 +3099,25 @@ files = [ [[package]] name = "webargs" -version = "8.4.0" +version = "8.6.0" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, Falcon, and aiohttp." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "webargs-8.4.0-py3-none-any.whl", hash = "sha256:22324305fbca6a2c4cce1235280e8b56372fb3211a8dac2ac8ed1948315a6f53"}, - {file = "webargs-8.4.0.tar.gz", hash = "sha256:ea99368214a4ce613924be99d71db58c269631e95eff4fa09b7354e52dc006a5"}, + {file = "webargs-8.6.0-py3-none-any.whl", hash = "sha256:83da4d7105643d0a50499b06d98a6ade1a330ce66d039eaa51f715172c704aba"}, + {file = "webargs-8.6.0.tar.gz", hash = "sha256:b8d098ab92bd74c659eca705afa31d681475f218cb15c1e57271fa2103c0547a"}, ] [package.dependencies] marshmallow = ">=3.0.0" -packaging = "*" +packaging = ">=17.0" [package.extras] -dev = ["Django (>=2.2.0)", "Flask (>=0.12.5)", "aiohttp (>=3.0.8)", "bottle (>=0.12.13)", "falcon (>=2.0.0)", "flake8 (==7.0.0)", "flake8-bugbear (==23.12.2)", "mypy (==1.8.0)", "pre-commit (>=2.4,<4.0)", "pyramid (>=1.9.1)", "pytest", "pytest-aiohttp (>=0.3.0)", "pytest-asyncio", "tornado (>=4.5.2)", "tox", "webtest (==3.0.0)", "webtest-aiohttp (==2.0.0)"] -docs = ["Django (>=2.2.0)", "Flask (>=0.12.5)", "Sphinx (==7.2.6)", "aiohttp (>=3.0.8)", "bottle (>=0.12.13)", "falcon (>=2.0.0)", "furo (==2023.9.10)", "pyramid (>=1.9.1)", "sphinx-issues (==3.0.1)", "tornado (>=4.5.2)"] +dev = ["pre-commit (>=3.5,<4.0)", "tox", "webargs[tests]"] +docs = ["Sphinx (==8.0.2)", "furo (==2024.8.6)", "sphinx-issues (==4.1.0)", "webargs[frameworks]"] frameworks = ["Django (>=2.2.0)", "Flask (>=0.12.5)", "aiohttp (>=3.0.8)", "bottle (>=0.12.13)", "falcon (>=2.0.0)", "pyramid (>=1.9.1)", "tornado (>=4.5.2)"] -lint = ["flake8 (==7.0.0)", "flake8-bugbear (==23.12.2)", "mypy (==1.8.0)", "pre-commit (>=2.4,<4.0)"] -tests = ["Django (>=2.2.0)", "Flask (>=0.12.5)", "aiohttp (>=3.0.8)", "bottle (>=0.12.13)", "falcon (>=2.0.0)", "pyramid (>=1.9.1)", "pytest", "pytest-aiohttp (>=0.3.0)", "pytest-asyncio", "tornado (>=4.5.2)", "webtest (==3.0.0)", "webtest-aiohttp (==2.0.0)"] +tests = ["pytest", "pytest-aiohttp (>=0.3.0)", "pytest-asyncio", "webargs[frameworks]", "webtest (==3.0.1)", "webtest-aiohttp (==2.0.0)"] [[package]] name = "yarl" @@ -3231,4 +3223,4 @@ didcommv2 = ["didcomm-messaging"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "6746da380149ae13b17400170c5a84a7481fe66bc809ae8edb075eddd1723abb" +content-hash = "8f055912002fa73be1dccbcbef2c2d01f4562155c90f72201ef4e2170d442176" diff --git a/pyproject.toml b/pyproject.toml index 5b10c80fc2..ffb3417a36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,32 +17,31 @@ repository = "https://github.com/openwallet-foundation/acapy" [tool.poetry.dependencies] python = "^3.12" aiohttp = "~3.11.11" -aiohttp-apispec-acapy = "~3.0.2" +aiohttp-apispec-acapy = "~3.0.3" aiohttp-cors = "~0.7.0" apispec = "^6.6.0" -async-timeout = "~4.0.2" base58 = "~2.1.0" ConfigArgParse = "~1.7" -deepmerge = "~0.3.0" +deepmerge = "^2.0" ecdsa = "~0.19.0" -jsonpath-ng = "1.7.0" +jsonpath-ng = "^1.7.0" Markdown = "~3.7" -markupsafe = "2.1.5" -marshmallow = "~3.25.1" +markupsafe = "^3.0.2" +marshmallow = "~3.26.0" nest_asyncio = "~1.6.0" -packaging = "~23.2" -portalocker = "~2.10.1" +packaging = "^24.2" +portalocker = "^3.1.1" prompt_toolkit = ">=2.0.9,<2.1.0" pydid = "^0.5.1" pyjwt = "~2.10.1" pyld = "^2.0.4" pynacl = "~1.5.0" python-dateutil = "^2.9.0" -python-json-logger = "~2.0.7" +python-json-logger = "^3.2.1" pyyaml = "~6.0.2" -qrcode = { version = ">=6.1,<7.0", extras = ["pil"] } +qrcode = { version = "^8.0", extras = ["pil"] } requests = "~2.32.3" -rlp = "4.0.1" +rlp = "^4.0.1" unflatten = "~0.2" sd-jwt = "^0.10.3" uuid_utils = "^0.10.0" @@ -54,7 +53,7 @@ did-webvh = "^0.2.1" # Verifiable Credentials indy-credx = "~1.1.1" -anoncreds = "0.2.0" +anoncreds = "~0.2.0" # askar aries-askar = "~0.3.2" @@ -71,16 +70,16 @@ canonicaljson = "^2.0.0" [tool.poetry.group.dev.dependencies] # Sync with version in .pre-commit-config.yaml and .github/workflows/format.yml -ruff = "0.9.2" +ruff = "~0.9.2" -pre-commit = "~4.0.1" +pre-commit = "~4.1.0" sphinx = "^8.1.3" sphinx-rtd-theme = "^3.0.2" pydevd = "~3.2.3" -pydevd-pycharm = "~243.23654.74" +pydevd-pycharm = "^251.17181.23" # testing pytest = "^8.3.4" 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)