Skip to content

Commit

Permalink
Merge pull request openwallet-foundation#23 from burdettadam/feature/…
Browse files Browse the repository at this point in the history
…didresolver

feature/did_resolver
  • Loading branch information
dbluhm authored Feb 4, 2021
2 parents 0f4af0c + 22d790a commit 01b58a8
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 51 deletions.
10 changes: 9 additions & 1 deletion aries_cloudagent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from ..core.plugin_registry import PluginRegistry
from ..core.profile import ProfileManager, ProfileManagerProvider
from ..core.protocol_registry import ProtocolRegistry
from ..resolver.did_resolver import DIDResolver
from ..resolver.did_resolver_registry import DIDResolverRegistry
from ..tails.base import BaseTailsServer
from ..ledger.indy import IndySdkLedgerPool, IndySdkLedgerPoolProvider

Expand All @@ -17,7 +19,6 @@
from ..protocols.didcomm_prefix import DIDCommPrefix
from ..protocols.introduction.v0_1.base_service import BaseIntroductionService
from ..protocols.introduction.v0_1.demo_service import DemoIntroductionService

from ..transport.wire_format import BaseWireFormat
from ..utils.stats import Collector

Expand All @@ -41,6 +42,13 @@ async def build_context(self) -> InjectionContext:
# Global protocol registry
context.injector.bind_instance(ProtocolRegistry, ProtocolRegistry())

# Global did resolver registry
did_resolver_registry = DIDResolverRegistry()
context.injector.bind_instance(DIDResolverRegistry, did_resolver_registry)

# Global did resolver
context.injector.bind_instance(DIDResolver, DIDResolver(did_resolver_registry))

await self.bind_providers(context)
await self.load_plugins(context)

Expand Down
12 changes: 0 additions & 12 deletions aries_cloudagent/core/tests/test_did_resolver_registry.py

This file was deleted.

12 changes: 8 additions & 4 deletions aries_cloudagent/resolver/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
from typing import Sequence

from .diddoc import ResolvedDIDDoc
from ..core.profile import ProfileSession
from ..core.profile import Profile


class ResolverError(Exception):
"""Base class for resolver exceptions."""


class DidNotFound(ResolverError):
class DIDNotFound(ResolverError):
"""Raised when DID is not found in verifiable data registry."""


class DIDMethodNotSupported(ResolverError):
"""Raised when no resolver is registered for a given did method."""


class ResolverType(Enum):
"""Resolver Type declarations."""

Expand All @@ -35,7 +39,7 @@ def __init__(self, type_: ResolverType = None):
self.type = type_ or ResolverType.NON_NATIVE

@abstractmethod
async def setup(self, session: ProfileSession):
async def setup(self, profile: Profile):
"""Do asynchronous resolver setup."""

@property
Expand All @@ -53,5 +57,5 @@ def supports(self, method: str):
return method in self.supported_methods

