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

Ledger Endpoint for OpenAPI #178

Merged
merged 9 commits into from
Sep 17, 2019
2 changes: 2 additions & 0 deletions aries_cloudagent/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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
from ..ledger.routes import register as register_ledger


async def register_module_routes(app: web.Application):
Expand All @@ -43,3 +44,4 @@ async def register_module_routes(app: web.Application):
await register_v10_issue_credential(app)
await register_v10_present_proof(app)
await register_wallet(app)
await register_ledger(app)
13 changes: 13 additions & 0 deletions aries_cloudagent/ledger/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ async def update_endpoint_for_did(self, did: str, endpoint: str) -> bool:
endpoint: The endpoint address
"""

@abstractmethod
async def register_nym(self, did: str, verkey: str, alias: str = None,
role: str = None):
"""
Register a nym on the ledger.

Args:
did: DID to register on the ledger.
verkey: The verification key of the keypair.
alias: Human-friendly alias to assign to the DID.
role: For permissioned ledgers, what role should the new DID have.
"""

@abstractmethod
def nym_to_did(self, nym: str) -> str:
"""Format a nym with the ledger's DID prefix."""
Expand Down
16 changes: 16 additions & 0 deletions aries_cloudagent/ledger/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,22 @@ async def update_endpoint_for_did(self, did: str, endpoint: str) -> bool:
return True
return False

async def register_nym(self, did: str, verkey: str, alias: str = None,
role: str = None):
"""
Register a nym on the ledger.

Args:
did: DID to register on the ledger.
verkey: The verification key of the keypair.
alias: Human-friendly alias to assign to the DID.
role: For permissioned ledgers, what role should the new DID have.
"""
public_did = await self.wallet.get_public_did()
r = await indy.ledger.build_nym_request(public_did and public_did.did,
did, verkey, alias, role)
await self._submit(r, True, True)

def nym_to_did(self, nym: str) -> str:
"""Format a nym with the ledger's DID prefix."""
if nym:
Expand Down
117 changes: 117 additions & 0 deletions aries_cloudagent/ledger/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Ledger admin routes."""

from aiohttp import web
from aiohttp_apispec import docs

from .base import BaseLedger
from .error import LedgerTransactionError


@docs(
tags=["ledger"],
summary="Send a NYM registration to the ledger.",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"},
"required": True},
{"name": "verkey", "in": "query", "schema": {"type": "string"},
"required": True},
{"name": "alias", "in": "query", "schema": {"type": "string"},
"required": False},
{"name": "role", "in": "query", "schema": {"type": "string"},
"required": False}
]
)
async def register_ledger_nym(request: web.BaseRequest):
"""
Request handler for registering a NYM with the ledger.

Args:
request: aiohttp request object
"""
context = request.app["request_context"]
ledger = await context.inject(BaseLedger, required=False)
if not ledger:
raise web.HTTPForbidden()

did = request.query.get("did")
verkey = request.query.get("verkey")
if not did or not verkey:
raise web.HTTPBadRequest()

alias, role = request.query.get("alias"), request.query.get("role")
success = False
async with ledger:
try:
await ledger.register_nym(did, verkey, alias, role)
success = True
except LedgerTransactionError as e:
raise web.HTTPForbidden(text=e.message)
return web.json_response({"success": success})


@docs(
tags=["ledger"],
summary="Get the verkey for a DID from the ledger.",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"}, "required": True}
]
)
async def get_did_verkey(request: web.BaseRequest):
"""
Request handler for getting a verkey for a DID from the ledger.

Args:
request: aiohttp request object
"""
context = request.app["request_context"]
ledger = await context.inject(BaseLedger, required=False)
if not ledger:
raise web.HTTPForbidden()

did = request.query.get("did")
if not did:
raise web.HTTPBadRequest()

async with ledger:
r = await ledger.get_key_for_did(did)
return web.json_response({"verkey": r})


@docs(
tags=["ledger"],
summary="Get the endpoint for a DID from the ledger.",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"}, "required": True}
]
)
async def get_did_endpoint(request: web.BaseRequest):
"""
Request handler for getting a verkey for a DID from the ledger.

Args:
request: aiohttp request object
"""
context = request.app["request_context"]
ledger = await context.inject(BaseLedger, required=False)
if not ledger:
raise web.HTTPForbidden()

did = request.query.get("did")
if not did:
raise web.HTTPBadRequest()

async with ledger:
r = await ledger.get_endpoint_for_did(did)
return web.json_response({"endpoint": r})


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

app.add_routes(
[
web.post("/ledger/register-nym", register_ledger_nym),
web.get("/ledger/did-verkey", get_did_verkey),
web.get("/ledger/did-endpoint", get_did_endpoint)
]
)
85 changes: 85 additions & 0 deletions aries_cloudagent/ledger/tests/test_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from asynctest import TestCase as AsyncTestCase
from asynctest import mock as async_mock
import pytest

from aiohttp.web import HTTPForbidden

from ...config.injection_context import InjectionContext
from ...ledger.base import BaseLedger

from .. import routes as test_module


class TestLedgerRoutes(AsyncTestCase):
def setUp(self):
self.context = InjectionContext(enforce_typing=False)
self.ledger = async_mock.create_autospec(BaseLedger)
self.context.injector.bind_instance(BaseLedger, self.ledger)
self.app = {
"outbound_message_router": async_mock.CoroutineMock(),
"request_context": self.context,
}
self.test_did = "did"
self.test_verkey = "verkey"
self.test_endpoint = "http://localhost:8021"

async def test_missing_ledger(self):
request = async_mock.MagicMock()
request.app = self.app
self.context.injector.clear_binding(BaseLedger)

with self.assertRaises(HTTPForbidden):
await test_module.register_ledger_nym(request)

with self.assertRaises(HTTPForbidden):
await test_module.get_did_verkey(request)

with self.assertRaises(HTTPForbidden):
await test_module.get_did_endpoint(request)


async def test_get_verkey(self):
request = async_mock.MagicMock()
request.app = self.app
request.query = {"did": self.test_did}
with async_mock.patch.object(
test_module.web, "json_response", async_mock.Mock()
) as json_response:
self.ledger.get_key_for_did.return_value = self.test_verkey
result = await test_module.get_did_verkey(request)
json_response.assert_called_once_with(
{"verkey": self.ledger.get_key_for_did.return_value }
)
assert result is json_response.return_value

async def test_get_endpoint(self):
request = async_mock.MagicMock()
request.app = self.app
request.query = {"did": self.test_did}
with async_mock.patch.object(
test_module.web, "json_response", async_mock.Mock()
) as json_response:
self.ledger.get_endpoint_for_did.return_value = self.test_endpoint
result = await test_module.get_did_endpoint(request)
json_response.assert_called_once_with(
{"endpoint": self.ledger.get_endpoint_for_did.return_value}
)
assert result is json_response.return_value


async def test_register_nym(self):
request = async_mock.MagicMock()
request.app = self.app
request.query = {
"did": self.test_did,
"verkey": self.test_verkey
}
with async_mock.patch.object(
test_module.web, "json_response", async_mock.Mock()
) as json_response:
self.ledger.register_nym.return_value = True
result = await test_module.register_ledger_nym(request)
json_response.assert_called_once_with(
{"success": self.ledger.register_nym.return_value }
)
assert result is json_response.return_value