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

Feature/native did web resolver #1218

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions aries_cloudagent/messaging/valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,20 @@ def __init__(self):
)


class DIDWeb(Regexp):
"""Validate value against did:web specification."""

EXAMPLE = "did:web:example.com"
PATTERN = re.compile(r"^(did:web:)?([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+$")

def __init__(self):
"""Initializer."""

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


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

Expand Down
6 changes: 6 additions & 0 deletions aries_cloudagent/resolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ async def setup(context: InjectionContext):
registry.register(indy_resolver)
else:
LOGGER.warning("Ledger is not configured, not loading IndyDIDResolver")

web_resolver = ClassProvider(
"aries_cloudagent.resolver.default.web.WebDIDResolver"
).provide(context.settings, context.injector)
await web_resolver.setup(context)
registry.register(web_resolver)
33 changes: 33 additions & 0 deletions aries_cloudagent/resolver/default/tests/test_web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Test did:web Resolver."""

import pytest
from ..web import WebDIDResolver


@pytest.fixture
def resolver():
yield WebDIDResolver()


@pytest.fixture
def profile():
yield None


def test_transformation_domain_only(resolver):
did = "did:web:example.com"
url = resolver._WebDIDResolver__transform_to_url(did)
assert url == "https://example.com/.well-known/did.json"


def test_transformation_domain_with_path(resolver):
did = "did:web:example.com:department:example"
url = resolver._WebDIDResolver__transform_to_url(did)
assert url == "https://example.com/department/example/did.json"


# TODO: Enable this test as soon as pyDID accepts % char in DID
# def test_transformation_domain_with_port(resolver):
# did = 'did:web:localhost%3A8443'
# url = resolver._WebDIDResolver__transform_to_url(did)
# assert url == "https://localhost:443/.well-known/did.json"
79 changes: 79 additions & 0 deletions aries_cloudagent/resolver/default/web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Web DID Resolver."""

from typing import Sequence, Pattern
import aiohttp
import urllib.parse

from ...config.injection_context import InjectionContext
from ...core.profile import Profile
from ..base import (
BaseDIDResolver,
DIDNotFound,
ResolverError,
ResolverType,
)
from ...messaging.valid import DIDWeb
from pydid import DID, DIDDocument
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reorder imports:

import urllib.parse

from typing import Sequence, Pattern

import aiohttp

from pydid import DID, DIDDocument

from ...config.injection_context import InjectionContext
from ...core.profile import Profile
from ...messaging.valid import DIDWeb

from ..base import (
    BaseDIDResolver,
    DIDNotFound,
    ResolverError,
    ResolverType,
)



class WebDIDResolver(BaseDIDResolver):
"""Web DID Resolver."""

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

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

@property
def supported_did_regex(self) -> Pattern:
"""Return supported_did_regex of Web DID Resolver."""
return DIDWeb.PATTERN

def supported_methods(self) -> Sequence[str]:
"""Return list of supported methods."""
return ["web"]
Comment on lines +38 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should no longer be required, technically.


def __transform_to_url(self, did):
"""
Transform did to url.

according to
https://w3c-ccg.github.io/did-method-web/#read-resolve
"""

as_did = DID(did)
method_specific_id = as_did.method_specific_id
if ":" in method_specific_id:
# contains path
url = method_specific_id.replace(":", "/")
else:
# bare domain needs /.well-known path
url = method_specific_id + "/.well-known"

# Support encoded ports (See: https://github.com/w3c-ccg/did-method-web/issues/7)
url = urllib.parse.unquote(url)

return "https://" + url + "/did.json"

async def _resolve(self, profile: Profile, did: str) -> dict:
"""Resolve did:web DIDs."""

url = self.__transform_to_url(did)
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
try:
# Validate DIDDoc with pyDID
did_doc = DIDDocument.from_json(await response.text())
return did_doc.serialize()
except Exception as err:
raise ResolverError(
"Response was incorrectly formatted"
) from err
if response.status == 404:
raise DIDNotFound(f"No document found for {did}")
raise ResolverError(
"Could not find doc for {}: {}".format(did, await response.text())
)