Skip to content

Commit

Permalink
did:tdw resolver
Browse files Browse the repository at this point in the history
Signed-off-by: Jamie Hale <[email protected]>
  • Loading branch information
jamshale committed Oct 16, 2024
1 parent 3d1682c commit 2e061bd
Show file tree
Hide file tree
Showing 11 changed files with 579 additions and 287 deletions.
18 changes: 18 additions & 0 deletions acapy_agent/messaging/tests/test_valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CREDENTIAL_TYPE_VALIDATE,
DID_KEY_VALIDATE,
DID_POSTURE_VALIDATE,
DID_TDW_VALIDATE,
ENDPOINT_TYPE_VALIDATE,
ENDPOINT_VALIDATE,
INDY_CRED_DEF_ID_VALIDATE,
Expand Down Expand Up @@ -114,6 +115,23 @@ def test_indy_did(self):
INDY_DID_VALIDATE("Q4zqM7aXqm7gDQkUVLng9h")
INDY_DID_VALIDATE("did:sov:Q4zqM7aXqm7gDQkUVLng9h")

def test_tdw_did(self):
valid_tdw_dids = [
"did:tdw:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px:example.com",
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000",
"did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000#z6MkkzY9skorPaoEbCJFKUo7thD8Yb8MBs28aJRopf1TUo9V",
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000#whois",
]
for valid_tdw_did in valid_tdw_dids:
DID_TDW_VALIDATE(valid_tdw_did)

non_valid_tdw_dids = [
"did:web:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px",
]
for non_valid_tdw_did in non_valid_tdw_dids:
with self.assertRaises(ValidationError):
DID_TDW_VALIDATE(non_valid_tdw_did)

def test_indy_raw_public_key(self):
non_indy_raw_public_keys = [
"Q4zqM7aXqm7gDQkUVLng9JQ4zqM7aXqm7gDQkUVLng9I", # 'I' not a base58 char
Expand Down
19 changes: 19 additions & 0 deletions acapy_agent/messaging/valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ def __init__(self):
)


class DIDTdw(Regexp):
"""Validate value against did:tdw specification."""

EXAMPLE = (
"did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000#whois"
)
PATTERN = re.compile(r"^(did:tdw:)([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+(#\w+)?$")

def __init__(self):
"""Initialize the instance."""

super().__init__(
DIDTdw.PATTERN, error="Value {input} is not in W3C did:tdw format"
)


class DIDPosture(OneOf):
"""Validate value against defined DID postures."""

