Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add DID Exchange specific problem reports and reject endpoint #2394

Merged
merged 14 commits into from
Aug 9, 2023
7 changes: 7 additions & 0 deletions aries_cloudagent/connections/models/conn_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,13 @@ async def delete_record(self, session: ProfileSession):
{"connection_id": self.connection_id},
)

async def abandon(self, session: ProfileSession, *, reason: Optional[str] = None):
"""Set state to abandoned."""
reason = reason or "Connectin abandoned"
self.state = ConnRecord.State.ABANDONED.rfc23
self.error_msg = reason
await self.save(session, reason=reason)

async def metadata_get(
self, session: ProfileSession, key: str, default: Any = None
) -> Any:
Expand Down
9 changes: 9 additions & 0 deletions aries_cloudagent/messaging/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ def Schema(self) -> Type["BaseModelSchema"]:
"""
return self._get_schema_class()

@overload
@classmethod
def deserialize(
cls: Type[ModelType],
obj,
) -> ModelType:
"""Convert from JSON representation to a model instance."""
...

@overload
@classmethod
def deserialize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
)

from ....out_of_band.v1_0.messages.invitation import InvitationMessage
from ....problem_report.v1_0.message import ProblemReport

from ..messages.problem_report_reason import ProblemReportReason
from ..messages.problem_report import DIDXProblemReport, ProblemReportReason


class InvitationHandler(BaseHandler):
Expand All @@ -27,7 +26,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
self._logger.debug(f"InvitationHandler called with context {context}")
assert isinstance(context.message, InvitationMessage)

report = ProblemReport(
report = DIDXProblemReport(
description={
"code": ProblemReportReason.INVITATION_NOT_ACCEPTED.value,
"en": (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Problem report handler for DID Exchange."""

from .....messaging.base_handler import (
BaseHandler,
BaseResponder,
HandlerException,
RequestContext,
)
from ..manager import DIDXManager, DIDXManagerError
from ..messages.problem_report import DIDXProblemReport


class DIDXProblemReportHandler(BaseHandler):
"""Handler class for DID Exchange problem report messages."""

