Skip to content

Commit

Permalink
Merge pull request #479 from sklump/evergreen-server-marshmallow-univ…
Browse files Browse the repository at this point in the history
…erse

Evergreen server marshmallow universe
  • Loading branch information
andrewwhitehead authored Apr 27, 2020
2 parents 3f3c7dc + 6c45fbd commit 4290bda
Show file tree
Hide file tree
Showing 40 changed files with 1,127 additions and 625 deletions.
17 changes: 11 additions & 6 deletions aries_cloudagent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import uuid

from aiohttp import web
from aiohttp_apispec import docs, response_schema, setup_aiohttp_apispec
from aiohttp_apispec import (
docs,
response_schema,
setup_aiohttp_apispec,
validation_middleware,
)
import aiohttp_cors

from marshmallow import fields, Schema
Expand Down Expand Up @@ -149,7 +154,7 @@ def __init__(
async def make_application(self) -> web.Application:
"""Get the aiohttp application instance."""

middlewares = []
middlewares = [validation_middleware]

admin_api_key = self.context.settings.get("admin.admin_api_key")
admin_insecure_mode = self.context.settings.get("admin.admin_insecure_mode")
Expand Down Expand Up @@ -203,11 +208,11 @@ async def collect_stats(request, handler):

app.add_routes(
[
web.get("/", self.redirect_handler),
web.get("/plugins", self.plugins_handler),
web.get("/status", self.status_handler),
web.get("/", self.redirect_handler, allow_head=False),
web.get("/plugins", self.plugins_handler, allow_head=False),
web.get("/status", self.status_handler, allow_head=False),
web.post("/status/reset", self.status_reset_handler),
web.get("/ws", self.websocket_handler),
web.get("/ws", self.websocket_handler, allow_head=False),
]
)

Expand Down
17 changes: 15 additions & 2 deletions aries_cloudagent/admin/tests/test_admin_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from aiohttp import web
from asynctest import TestCase as AsyncTestCase
from asynctest.mock import patch
from asynctest.mock import CoroutineMock, patch

from ...config.default_context import DefaultContextBuilder
from ...config.injection_context import InjectionContext
Expand All @@ -12,7 +13,7 @@
from ...core.protocol_registry import ProtocolRegistry
from ...transport.outbound.message import OutboundMessage

from ..server import AdminServer
from ..server import AdminServer, AdminSetupError


class TestAdminServerBasic(AsyncTestCase):
Expand Down Expand Up @@ -64,6 +65,11 @@ async def test_start_stop(self):
await server.start()
await server.stop()

with patch.object(web.TCPSite, "start", CoroutineMock()) as mock_start:
mock_start.side_effect = OSError("Failure to launch")
with self.assertRaises(AdminSetupError):
await self.get_admin_server(settings).start()

async def test_responder_send(self):
message = OutboundMessage(payload="{}")
admin_server = self.get_admin_server()
Expand All @@ -75,14 +81,21 @@ async def test_responder_webhook(self):
admin_server = self.get_admin_server()
test_url = "target_url"
test_attempts = 99
admin_server.add_webhook_target(test_url, max_attempts=test_attempts)
admin_server.add_webhook_target(
target_url=test_url,
topic_filter=["*"], # cover vacuous filter
max_attempts=test_attempts,
)
test_topic = "test_topic"
test_payload = {"test": "TEST"}
await admin_server.responder.send_webhook(test_topic, test_payload)
assert self.webhook_results == [
(test_topic, test_payload, test_url, test_attempts)
]

admin_server.remove_webhook_target(target_url=test_url)
assert test_url not in admin_server.webhook_targets

async def test_import_routes(self):
# this test just imports all default admin routes
# for routes with associated tests, this shouldn't make a difference in coverage
Expand Down
9 changes: 4 additions & 5 deletions aries_cloudagent/connections/models/connection_record.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Handle connection information interface with non-secrets storage."""

from marshmallow import fields
from marshmallow.validate import OneOf
from marshmallow import fields, validate

from ...config.injection_context import InjectionContext
from ...messaging.models.base_record import BaseRecord, BaseRecordSchema
Expand Down Expand Up @@ -298,7 +297,7 @@ class Meta:
required=False,
description="Connection initiator: self, external, or multiuse",
example=ConnectionRecord.INITIATOR_SELF,
validate=OneOf(["self", "external", "multiuse"]),
validate=validate.OneOf(["self", "external", "multiuse"]),
)
invitation_key = fields.Str(
required=False, description="Public key for connection", **INDY_RAW_PUBLIC_KEY
Expand All @@ -317,7 +316,7 @@ class Meta:
required=False,
description="Connection acceptance: manual or auto",
example=ConnectionRecord.ACCEPT_AUTO,
validate=OneOf(["manual", "auto"]),
validate=validate.OneOf(["manual", "auto"]),
)
error_msg = fields.Str(
required=False,
Expand All @@ -328,7 +327,7 @@ class Meta:
required=False,
description="Invitation mode: once, multi, or static",
example=ConnectionRecord.INVITATION_MODE_ONCE,
validate=OneOf(["once", "multi", "static"]),
validate=validate.OneOf(["once", "multi", "static"]),
)
alias = fields.Str(
required=False,
Expand Down
72 changes: 44 additions & 28 deletions aries_cloudagent/holder/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
import json

from aiohttp import web
from aiohttp_apispec import docs, response_schema
from aiohttp_apispec import docs, match_info_schema, querystring_schema, response_schema
from marshmallow import fields, Schema

from .base import BaseHolder
from ..messaging.valid import INDY_CRED_DEF_ID, INDY_REV_REG_ID, INDY_SCHEMA_ID
from .base import BaseHolder, HolderError
from ..messaging.valid import (
INDY_CRED_DEF_ID,
INDY_REV_REG_ID,
INDY_SCHEMA_ID,
INDY_WQL,
NATURAL_NUM,
WHOLE_NUM,
UUIDFour,
)
from ..wallet.error import WalletNotFoundError


Expand Down Expand Up @@ -60,13 +68,32 @@ class CredentialSchema(Schema):
witness = fields.Nested(WitnessSchema)


class CredentialListSchema(Schema):
class CredentialsListSchema(Schema):
"""Result schema for a credential query."""

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


class CredentialsListQueryStringSchema(Schema):
"""Parameters and validators for query string with DID only."""

start = fields.Int(description="Start index", required=False, **WHOLE_NUM,)
count = fields.Int(
description="Maximum number to retrieve", required=False, **NATURAL_NUM,
)
wql = fields.Str(description="(JSON) WQL query", required=False, **INDY_WQL,)


class CredIdMatchInfoSchema(Schema):
"""Path parameters and validators for request taking credential id."""

credential_id = fields.Str(
description="Credential identifier", required=True, example=UUIDFour.EXAMPLE
)


@docs(tags=["credentials"], summary="Fetch a credential from wallet by id")
@match_info_schema(CredIdMatchInfoSchema())
@response_schema(CredentialSchema(), 200)
async def credentials_get(request: web.BaseRequest):
"""
Expand All @@ -81,7 +108,7 @@ async def credentials_get(request: web.BaseRequest):
"""
context = request.app["request_context"]

credential_id = request.match_info["id"]
credential_id = request.match_info["credential_id"]

holder: BaseHolder = await context.inject(BaseHolder)
try:
Expand All @@ -94,6 +121,7 @@ async def credentials_get(request: web.BaseRequest):


@docs(tags=["credentials"], summary="Remove a credential from the wallet by id")
@match_info_schema(CredIdMatchInfoSchema())
async def credentials_remove(request: web.BaseRequest):
"""
Request handler for searching connection records.
Expand All @@ -107,7 +135,7 @@ async def credentials_remove(request: web.BaseRequest):
"""
context = request.app["request_context"]

credential_id = request.match_info["id"]
credential_id = request.match_info["credential_id"]

holder: BaseHolder = await context.inject(BaseHolder)
try:
Expand All @@ -119,25 +147,10 @@ async def credentials_remove(request: web.BaseRequest):


@docs(
tags=["credentials"],
parameters=[
{
"name": "start",
"in": "query",
"schema": {"type": "string"},
"required": False,
},
{
"name": "count",
"in": "query",
"schema": {"type": "string"},
"required": False,
},
{"name": "wql", "in": "query", "schema": {"type": "string"}, "required": False},
],
summary="Fetch credentials from wallet",
tags=["credentials"], summary="Fetch credentials from wallet",
)
@response_schema(CredentialListSchema(), 200)
@querystring_schema(CredentialsListQueryStringSchema())
@response_schema(CredentialsListSchema(), 200)
async def credentials_list(request: web.BaseRequest):
"""
Request handler for searching credential records.
Expand All @@ -163,7 +176,10 @@ async def credentials_list(request: web.BaseRequest):
count = int(count) if isinstance(count, str) else 10

holder: BaseHolder = await context.inject(BaseHolder)
credentials = await holder.get_credentials(start, count, wql)
try:
credentials = await holder.get_credentials(start, count, wql)
except HolderError as x_holder:
raise web.HTTPBadRequest(reason=x_holder.message)

return web.json_response({"results": credentials})

Expand All @@ -173,8 +189,8 @@ async def register(app: web.Application):

app.add_routes(
[
web.get("/credential/{id}", credentials_get),
web.post("/credential/{id}/remove", credentials_remove),
web.get("/credentials", credentials_list),
web.get("/credential/{credential_id}", credentials_get, allow_head=False),
web.post("/credential/{credential_id}/remove", credentials_remove),
web.get("/credentials", credentials_list, allow_head=False),
]
)
79 changes: 40 additions & 39 deletions aries_cloudagent/ledger/routes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Ledger admin routes."""

from aiohttp import web
from aiohttp_apispec import docs, request_schema, response_schema
from aiohttp_apispec import docs, querystring_schema, request_schema, response_schema

from marshmallow import fields, Schema
from marshmallow import fields, Schema, validate

from ..messaging.valid import INDY_DID, INDY_RAW_PUBLIC_KEY
from .base import BaseLedger
from .error import LedgerTransactionError

Expand Down Expand Up @@ -55,31 +56,33 @@ class TAAAcceptSchema(Schema):
mechanism = fields.Str()


class RegisterLedgerNymQueryStringSchema(Schema):
"""Query string parameters and validators for register ledger nym request."""

did = fields.Str(description="DID to register", required=True, **INDY_DID,)
verkey = fields.Str(
description="Verification key", required=True, **INDY_RAW_PUBLIC_KEY
)
alias = fields.Str(description="Alias", required=False, example="Barry",)
role = fields.Str(
description="Role",
required=False,
validate=validate.OneOf(
["TRUSTEE", "STEWARD", "ENDORSER", "NETWORK_MONITOR", "reset"]
),
)


class QueryStringDIDSchema(Schema):
"""Parameters and validators for query string with DID only."""

did = fields.Str(description="DID of interest", required=True, **INDY_DID)


@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,
},
],
tags=["ledger"], summary="Send a NYM registration to the ledger.",
)
@querystring_schema(RegisterLedgerNymQueryStringSchema())
async def register_ledger_nym(request: web.BaseRequest):
"""
Request handler for registering a NYM with the ledger.
Expand All @@ -97,7 +100,11 @@ async def register_ledger_nym(request: web.BaseRequest):
if not did or not verkey:
raise web.HTTPBadRequest()

alias, role = request.query.get("alias"), request.query.get("role")
alias = request.query.get("alias")
role = request.query.get("role")
if role == "reset": # indy: empty to reset, null for regular user
role = "" # visually: confusing - correct 'reset' to empty string here

success = False
async with ledger:
try:
Expand All @@ -109,12 +116,9 @@ async def register_ledger_nym(request: web.BaseRequest):


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


@docs(
tags=["ledger"],
summary="Get the endpoint for a DID from the ledger.",
parameters=[
{"name": "did", "in": "query", "schema": {"type": "string"}, "required": True}
],
tags=["ledger"], summary="Get the endpoint for a DID from the ledger.",
)
@querystring_schema(QueryStringDIDSchema())
async def get_did_endpoint(request: web.BaseRequest):
"""
Request handler for getting a verkey for a DID from the ledger.
Expand Down Expand Up @@ -233,9 +234,9 @@ async def register(app: web.Application):
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),
web.get("/ledger/taa", ledger_get_taa),
web.get("/ledger/did-verkey", get_did_verkey, allow_head=False),
web.get("/ledger/did-endpoint", get_did_endpoint, allow_head=False),
web.get("/ledger/taa", ledger_get_taa, allow_head=False),
web.post("/ledger/taa/accept", ledger_accept_taa),
]
)
Loading

0 comments on commit 4290bda

Please sign in to comment.