Expand Down Expand Up @@ -934,6 +950,9 @@ def __init__(
DID_WEB_VALIDATE = DIDWeb()
DID_WEB_EXAMPLE = DIDWeb.EXAMPLE

DID_TDW_VALIDATE = DIDTdw()
DID_TDW_EXAMPLE = DIDTdw.EXAMPLE

ROUTING_KEY_VALIDATE = RoutingKey()
ROUTING_KEY_EXAMPLE = RoutingKey.EXAMPLE

Expand Down
6 changes: 6 additions & 0 deletions acapy_agent/resolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ async def setup(context: InjectionContext):
await web_resolver.setup(context)
registry.register_resolver(web_resolver)

tdw_resolver = ClassProvider(
"acapy_agent.resolver.default.tdw.TdwDIDResolver"
).provide(context.settings, context.injector)
await tdw_resolver.setup(context)
registry.register_resolver(tdw_resolver)

if context.settings.get("resolver.universal"):
universal_resolver = ClassProvider(
"acapy_agent.resolver.default.universal.UniversalResolver"
Expand Down
6 changes: 4 additions & 2 deletions acapy_agent/resolver/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from enum import Enum
from typing import NamedTuple, Optional, Pattern, Sequence, Text, Union

from pydid import DID
from pydid import DID, DIDUrl, InvalidDIDError

from ..cache.base import BaseCache
from ..config.injection_context import InjectionContext
Expand Down Expand Up @@ -145,7 +145,9 @@ async def resolve(
if isinstance(did, DID):
did = str(did)
else:
DID.validate(did)
if not DID.is_valid(did) and not DIDUrl.is_valid(did):
raise InvalidDIDError(f"Invalid DID or DID URL: {did}")

if not await self.supports(profile, did):
raise DIDMethodNotSupported(
f"{self.__class__.__name__} does not support DID method for: {did}"
Expand Down
41 changes: 41 additions & 0 deletions acapy_agent/resolver/default/tdw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""TDW DID Resolver.
Resolution is performed by the did_tdw library.
"""

from re import Pattern
from typing import Optional, Sequence, Text

from did_history.resolver import ResolutionResult
from did_tdw.resolver import resolve_did

from ...config.injection_context import InjectionContext
from ...core.profile import Profile
from ...messaging.valid import DIDTdw
from ..base import BaseDIDResolver, ResolverType


class TdwDIDResolver(BaseDIDResolver):
"""TDW DID Resolver."""

def __init__(self):
"""Initialize the TDW DID Resolver."""
super().__init__(ResolverType.NATIVE)

async def setup(self, context: InjectionContext):
"""Perform required setup for TDW DID resolution."""

@property
def supported_did_regex(self) -> Pattern:
"""Return supported DID regex of TDW DID Resolver."""
return DIDTdw.PATTERN

async def _resolve(
self, profile: Profile, did: str, service_accept: Optional[Sequence[Text]] = None
) -> dict:
"""Resolve DID using TDW."""
response: ResolutionResult = await resolve_did(did)
if response.resolution_metadata and response.resolution_metadata.get("error"):
return response.resolution_metadata

return response.document
37 changes: 37 additions & 0 deletions acapy_agent/resolver/default/tests/test_tdw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest

from ....core.in_memory import InMemoryProfile
from ....core.profile import Profile
from ....messaging.valid import DIDTdw
from ..tdw import TdwDIDResolver

TEST_DID = "did:tdw:Qma6mc1qZw3NqxwX6SB5GPQYzP4pGN2nXD15Jwi4bcDBKu:domain.example"


@pytest.fixture
def resolver():
"""Resolver fixture."""
yield TdwDIDResolver()


@pytest.fixture
def profile():
"""Profile fixture."""
profile = InMemoryProfile.test_profile()
yield profile


@pytest.mark.asyncio
async def test_supported_did_regex(profile, resolver: TdwDIDResolver):
"""Test the supported_did_regex."""
assert resolver.supported_did_regex == DIDTdw.PATTERN
assert await resolver.supports(
profile,
TEST_DID,
)


@pytest.mark.asyncio
async def test_resolve(resolver: TdwDIDResolver, profile: Profile):
"""Test resolve method."""
assert await resolver.resolve(profile, TEST_DID)
6 changes: 4 additions & 2 deletions acapy_agent/resolver/did_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from typing import List, Optional, Sequence, Text, Tuple, Union

import pydid
from pydid import DID, DIDError, DIDUrl, Resource, VerificationMethod
from pydid import DID, DIDError, DIDUrl, InvalidDIDError, Resource, VerificationMethod
from pydid.doc.doc import BaseDIDDocument, IDNotFoundError

from ..core.profile import Profile
Expand Down Expand Up @@ -56,7 +56,9 @@ async def _resolve(
if isinstance(did, DID):
did = str(did)
else:
DID.validate(did)
if not DID.is_valid(did) and not DIDUrl.is_valid(did):
raise InvalidDIDError(f"Invalid DID or DID URL: {did}")

for resolver in await self._match_did_to_resolver(profile, did):
try:
LOGGER.debug("Resolving DID %s with %s", did, resolver)
Expand Down
8 changes: 6 additions & 2 deletions acapy_agent/resolver/routes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Resolve did document admin routes."""

import re

from aiohttp import web
from aiohttp_apispec import docs, match_info_schema, response_schema
from marshmallow import fields, validate
from pydid.common import DID_PATTERN

from ..admin.decorators.auth import tenant_authentication
from ..admin.request_context import AdminRequestContext
Expand All @@ -23,7 +24,10 @@ class W3cDID(validate.Regexp):
"""Validate value against w3c DID."""

EXAMPLE = "did:ted:WgWxqztrNooG92RXvxSTWv"
PATTERN = DID_PATTERN
# Did or DidUrl regex
PATTERN = re.compile(
"^did:([a-z0-9]+):((?:[a-zA-Z0-9._%-]*:)*[a-zA-Z0-9._%-]+)(#\w+)?$"
)

def __init__(self):
"""Initialize the instance."""
Expand Down
8 changes: 8 additions & 0 deletions acapy_agent/wallet/did_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ def holder_defined_did(self) -> HolderDefinedDid:
holder_defined_did=HolderDefinedDid.NO,
)

TDW = DIDMethod(
name="tdw",
key_types=[ED25519, X25519],
rotation=False,
holder_defined_did=HolderDefinedDid.NO,
)


class DIDMethods:
"""DID Method class specifying DID methods with supported key types."""
Expand All @@ -102,6 +109,7 @@ def __init__(self) -> None:
WEB.method_name: WEB,
PEER2.method_name: PEER2,
PEER4.method_name: PEER4,
TDW.method_name: TDW,
}

def registered(self, method: str) -> bool:
Expand Down
Loading

0 comments on commit 2e061bd

Please sign in to comment.