async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle problem report message."""
self._logger.debug(f"DIDXProblemReportHandler called with context {context}")
assert isinstance(context.message, DIDXProblemReport)

self._logger.info("Received problem report: %s", context.message.description)
profile = context.profile
mgr = DIDXManager(profile)
try:
if context.connection_record:
await mgr.receive_problem_report(
context.connection_record, context.message
)
else:
raise HandlerException("No connection established for problem report")
except DIDXManagerError:
# Unrecognized problem report code
self._logger.exception("Error receiving DID Exchange problem report")
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""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
from ....problem_report.v1_0.message import ProblemReport
from ..manager import DIDXManager, DIDXManagerError
from ..messages.request import DIDXRequest

Expand Down Expand Up @@ -75,6 +77,8 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
"Error parsing DIDDoc for problem report"
)
await responder.send_reply(
ProblemReport(description={"en": e.message, "code": e.error_code}),
DIDXProblemReport(
description={"en": e.message, "code": e.error_code}
),
target_list=targets,
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""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 ....problem_report.v1_0.message import ProblemReport
from ....trustping.v1_0.messages.ping import Ping

from ..manager import DIDXManager, DIDXManagerError
Expand Down Expand Up @@ -48,7 +50,9 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
"Error parsing DIDDoc for problem report"
)
await responder.send_reply(
ProblemReport(description={"en": e.message, "code": e.error_code}),
DIDXProblemReport(
description={"en": e.message, "code": e.error_code}
),
target_list=targets,
)
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ...manager import DIDXManagerError
from ...messages.complete import DIDXComplete
from ...messages.problem_report_reason import ProblemReportReason
from ...messages.problem_report import ProblemReportReason

from .. import complete_handler as test_module
from ......wallet.did_method import DIDMethods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from ......transport.inbound.receipt import MessageReceipt

from .....out_of_band.v1_0.messages.invitation import InvitationMessage
from .....problem_report.v1_0.message import ProblemReport

from ...handlers.invitation_handler import InvitationHandler
from ...messages.problem_report_reason import ProblemReportReason
from ...messages.problem_report import DIDXProblemReport, ProblemReportReason
from ......wallet.did_method import DIDMethods


Expand All @@ -30,8 +29,12 @@ async def test_problem_report(self, request_context):
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"]
== ProblemReportReason.INVITATION_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.INVITATION_NOT_ACCEPTED.value
)
)
assert not target
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from asynctest import mock as async_mock
import pytest

from .. import problem_report_handler as test_module
from ......messaging.base_handler import HandlerException
from ......messaging.request_context import RequestContext
from ......messaging.responder import MockResponder
from ...manager import DIDXManagerError
from ...messages.problem_report import DIDXProblemReport


@pytest.fixture()
def request_context():
ctx = RequestContext.test_context()
yield ctx


class TestDIDXProblemReportHandler:
"""Unit test problem report handler."""

@pytest.mark.asyncio
@async_mock.patch.object(test_module, "DIDXManager")
async def test_called(self, manager, request_context):
manager.return_value.receive_problem_report = async_mock.CoroutineMock()
request_context.message = DIDXProblemReport()
request_context.connection_record = async_mock.MagicMock()
handler_inst = test_module.DIDXProblemReportHandler()
responder = MockResponder()
await handler_inst.handle(request_context, responder)
assert not responder.messages
assert manager.return_value.receive_problem_report.called_once()

@pytest.mark.asyncio
@async_mock.patch.object(test_module, "DIDXManager")
async def test_called_no_conn(self, manager, request_context):
manager.return_value.receive_problem_report = async_mock.CoroutineMock()
request_context.message = DIDXProblemReport()
handler_inst = test_module.DIDXProblemReportHandler()
responder = MockResponder()
with pytest.raises(HandlerException):
await handler_inst.handle(request_context, responder)

@pytest.mark.asyncio
@async_mock.patch.object(test_module, "DIDXManager")
async def test_called_unrecognized_report_exception(
self, manager, request_context, caplog
):
manager.return_value.receive_problem_report = async_mock.CoroutineMock(
side_effect=DIDXManagerError()
)
request_context.message = DIDXProblemReport()
request_context.connection_record = async_mock.MagicMock()
handler_inst = test_module.DIDXProblemReportHandler()
responder = MockResponder()
await handler_inst.handle(request_context, responder)
assert "Error receiving DID Exchange problem report" in caplog.text
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
from asynctest import mock as async_mock
from asynctest import TestCase as AsyncTestCase

from ......connections.models import connection_target, conn_record
from ......connections.models.diddoc import (
DIDDoc,
PublicKey,
PublicKeyType,
Service,
)
from ......connections.models import conn_record, connection_target
from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service
from ......core.in_memory import InMemoryProfile
from ......wallet.did_method import SOV, DIDMethods
from ......wallet.key_type import ED25519
from ......messaging.decorators.attach_decorator import AttachDecorator
from ......messaging.request_context import RequestContext
from ......messaging.responder import MockResponder
from ......transport.inbound.receipt import MessageReceipt

from .....problem_report.v1_0.message import ProblemReport

from ......wallet.did_method import DIDMethods, SOV
from ......wallet.key_type import ED25519
from ...handlers import request_handler as test_module
from ...manager import DIDXManagerError
from ...messages.problem_report import DIDXProblemReport, ProblemReportReason
from ...messages.request import DIDXRequest
from ...messages.problem_report_reason import ProblemReportReason

TEST_DID = "55GkHamhTU1ZbTbV2ab9DE"
TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
Expand Down Expand Up @@ -184,8 +176,13 @@ async def test_problem_report(self, mock_didx_mgr):
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"] == ProblemReportReason.REQUEST_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.REQUEST_NOT_ACCEPTED.value
)
)
assert target == {"target_list": None}

Expand All @@ -211,8 +208,13 @@ async def test_problem_report_did_doc(self, mock_conn_target, mock_didx_mgr):
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"] == ProblemReportReason.REQUEST_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.REQUEST_NOT_ACCEPTED.value
)
)
assert target == {"target_list": [mock_conn_target]}

Expand Down Expand Up @@ -242,7 +244,12 @@ async def test_problem_report_did_doc_no_conn_target(
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"] == ProblemReportReason.REQUEST_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.REQUEST_NOT_ACCEPTED.value
)
)
assert target == {"target_list": None}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
from ......wallet.did_method import SOV, DIDMethods
from ......wallet.key_type import ED25519

from .....problem_report.v1_0.message import ProblemReport
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_reason import ProblemReportReason
from ...messages.problem_report import DIDXProblemReport, ProblemReportReason

TEST_DID = "55GkHamhTU1ZbTbV2ab9DE"
TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
Expand Down Expand Up @@ -125,9 +124,13 @@ async def test_problem_report(self, mock_didx_mgr):
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
)
)
assert target == {"target_list": None}

Expand Down Expand Up @@ -157,9 +160,13 @@ async def test_problem_report_did_doc(
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
)
)
assert target == {"target_list": [mock_conn_target]}

Expand Down Expand Up @@ -189,8 +196,12 @@ async def test_problem_report_did_doc_no_conn_target(
messages = responder.messages
assert len(messages) == 1
result, target = messages[0]
assert isinstance(result, ProblemReport) and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
assert (
isinstance(result, DIDXProblemReport)
and result.description
and (
result.description["code"]
== ProblemReportReason.RESPONSE_NOT_ACCEPTED.value
)
)
assert target == {"target_list": None}
Loading