@abstractmethod
async def resolve(self, session: ProfileSession, did: str) -> ResolvedDIDDoc:
async def resolve(self, profile: Profile, did: str) -> ResolvedDIDDoc:
"""Resolve a DID using this resolver."""
12 changes: 6 additions & 6 deletions aries_cloudagent/resolver/default/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"""
from typing import Sequence

from ...core.profile import ProfileSession
from ...core.profile import Profile
from ...ledger.indy import IndySdkLedger
from ...ledger.error import LedgerError
from ..base import BaseDIDResolver, DidNotFound, ResolverError, ResolverType
from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType
from ..did import DID
from ..diddoc import ResolvedDIDDoc

Expand All @@ -25,17 +25,17 @@ def __init__(self):
"""Initialize Indy Resolver."""
super().__init__(ResolverType.NATIVE)

async def setup(self, session: ProfileSession):
async def setup(self, profile: Profile):
"""Perform required setup for Indy DID resolution."""

@property
def supported_methods(self) -> Sequence[str]:
"""Return supported methods of Indy DID Resolver."""
return ["sov"]

async def resolve(self, session: ProfileSession, did: str) -> ResolvedDIDDoc:
async def resolve(self, profile: Profile, did: str) -> ResolvedDIDDoc:
"""Resolve an indy DID."""
ledger = session.inject(IndySdkLedger, required=False)
ledger = profile.inject(IndySdkLedger, required=False)
if not ledger:
raise NoIndyLedger("No Indy ledger isntance is configured.")

Expand All @@ -46,7 +46,7 @@ async def resolve(self, session: ProfileSession, did: str) -> ResolvedDIDDoc:
recipient_key = await ledger.get_key_for_did(str(did))
endpoint = await ledger.get_endpoint_for_did(str(did))
except LedgerError as err:
raise DidNotFound(f"DID {did} could not be resolved") from err
raise DIDNotFound(f"DID {did} could not be resolved") from err

doc = ResolvedDIDDoc(
{
Expand Down
31 changes: 16 additions & 15 deletions aries_cloudagent/resolver/default/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

from ..indy import IndyDIDResolver
from ....core.in_memory import InMemoryProfile
from ....core.profile import ProfileSession
from ....core.profile import Profile
from ....ledger.indy import IndySdkLedger
from ....ledger.error import LedgerError
from ...tests.test_did import TEST_DID0
from ...base import ResolverError, DidNotFound
from ...base import ResolverError, DIDNotFound

# pylint: disable=W0621

Expand All @@ -30,11 +30,11 @@ def ledger():


@pytest.fixture
def session(ledger):
"""Session fixture."""
session = InMemoryProfile.test_session()
session.context.injector.bind_instance(IndySdkLedger, ledger)
yield session
def profile(ledger):
"""Profile fixture."""
profile = InMemoryProfile.test_profile()
profile.context.injector.bind_instance(IndySdkLedger, ledger)
yield profile


def test_supported_methods(resolver: IndyDIDResolver):
Expand All @@ -44,23 +44,24 @@ def test_supported_methods(resolver: IndyDIDResolver):


@pytest.mark.asyncio
async def test_resolve(resolver: IndyDIDResolver, session: ProfileSession):
async def test_resolve(resolver: IndyDIDResolver, profile: Profile):
"""Test resolve method."""
assert await resolver.resolve(session, TEST_DID0)
assert await resolver.resolve(profile, TEST_DID0)


@pytest.mark.asyncio
async def test_resolve_x_no_ledger(resolver: IndyDIDResolver, session: ProfileSession):
async def test_resolve_x_no_ledger(resolver: IndyDIDResolver, profile: Profile):
"""Test resolve method with no ledger."""
session.context.injector.clear_binding(IndySdkLedger)
profile.context.injector.clear_binding(IndySdkLedger)
with pytest.raises(ResolverError):
await resolver.resolve(session, TEST_DID0)
await resolver.resolve(profile, TEST_DID0)


@pytest.mark.asyncio
async def test_resolve_x_did_not_found(
resolver: IndyDIDResolver, ledger: IndySdkLedger, session: ProfileSession
resolver: IndyDIDResolver, ledger: IndySdkLedger, profile: Profile
):
"""Test resolve method when no did is found."""
ledger.get_key_for_did.side_effect = LedgerError
with pytest.raises(DidNotFound):
await resolver.resolve(session, TEST_DID0)
with pytest.raises(DIDNotFound):
await resolver.resolve(profile, TEST_DID0)
72 changes: 72 additions & 0 deletions aries_cloudagent/resolver/did_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
the did resolver.
responsible for keeping track of all resolvers. more importantly
retrieving did's from different sources provided by the method type.
"""

import logging
from itertools import chain
from typing import Union

from ..core.profile import Profile
from ..resolver.base import BaseDIDResolver, DIDMethodNotSupported, DIDNotFound
from ..resolver.did import DID, DIDUrl # , DID_PATTERN
from ..resolver.diddoc import ResolvedDIDDoc # , ExternalResourceError
from .did_resolver_registry import DIDResolverRegistry

LOGGER = logging.getLogger(__name__)


class DIDResolver:
"""did resolver singleton."""

