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/revocation notification v2 #1734

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@
"minimum_minor_version": 0,
"current_minor_version": 0,
"path": "v1_0",
}
},
{
"major_version": 2,
"minimum_minor_version": 0,
"current_minor_version": 0,
"path": "v2_0",
},
]
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Meta:
"rev_reg_id",
"cred_rev_id",
"connection_id",
"version",
}

def __init__(
Expand All @@ -38,6 +39,7 @@ def __init__(
connection_id: str = None,
thread_id: str = None,
comment: str = None,
version: str = None,
**kwargs,
):
"""Construct record."""
Expand All @@ -47,6 +49,7 @@ def __init__(
self.connection_id = connection_id
self.thread_id = thread_id
self.comment = comment
self.version = version

@property
def revocation_notification_id(self) -> Optional[str]:
Expand All @@ -73,6 +76,7 @@ async def query_by_ids(
rev_reg_id: the rev reg id by which to filter
"""
tag_filter = {
**{"version": "v1_0"},
**{"cred_rev_id": cred_rev_id for _ in [""] if cred_rev_id},
**{"rev_reg_id": rev_reg_id for _ in [""] if rev_reg_id},
}
Expand Down Expand Up @@ -101,6 +105,7 @@ async def query_by_rev_reg_id(
rev_reg_id: the rev reg id by which to filter
"""
tag_filter = {
**{"version": "v1_0"},
**{"rev_reg_id": rev_reg_id for _ in [""] if rev_reg_id},
}

Expand Down Expand Up @@ -157,3 +162,7 @@ class Meta:
description="Optional comment to include in revocation notification",
required=False,
)
version = fields.Str(
description="Version of Revocation Notification to send out",
required=False,
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def rec():
connection_id="mock_connection_id",
thread_id="mock_thread_id",
comment="mock_comment",
version="v1_0",
)


Expand Down Expand Up @@ -50,6 +51,7 @@ async def test_storage(profile, rec):
another = RevNotificationRecord(
rev_reg_id="mock_rev_reg_id",
cred_rev_id="mock_cred_rev_id",
version="v1_0",
)
await another.save(session)
await RevNotificationRecord.query_by_ids(
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Handler for revoke message."""

from .....messaging.base_handler import BaseHandler
from .....messaging.request_context import RequestContext
from .....messaging.responder import BaseResponder

from ..messages.revoke import Revoke


class RevokeHandler(BaseHandler):
"""Handler for revoke message."""

RECIEVED_TOPIC = "acapy::revocation-notification-v2::received"
WEBHOOK_TOPIC = "acapy::webhook::revocation-notification-v2"

async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle revoke message."""
assert isinstance(context.message, Revoke)
self._logger.debug(
"Received notification of revocation for %s cred %s with comment: %s",
context.message.revocation_format,
context.message.credential_id,
context.message.comment,
)
# Emit a webhook
if context.settings.get("revocation.monitor_notification"):
await context.profile.notify(
self.WEBHOOK_TOPIC,
{
"revocation_format": context.message.revocation_format,
"credential_id": context.message.credential_id,
"comment": context.message.comment,
},
)

# Emit an event
await context.profile.notify(
self.RECIEVED_TOPIC,
{
"revocation_format": context.message.revocation_format,
"credential_id": context.message.credential_id,
"comment": context.message.comment,
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Test RevokeHandler."""

import pytest

from ......config.settings import Settings
from ......core.event_bus import EventBus, MockEventBus
from ......core.in_memory import InMemoryProfile
from ......core.profile import Profile
from ......messaging.request_context import RequestContext
from ......messaging.responder import MockResponder, BaseResponder
from ...messages.revoke import Revoke
from ..revoke_handler import RevokeHandler


@pytest.fixture
def event_bus():
yield MockEventBus()


@pytest.fixture
def responder():
yield MockResponder()


@pytest.fixture
def profile(event_bus):
yield InMemoryProfile.test_profile(bind={EventBus: event_bus})


@pytest.fixture
def message():
yield Revoke(
revocation_format="indy-anoncreds",
credential_id="mock_cred_revocation_id",
comment="mock_comment",
)


@pytest.fixture
def context(profile: Profile, message: Revoke):
request_context = RequestContext(profile)
request_context.message = message
yield request_context


@pytest.mark.asyncio
async def test_handle(
context: RequestContext, responder: BaseResponder, event_bus: MockEventBus
):
await RevokeHandler().handle(context, responder)
assert event_bus.events
[(_, received)] = event_bus.events
assert received.topic == RevokeHandler.RECIEVED_TOPIC
assert "revocation_format" in received.payload
assert "credential_id" in received.payload
assert "comment" in received.payload


@pytest.mark.asyncio
async def test_handle_monitor(
context: RequestContext, responder: BaseResponder, event_bus: MockEventBus
):
context.settings["revocation.monitor_notification"] = True
await RevokeHandler().handle(context, responder)
[(_, webhook), (_, received)] = event_bus.events

assert webhook.topic == RevokeHandler.WEBHOOK_TOPIC
assert "revocation_format" in received.payload
assert "credential_id" in received.payload
assert "comment" in webhook.payload

assert received.topic == RevokeHandler.RECIEVED_TOPIC
assert "revocation_format" in received.payload
assert "credential_id" in received.payload
assert "comment" in received.payload
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Message type identifiers for Revocation Notification protocol."""

from ...didcomm_prefix import DIDCommPrefix


SPEC_URI = (
"https://github.com/hyperledger/aries-rfcs/blob/main/features/"
"0721-revocation-notification-v2/README.md"
)
PROTOCOL = "revocation_notification"
VERSION = "2.0"
BASE = f"{PROTOCOL}/{VERSION}"

# Message types
REVOKE = f"{BASE}/revoke"

PROTOCOL_PACKAGE = "aries_cloudagent.protocols.revocation_notification.v2_0"
MESSAGE_TYPES = DIDCommPrefix.qualify_all(
{REVOKE: f"{PROTOCOL_PACKAGE}.messages.revoke.Revoke"}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Revoke message."""

from marshmallow import fields, validate
from .....messaging.agent_message import AgentMessage, AgentMessageSchema
from .....messaging.decorators.please_ack_decorator import (
PleaseAckDecorator,
PleaseAckDecoratorSchema,
)
from .....messaging.valid import UUIDFour
from ..message_types import PROTOCOL_PACKAGE, REVOKE

HANDLER_CLASS = f"{PROTOCOL_PACKAGE}.handlers.revoke_handler.RevokeHandler"


class Revoke(AgentMessage):
"""Class representing revoke message."""

class Meta:
"""Revoke Meta."""

handler_class = HANDLER_CLASS
message_type = REVOKE
schema_class = "RevokeSchema"

def __init__(
self,
*,
revocation_format: str,
credential_id: str,
please_ack: PleaseAckDecorator = None,
comment: str = None,
**kwargs,
):
"""Initialize revoke message."""
super().__init__(**kwargs)
self.revocation_format = revocation_format
self.credential_id = credential_id
self.comment = comment


class RevokeSchema(AgentMessageSchema):
"""Schema of Revoke message."""

class Meta:
"""RevokeSchema Meta."""

model_class = Revoke

revocation_format = fields.Str(
required=True,
description=("The format of the credential revocation ID"),
example="indy-anoncreds",
validate=validate.OneOf(["indy-anoncreds"]),
)
credential_id = fields.Str(
required=True,
description=("Credential ID of the issued credential to be revoked"),
example=UUIDFour.EXAMPLE,
)
please_ack = fields.Nested(
PleaseAckDecoratorSchema,
required=False,
description="Whether or not the holder should acknowledge receipt",
data_key="~please_ack",
)
comment = fields.Str(
required=False,
description="Human readable information about revocation notification",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test Revoke Message."""

from ..revoke import Revoke


def test_instantiate():
msg = Revoke(
revocation_format="indy-anoncreds",
credential_id="test-id",
comment="test",
)
assert msg.revocation_format == "indy-anoncreds"
assert msg.credential_id == "test-id"
assert msg.comment == "test"
Loading