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

Add admin routes for creating and listing wallet DIDs, adjusting the public DID #102

Merged
merged 4 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions aries_cloudagent/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ..messaging.basicmessage.routes import register as register_basicmessages
from ..messaging.discovery.routes import register as register_discovery
from ..messaging.trustping.routes import register as register_trustping
from ..wallet.routes import register as register_wallet


async def register_module_routes(app: web.Application):
Expand All @@ -33,3 +34,4 @@ async def register_module_routes(app: web.Application):
await register_basicmessages(app)
await register_discovery(app)
await register_trustping(app)
await register_wallet(app)
6 changes: 1 addition & 5 deletions aries_cloudagent/conductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,7 @@ async def setup(self):
# at the class level (!) should not be performed multiple times
collector.wrap(
ConnectionManager,
(
"get_connection_target",
"fetch_did_document",
"find_connection",
),
("get_connection_target", "fetch_did_document", "find_connection"),
)

async def start(self) -> None:
Expand Down
30 changes: 30 additions & 0 deletions aries_cloudagent/wallet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections import namedtuple
from typing import Sequence


KeyInfo = namedtuple("KeyInfo", "verkey metadata")

DIDInfo = namedtuple("DIDInfo", "did verkey metadata")
Expand Down Expand Up @@ -184,6 +185,35 @@ async def get_public_did(self) -> DIDInfo:

return None

async def set_public_did(self, did: str) -> DIDInfo:
"""
Assign the public did.

Returns:
The created `DIDInfo`

"""

# will raise an exception if not found
info = None if did is None else await self.get_local_did(did)

public = await self.get_public_did()
if public and info and public.did == info.did:
info = public
else:
if public:
metadata = public.metadata.copy()
del metadata["public"]
await self.replace_local_did_metadata(public.did, metadata)

if info:
metadata = info.metadata.copy()
metadata["public"] = True
await self.replace_local_did_metadata(info.did, metadata)
info = await self.get_local_did(info.did)

return info

@abstractmethod
async def get_local_dids(self) -> Sequence[DIDInfo]:
"""
Expand Down
215 changes: 215 additions & 0 deletions aries_cloudagent/wallet/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"""Wallet admin routes."""

from aiohttp import web
from aiohttp_apispec import docs, response_schema

from marshmallow import fields, Schema

from ..ledger.base import BaseLedger

from .base import DIDInfo, BaseWallet
from .error import WalletError


class DIDSchema(Schema):
"""Result schema for a DID."""

did = fields.Str()
verkey = fields.Str()
public = fields.Bool()


class DIDResultSchema(Schema):
"""Result schema for a DID."""

result = fields.Nested(DIDSchema())


class DIDListSchema(Schema):
"""Result schema for connection list."""

results = fields.List(fields.Nested(DIDSchema()))


def format_did_info(info: DIDInfo):
"""Serialize a DIDInfo object."""
if info:
return {
"did": info.did,
"verkey": info.verkey,
"public": info.metadata
and info.metadata.get("public")
and "true"
or "false",
}


@docs(
tags=["wallet"],
summary="List wallet DIDs",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"}, "required": False},
{
"name": "verkey",
"in": "query",
"schema": {"type": "string"},
"required": False,
},
{
"name": "public",
"in": "query",
"schema": {"type": "boolean"},
"required": False,
},
],
)
@response_schema(DIDListSchema, 200)
async def wallet_did_list(request: web.BaseRequest):
"""
Request handler for searching wallet DIDs.

Args:
request: aiohttp request object

Returns:
The DID list response

"""
context = request.app["request_context"]
wallet: BaseWallet = await context.inject(BaseWallet, required=False)
if not wallet:
raise web.HTTPForbidden()
filter_did = request.query.get("did")
filter_verkey = request.query.get("verkey")
filter_public = request.query.get("public")
results = []

if filter_public == "true":
info = await wallet.get_public_did()
if (
info
and (not filter_verkey or info.verkey == filter_verkey)
and (not filter_did or info.did == filter_did)
):
results.append(format_did_info(info))
elif filter_did:
try:
info = await wallet.get_local_did(filter_did)
except WalletError:
# badly formatted DID or record not found
info = None
if info and (not filter_verkey or info.verkey == filter_verkey):
results.append(format_did_info(info))
elif filter_verkey:
try:
info = await wallet.get_local_did_for_verkey(filter_verkey)
except WalletError:
info = None
if info:
results.append(format_did_info(info))
else:
dids = await wallet.get_local_dids()
results = []
for info in dids:
results.append(format_did_info(info))

results.sort(key=lambda info: info["did"])
return web.json_response({"results": results})


@docs(tags=["wallet"], summary="Create a local DID")
@response_schema(DIDResultSchema, 200)
async def wallet_create_did(request: web.BaseRequest):
"""
Request handler for creating a new wallet DID.

Args:
request: aiohttp request object

Returns:
The DID list response

"""
context = request.app["request_context"]
wallet: BaseWallet = await context.inject(BaseWallet, required=False)
if not wallet:
raise web.HTTPForbidden()
info = await wallet.create_local_did()
return web.json_response({"result": format_did_info(info)})


@docs(tags=["wallet"], summary="Fetch the current public DID")
@response_schema(DIDResultSchema, 200)
async def wallet_get_public_did(request: web.BaseRequest):
"""
Request handler for fetching the current public DID.

Args:
request: aiohttp request object

Returns:
The DID list response

"""
context = request.app["request_context"]
wallet: BaseWallet = await context.inject(BaseWallet, required=False)
if not wallet:
raise web.HTTPForbidden()
info = await wallet.get_public_did()
return web.json_response({"result": format_did_info(info)})


@docs(
tags=["wallet"],
summary="Assign the current public DID",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"}, "required": True}
],
)
@response_schema(DIDResultSchema, 200)
async def wallet_set_public_did(request: web.BaseRequest):
"""
Request handler for setting the current public DID.

Args:
request: aiohttp request object

Returns:
The updated DID info

"""
context = request.app["request_context"]
wallet: BaseWallet = await context.inject(BaseWallet, required=False)
if not wallet:
raise web.HTTPForbidden()
did = request.query.get("did")
if not did:
raise web.HTTPBadRequest()
try:
info = await wallet.get_local_did(did)
except WalletError:
# DID not found or not in valid format
raise web.HTTPBadRequest()
info = await wallet.set_public_did(did)
if info:
# Publish endpoint if necessary
endpoint = context.settings.get("default_endpoint")
ledger = await context.inject(BaseLedger, required=False)
if ledger:
async with ledger:
await ledger.update_endpoint_for_did(info.did, endpoint)

return web.json_response({"result": format_did_info(info)})


async def register(app: web.Application):
"""Register routes."""

app.add_routes(
[
web.get("/wallet/did", wallet_did_list),
web.post("/wallet/did/create", wallet_create_did),
web.get("/wallet/did/public", wallet_get_public_did),
web.post("/wallet/did/public", wallet_set_public_did),
]
)
Loading