def __init__(self, registry: DIDResolverRegistry):
"""Initialize a `didresolver` instance."""
self.did_resolver_registry = registry

async def resolve(
self, profile: Profile, did: Union[str, DID]
) -> ResolvedDIDDoc:
"""Retrieve did doc from public registry."""
# TODO Cache results
if isinstance(did, str):
did = DID(did)
for resolver in self._match_did_to_resolver(did):
try:
LOGGER.debug("Resolving DID %s with %s", did, resolver)
return await resolver.resolve(profile, did)
except DIDNotFound:
LOGGER.debug("DID %s not found by resolver %s", did, resolver)

raise DIDNotFound(f"DID {did} could not be resolved.")

def _match_did_to_resolver(self, did: DID) -> BaseDIDResolver:
"""Generate supported DID Resolvers.
Native resolvers are yielded first, in registered order followed by
non-native resolvers in registered order.
"""
valid_resolvers = list(
filter(
lambda resolver: resolver.supports(did.method),
self.did_resolver_registry.resolvers,
)
)
native_resolvers = filter(lambda resolver: resolver.native, valid_resolvers)
non_native_resolvers = filter(
lambda resolver: not resolver.native, valid_resolvers
)
resolvers = list(chain(native_resolvers, non_native_resolvers))
if not resolvers:
raise DIDMethodNotSupported(f"{did.method} not supported")
return resolvers

async def dereference(
self, profile: Profile, did_url: str
) -> ResolvedDIDDoc:
"""Dereference a DID URL to its corresponding DID Doc object."""
# TODO Use cached DID Docs when possible
did_url = DIDUrl.parse(did_url)
doc = await self.resolve(profile, did_url.did)
return doc.dereference(did_url)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""In memmory storage for registering did resolvers"""
"""In memmory storage for registering did resolvers."""

import logging
from typing import Sequence
Expand All @@ -12,12 +12,11 @@ class DIDResolverRegistry:
def __init__(self):
"""Initialize list for did resolvers."""
self._resolvers = []
LOGGER.debug("Resolvers listed")

@property
def did_resolvers(
def resolvers(
self,
) -> Sequence[str]: # Todo: add priority filtering and return copy
) -> Sequence[str]:
"""Accessor for a list of all did resolvers."""
return self._resolvers

Expand Down
12 changes: 5 additions & 7 deletions aries_cloudagent/resolver/diddoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

import json
from typing import Sequence
from typing import Sequence, Union
from operator import itemgetter
from .did import DID, DIDUrl

Expand Down Expand Up @@ -70,10 +70,8 @@ def _valid_filter(service):
and "recipientKeys" in service
and "serviceEndpoint" in service
and "type" in service
and (
service["type"] == self.OLD_AGENT_SERVICE_TYPE
or service["type"] == self.AGENT_SERVICE_TYPE
)
and service["type"]
in (self.OLD_AGENT_SERVICE_TYPE, self.AGENT_SERVICE_TYPE)
)

# Filter out all but didcomm services with expected properties
Expand All @@ -93,9 +91,9 @@ def _valid_filter(service):
*(sorted(old, key=itemgetter("priority"))),
]

def dereference(self, did_url: str):
def dereference(self, did_url: Union[str, DIDUrl]):
"""Dereference values contained in this DID Document."""
parsed = DIDUrl.parse(did_url)
parsed = DIDUrl.parse(did_url) if isinstance(did_url, str) else did_url
if self.did != parsed.did:
raise ExternalResourceError(
"{} is not contained in this DID Document".format(did_url)
Expand Down
4 changes: 2 additions & 2 deletions aries_cloudagent/resolver/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ class ExampleDIDResolver(BaseDIDResolver):
def __init__(self):
super().__init__()

async def setup(self, session):
async def setup(self, profile):
pass

@property
def supported_methods(self):
return ["test"]

async def resolve(self, session, did: str) -> ResolvedDIDDoc:
async def resolve(self, profile, did: str) -> ResolvedDIDDoc:
return ResolvedDIDDoc({"id": "did:example:123"})


Expand Down
Loading

0 comments on commit 01b58a8

Please sign in to comment.