diff --git a/aries_cloudagent/messaging/responder.py b/aries_cloudagent/messaging/responder.py index 90f4bb134e..e978b0ee2c 100644 --- a/aries_cloudagent/messaging/responder.py +++ b/aries_cloudagent/messaging/responder.py @@ -5,26 +5,26 @@ """ import asyncio import json - from abc import ABC, abstractmethod -from typing import List, Sequence, Union, Optional, Tuple +from typing import List, Optional, Sequence, Tuple, Union from ..cache.base import BaseCache -from ..connections.models.connection_target import ConnectionTarget from ..connections.models.conn_record import ConnRecord +from ..connections.models.connection_target import ConnectionTarget from ..core.error import BaseError from ..core.profile import Profile from ..transport.outbound.message import OutboundMessage - -from .base_message import BaseMessage from ..transport.outbound.status import OutboundSendStatus +from .base_message import BaseMessage SKIP_ACTIVE_CONN_CHECK_MSG_TYPES = [ "didexchange/1.0/request", "didexchange/1.0/response", + "didexchange/1.0/problem_report", "connections/1.0/invitation", "connections/1.0/request", "connections/1.0/response", + "connections/1.0/problem_report", ] diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py index babfedb5eb..1f4c4c3ec6 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_invitation_handler.py @@ -31,5 +31,6 @@ async def handle(self, context: RequestContext, responder: BaseResponder): ), } ) + report.assign_thread_from(context.message) # client likely needs to be using direct responses to receive the problem report await responder.send_reply(report) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py index 2f55b4a551..042759f225 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_request_handler.py @@ -5,7 +5,6 @@ from ....coordinate_mediation.v1_0.manager import MediationManager from ..manager import ConnectionManager, ConnectionManagerError from ..messages.connection_request import ConnectionRequest -from ..messages.problem_report import ConnectionProblemReport class ConnectionRequestHandler(BaseHandler): @@ -49,22 +48,11 @@ async def handle(self, context: RequestContext, responder: BaseResponder): else: self._logger.debug("Connection request will await acceptance") except ConnectionManagerError as e: - self._logger.exception("Error receiving connection request") - if e.error_code: - targets = None - if context.message.connection and context.message.connection.did_doc: - try: - targets = mgr.diddoc_connection_targets( - context.message.connection.did_doc, - context.message_receipt.recipient_verkey, - ) - except ConnectionManagerError: - self._logger.exception( - "Error parsing DIDDoc for problem report" - ) + report, targets = mgr.manager_error_to_problem_report( + e, context.message, context.message_receipt + ) + if report and targets: await responder.send_reply( - ConnectionProblemReport( - description={"en": e.message, "code": e.error_code} - ), + message=report, target_list=targets, ) diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py index 93d7c4163d..6fea9398c6 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/connection_response_handler.py @@ -8,7 +8,6 @@ from .....protocols.trustping.v1_0.messages.ping import Ping from ..manager import ConnectionManager, ConnectionManagerError from ..messages.connection_response import ConnectionResponse -from ..messages.problem_report import ConnectionProblemReport class ConnectionResponseHandler(BaseHandler): @@ -31,27 +30,16 @@ async def handle(self, context: RequestContext, responder: BaseResponder): context.message, context.message_receipt ) except ConnectionManagerError as e: - self._logger.exception("Error receiving connection response") - if e.error_code: - targets = None - if context.message.connection and context.message.connection.did_doc: - try: - targets = mgr.diddoc_connection_targets( - context.message.connection.did_doc, - context.message_receipt.recipient_verkey, - ) - except ConnectionManagerError: - self._logger.exception( - "Error parsing DIDDoc for problem report" - ) + report, targets = mgr.manager_error_to_problem_report( + e, context.message, context.message_receipt + ) + if report and targets: await responder.send_reply( - ConnectionProblemReport( - description={"en": e.message, "code": e.error_code} - ), + 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) + await responder.send(Ping(), connection_id=connection.connection_id), diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py index 70ea0e17f6..8be8ec31fd 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/problem_report_handler.py @@ -1,11 +1,13 @@ """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 @@ -24,10 +26,19 @@ async def handle(self, context: RequestContext, responder: BaseResponder): profile = context.profile mgr = ConnectionManager(profile) try: - if context.connection_record: - await mgr.receive_problem_report( - context.connection_record, context.message - ) + 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: diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py index 7b68ac2b32..db8ef1ce5f 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_request_handler.py @@ -159,11 +159,25 @@ async def test_connection_record_without_mediation_metadata( @pytest.mark.asyncio @mock.patch.object(handler, "ConnectionManager") - async def test_problem_report(self, mock_conn_mgr, request_context): + @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() @@ -179,7 +193,7 @@ async def test_problem_report(self, mock_conn_mgr, request_context): == ProblemReportReason.REQUEST_NOT_ACCEPTED.value ) ) - assert target == {"target_list": None} + assert target == {"target_list": [mock_conn_target]} @pytest.mark.asyncio @mock.patch.object(handler, "ConnectionManager") @@ -194,6 +208,17 @@ async def test_problem_report_did_doc( 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, @@ -228,6 +253,17 @@ async def test_problem_report_did_doc_no_conn_target( 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, @@ -237,14 +273,4 @@ async def test_problem_report_did_doc_no_conn_target( 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": None} + assert len(messages) == 0 # messages require a target! diff --git a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py index 7f66336c77..c52111330e 100644 --- a/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py +++ b/aries_cloudagent/protocols/connections/v1_0/handlers/tests/test_response_handler.py @@ -96,11 +96,25 @@ async def test_called_auto_ping(self, mock_conn_mgr, request_context): @pytest.mark.asyncio @mock.patch.object(handler, "ConnectionManager") - async def test_problem_report(self, mock_conn_mgr, request_context): + @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() @@ -116,7 +130,7 @@ async def test_problem_report(self, mock_conn_mgr, request_context): == ProblemReportReason.RESPONSE_NOT_ACCEPTED.value ) ) - assert target == {"target_list": None} + assert target == {"target_list": [mock_conn_target]} @pytest.mark.asyncio @mock.patch.object(handler, "ConnectionManager") @@ -131,6 +145,17 @@ async def test_problem_report_did_doc( 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) ) @@ -163,6 +188,17 @@ async def test_problem_report_did_doc_no_conn_target( 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) ) @@ -170,14 +206,4 @@ async def test_problem_report_did_doc_no_conn_target( 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": None} + assert len(messages) == 0 # need a connection target to send message diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index b7211ffcef..700fd0f364 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -1,10 +1,11 @@ """Classes to manage connections.""" import logging -from typing import Optional, Sequence, Tuple, cast +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 @@ -780,3 +781,29 @@ async def receive_problem_report( raise ConnectionManagerError( f"Received unrecognized problem report: {report.description}" ) + + def manager_error_to_problem_report( + self, + e: ConnectionManagerError, + message: Union[ConnectionRequest, ConnectionResponse], + message_receipt, + ) -> tuple[ConnectionProblemReport, Sequence[ConnectionTarget]]: + """Convert ConnectionManagerError to problem report.""" + self._logger.exception("Error receiving connection request") + targets = None + report = None + if e.error_code: + report = ConnectionProblemReport( + description={"en": e.message, "code": e.error_code} + ) + report.assign_thread_from(message) + if message.connection and message.connection.did_doc: + try: + targets = self.diddoc_connection_targets( + message.connection.did_doc, + message_receipt.recipient_verkey, + ) + except ConnectionManagerError: + self._logger.exception("Error parsing DIDDoc for problem report") + + return report, targets diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/invitation_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/invitation_handler.py index 4bf178512b..6b95209006 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/invitation_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/invitation_handler.py @@ -5,9 +5,7 @@ BaseResponder, RequestContext, ) - from ....out_of_band.v1_0.messages.invitation import InvitationMessage - from ..messages.problem_report import DIDXProblemReport, ProblemReportReason @@ -34,6 +32,6 @@ async def handle(self, context: RequestContext, responder: BaseResponder): ), } ) - + report.assign_thread_from(context.message) # client likely needs to be using direct responses to receive the problem report await responder.send_reply(report) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/problem_report_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/problem_report_handler.py index 8039abc946..1dfd77e6b0 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/problem_report_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/problem_report_handler.py @@ -1,11 +1,13 @@ """Problem report handler for DID Exchange.""" +from .....connections.models.conn_record import ConnRecord from .....messaging.base_handler import ( BaseHandler, BaseResponder, HandlerException, RequestContext, ) +from .....storage.error import StorageNotFoundError from ..manager import DIDXManager, DIDXManagerError from ..messages.problem_report import DIDXProblemReport @@ -22,10 +24,19 @@ async def handle(self, context: RequestContext, responder: BaseResponder): profile = context.profile mgr = DIDXManager(profile) try: - if context.connection_record: - await mgr.receive_problem_report( - context.connection_record, context.message - ) + 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 DIDXManagerError: diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py index b4b85eb1dc..a76807db73 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/request_handler.py @@ -1,8 +1,6 @@ """Connection request handler under RFC 23 (DID exchange).""" -from aries_cloudagent.protocols.didexchange.v1_0.messages.problem_report import ( - DIDXProblemReport, -) + from .....connections.models.conn_record import ConnRecord from .....messaging.base_handler import BaseHandler, BaseResponder, RequestContext from ....coordinate_mediation.v1_0.manager import MediationManager @@ -45,7 +43,6 @@ async def handle(self, context: RequestContext, responder: BaseResponder): else context.message_receipt.recipient_verkey ), ) - # Auto respond if conn_rec.accept == ConnRecord.ACCEPT_AUTO: response = await mgr.create_response( @@ -62,22 +59,11 @@ async def handle(self, context: RequestContext, responder: BaseResponder): self._logger.debug("DID exchange request will await acceptance") except DIDXManagerError as e: - self._logger.exception("Error receiving RFC 23 connection request") - if e.error_code: - targets = None - if context.message.did_doc_attach: - try: - targets = mgr.diddoc_connection_targets( - context.message.did_doc_attach, - context.message_receipt.recipient_verkey, - ) - except DIDXManagerError: - self._logger.exception( - "Error parsing DIDDoc for problem report" - ) + report, targets = await mgr.manager_error_to_problem_report( + e, context.message, context.message_receipt + ) + if report and targets: await responder.send_reply( - DIDXProblemReport( - description={"en": e.message, "code": e.error_code} - ), + message=report, target_list=targets, ) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/response_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/response_handler.py index c2c094320a..4dae2bf54d 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/response_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/response_handler.py @@ -1,16 +1,12 @@ """DID exchange response handler under RFC 23.""" -from aries_cloudagent.protocols.didexchange.v1_0.messages.problem_report import ( - DIDXProblemReport, -) + from .....messaging.base_handler import ( BaseHandler, BaseResponder, RequestContext, ) - from ....trustping.v1_0.messages.ping import Ping - from ..manager import DIDXManager, DIDXManagerError from ..messages.response import DIDXResponse @@ -35,23 +31,12 @@ async def handle(self, context: RequestContext, responder: BaseResponder): context.message, context.message_receipt ) except DIDXManagerError as e: - self._logger.exception("Error receiving DID exchange response") - if e.error_code: - targets = None - if context.message.did_doc_attach: - try: - targets = mgr.diddoc_connection_targets( - context.message.did_doc_attach, - context.message_receipt.recipient_verkey, - ) - except DIDXManagerError: - self._logger.exception( - "Error parsing DIDDoc for problem report" - ) + report, targets = await mgr.manager_error_to_problem_report( + e, context.message, context.message_receipt + ) + if report and targets: await responder.send_reply( - DIDXProblemReport( - description={"en": e.message, "code": e.error_code} - ), + message=report, target_list=targets, ) return diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py index c956b5f8c8..da9de340b6 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py @@ -1,6 +1,7 @@ -from aries_cloudagent.tests import mock from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.tests import mock + from ......connections.models import conn_record, connection_target from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from ......core.in_memory import InMemoryProfile @@ -8,7 +9,7 @@ from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder from ......transport.inbound.receipt import MessageReceipt -from ......wallet.did_method import DIDMethods, SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from ...handlers import request_handler as test_module from ...manager import DIDXManagerError @@ -163,12 +164,24 @@ async def test_connection_record_with_mediation_metadata_auto_response( assert responder.messages @mock.patch.object(test_module, "DIDXManager") - async def test_problem_report(self, mock_didx_mgr): + @mock.patch.object(connection_target, "ConnectionTarget") + async def test_problem_report(self, mock_conn_target, mock_didx_mgr): mock_didx_mgr.return_value.receive_request = mock.CoroutineMock( side_effect=DIDXManagerError( error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED.value ) ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, + } + ), + [mock_conn_target], + ) + ) self.ctx.message = DIDXRequest() handler_inst = test_module.DIDXRequestHandler() responder = MockResponder() @@ -184,7 +197,7 @@ async def test_problem_report(self, mock_didx_mgr): == ProblemReportReason.REQUEST_NOT_ACCEPTED.value ) ) - assert target == {"target_list": None} + assert target == {"target_list": [mock_conn_target]} @mock.patch.object(test_module, "DIDXManager") @mock.patch.object(connection_target, "ConnectionTarget") @@ -197,6 +210,17 @@ async def test_problem_report_did_doc(self, mock_conn_target, mock_didx_mgr): mock_didx_mgr.return_value.diddoc_connection_targets = mock.MagicMock( return_value=[mock_conn_target] ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, + } + ), + [mock_conn_target], + ) + ) self.ctx.message = DIDXRequest( label=TEST_LABEL, did=TEST_DID, @@ -233,6 +257,17 @@ async def test_problem_report_did_doc_no_conn_target( mock_didx_mgr.return_value.diddoc_connection_targets = mock.MagicMock( side_effect=DIDXManagerError("no targets") ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.REQUEST_NOT_ACCEPTED.value, + } + ), + None, + ) + ) self.ctx.message = DIDXRequest( label=TEST_LABEL, did=TEST_DID, @@ -242,14 +277,4 @@ async def test_problem_report_did_doc_no_conn_target( responder = MockResponder() await handler_inst.handle(self.ctx, responder) messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, DIDXProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.REQUEST_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": None} + assert len(messages) == 0 # need connection target to add message diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py index af9fd617ee..0fd802b2b1 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py @@ -1,6 +1,8 @@ +from unittest import IsolatedAsyncioTestCase + import pytest + from aries_cloudagent.tests import mock -from unittest import IsolatedAsyncioTestCase from ......connections.models import connection_target from ......connections.models.diddoc import ( @@ -15,13 +17,11 @@ from ......transport.inbound.receipt import MessageReceipt from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 - from .....trustping.v1_0.messages.ping import Ping - from ...handlers import response_handler as test_module from ...manager import DIDXManagerError -from ...messages.response import DIDXResponse from ...messages.problem_report import DIDXProblemReport, ProblemReportReason +from ...messages.response import DIDXResponse TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" @@ -111,12 +111,24 @@ async def test_called_auto_ping(self, mock_didx_mgr): @pytest.mark.asyncio @mock.patch.object(test_module, "DIDXManager") - async def test_problem_report(self, mock_didx_mgr): + @mock.patch.object(connection_target, "ConnectionTarget") + async def test_problem_report(self, mock_conn_target, mock_didx_mgr): mock_didx_mgr.return_value.accept_response = mock.CoroutineMock( side_effect=DIDXManagerError( error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED.value ) ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, + } + ), + [mock_conn_target], + ) + ) self.ctx.message = DIDXResponse() handler_inst = test_module.DIDXResponseHandler() responder = MockResponder() @@ -132,7 +144,7 @@ async def test_problem_report(self, mock_didx_mgr): == ProblemReportReason.RESPONSE_NOT_ACCEPTED.value ) ) - assert target == {"target_list": None} + assert target == {"target_list": [mock_conn_target]} @pytest.mark.asyncio @mock.patch.object(test_module, "DIDXManager") @@ -150,6 +162,17 @@ async def test_problem_report_did_doc( mock_didx_mgr.return_value.diddoc_connection_targets = mock.MagicMock( return_value=[mock_conn_target] ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, + } + ), + [mock_conn_target], + ) + ) self.ctx.message = DIDXResponse( did=TEST_DID, did_doc_attach=self.did_doc_attach, @@ -186,6 +209,17 @@ async def test_problem_report_did_doc_no_conn_target( mock_didx_mgr.return_value.diddoc_connection_targets = mock.MagicMock( side_effect=DIDXManagerError("no target") ) + mock_didx_mgr.return_value.manager_error_to_problem_report = mock.CoroutineMock( + return_value=( + DIDXProblemReport( + description={ + "en": "test error", + "code": ProblemReportReason.RESPONSE_NOT_ACCEPTED.value, + } + ), + None, + ) + ) self.ctx.message = DIDXResponse( did=TEST_DID, did_doc_attach=self.did_doc_attach, @@ -194,14 +228,4 @@ async def test_problem_report_did_doc_no_conn_target( responder = MockResponder() await handler_inst.handle(self.ctx, responder) messages = responder.messages - assert len(messages) == 1 - result, target = messages[0] - assert ( - isinstance(result, DIDXProblemReport) - and result.description - and ( - result.description["code"] - == ProblemReportReason.RESPONSE_NOT_ACCEPTED.value - ) - ) - assert target == {"target_list": None} + assert len(messages) == 0 # need target to add message diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 4de5712624..4477ceee8a 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -2,7 +2,7 @@ import json import logging -from typing import Optional +from typing import Optional, Sequence, Union import pydid from pydid import BaseDIDDocument as ResolvedDocument @@ -10,6 +10,7 @@ from ....connections.base_manager import BaseConnectionManager from ....connections.models.conn_record import ConnRecord +from ....connections.models.connection_target import ConnectionTarget from ....connections.models.diddoc import DIDDoc from ....core.error import BaseError from ....core.oob_processor import OobMessageProcessor @@ -989,3 +990,36 @@ async def get_first_applicable_didcomm_service( first_didcomm_service, *_ = didcomm_services return first_didcomm_service.id + + async def manager_error_to_problem_report( + self, + e: DIDXManagerError, + message: Union[DIDXRequest, DIDXResponse], + message_receipt, + ) -> tuple[DIDXProblemReport, Sequence[ConnectionTarget]]: + """Convert DIDXManagerError to problem report.""" + self._logger.exception("Error receiving RFC 23 connection request") + targets = None + report = None + if e.error_code: + report = DIDXProblemReport( + description={"en": e.message, "code": e.error_code} + ) + report.assign_thread_from(message) + if message.did_doc_attach: + try: + # convert diddoc attachment to diddoc... + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + conn_did_doc = await self.verify_diddoc( + wallet, message.did_doc_attach + ) + # get the connection targets... + targets = self.diddoc_connection_targets( + conn_did_doc, + message_receipt.recipient_verkey, + ) + except DIDXManagerError: + self._logger.exception("Error parsing DIDDoc for problem report") + + return report, targets diff --git a/aries_cloudagent/protocols/didexchange/v1_0/message_types.py b/aries_cloudagent/protocols/didexchange/v1_0/message_types.py index ce0f952848..ecb038e1f4 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/message_types.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/message_types.py @@ -21,5 +21,8 @@ DIDX_REQUEST: f"{PROTOCOL_PACKAGE}.messages.request.DIDXRequest", DIDX_RESPONSE: f"{PROTOCOL_PACKAGE}.messages.response.DIDXResponse", DIDX_COMPLETE: f"{PROTOCOL_PACKAGE}.messages.complete.DIDXComplete", + PROBLEM_REPORT: ( + f"{PROTOCOL_PACKAGE}.messages.problem_report.DIDXProblemReport" + ), } ) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/messages/problem_report.py b/aries_cloudagent/protocols/didexchange/v1_0/messages/problem_report.py index 46e7987ec4..4df4b1cbdd 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/messages/problem_report.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/messages/problem_report.py @@ -1,17 +1,16 @@ """DID Exchange problem report and reasons.""" -from enum import Enum import logging +from enum import Enum from marshmallow import EXCLUDE, ValidationError, validates_schema from ....problem_report.v1_0.message import ProblemReport, ProblemReportSchema from ..message_types import PROBLEM_REPORT - HANDLER_CLASS = ( "aries_cloudagent.protocols.didexchange.v1_0.handlers." - "problem_report_handler.ProblemReportHandler" + "problem_report_handler.DIDXProblemReportHandler" ) LOGGER = logging.getLogger(__name__)