Skip to content

Commit

Permalink
Merge pull request #2653 from usingtechnology/issue_2650_problem_report
Browse files Browse the repository at this point in the history
Connection and DIDX Problem Reports
  • Loading branch information
dbluhm authored Dec 6, 2023
2 parents 11914ec + 353bfae commit c677185
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 155 deletions.
10 changes: 5 additions & 5 deletions aries_cloudagent/messaging/responder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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),
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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!
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand All @@ -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)
)
Expand Down Expand Up @@ -163,21 +188,22 @@ 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)
)
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": None}
assert len(messages) == 0 # need a connection target to send message
29 changes: 28 additions & 1 deletion aries_cloudagent/protocols/connections/v1_0/manager.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
BaseResponder,
RequestContext,
)

from ....out_of_band.v1_0.messages.invitation import InvitationMessage

from ..messages.problem_report import DIDXProblemReport, ProblemReportReason


Expand All @@ -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)
Loading

0 comments on commit c677185

Please sign in to comment.