Skip to content

Commit

Permalink
Merge branch 'main' into gha_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewwhitehead authored Apr 14, 2021
2 parents 925016b + 4ed39ba commit ef092c0
Show file tree
Hide file tree
Showing 32 changed files with 671 additions and 173 deletions.
22 changes: 19 additions & 3 deletions aries_cloudagent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,24 @@ class AdminModulesSchema(OpenAPISchema):
)


class AdminConfigSchema(OpenAPISchema):
"""Schema for the config endpoint."""

config = fields.Dict(description="Configuration settings")


class AdminStatusSchema(OpenAPISchema):
"""Schema for the status endpoint."""

version = fields.Str(description="Version code")
label = fields.Str(description="Default label", allow_none=True)
timing = fields.Dict(description="Timing results", required=False)
conductor = fields.Dict(description="Conductor statistics", required=False)


class AdminResetSchema(OpenAPISchema):
"""Schema for the reset endpoint."""


class AdminStatusLivelinessSchema(OpenAPISchema):
"""Schema for the liveliness endpoint."""
Expand Down Expand Up @@ -230,6 +245,7 @@ def __init__(
webhook_router: Callable for delivering webhooks
conductor_stop: Conductor (graceful) stop for shutdown API call
task_queue: An optional task queue for handlers
conductor_stats: Conductor statistics API call
"""
self.app = None
self.admin_api_key = context.settings.get("admin.admin_api_key")
Expand Down Expand Up @@ -535,7 +551,7 @@ async def plugins_handler(self, request: web.BaseRequest):
return web.json_response({"result": plugins})

@docs(tags=["server"], summary="Fetch the server configuration")
@response_schema(AdminStatusSchema(), 200, description="")
@response_schema(AdminConfigSchema(), 200, description="")
async def config_handler(self, request: web.BaseRequest):
"""
Request handler for the server configuration.
Expand Down Expand Up @@ -567,7 +583,7 @@ async def config_handler(self, request: web.BaseRequest):
config["admin.webhook_urls"][index],
)

return web.json_response(config)
return web.json_response({"config": config})

@docs(tags=["server"], summary="Fetch the server status")
@response_schema(AdminStatusSchema(), 200, description="")
Expand All @@ -592,7 +608,7 @@ async def status_handler(self, request: web.BaseRequest):
return web.json_response(status)

@docs(tags=["server"], summary="Reset statistics")
@response_schema(AdminStatusSchema(), 200, description="")
@response_schema(AdminResetSchema(), 200, description="")
async def status_reset_handler(self, request: web.BaseRequest):
"""
Request handler for resetting the timing statistics.
Expand Down
10 changes: 5 additions & 5 deletions aries_cloudagent/admin/tests/test_admin_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def get_admin_server(
conductor_stop=async_mock.CoroutineMock(),
task_queue=TaskQueue(max_active=4) if task_queue else None,
conductor_stats=(
None if task_queue else async_mock.CoroutineMock(return_value=[1, 2])
None if task_queue else async_mock.CoroutineMock(return_value={"a": 1})
),
)

Expand Down Expand Up @@ -394,10 +394,10 @@ async def test_query_config(self):
f"http://127.0.0.1:{self.port}/status/config",
headers={"x-api-key": "test-api-key"},
) as response:
result = json.loads(await response.text())
assert "admin.admin_insecure_mode" in result
config = json.loads(await response.text())["config"]
assert "admin.admin_insecure_mode" in config
assert all(
k not in result
k not in config
for k in [
"admin.admin_api_key",
"multitenant.jwt_secret",
Expand All @@ -407,7 +407,7 @@ async def test_query_config(self):
"wallet.storage.creds",
]
)
assert result["admin.webhook_urls"] == [
assert config["admin.webhook_urls"] == [
"localhost:8123/abc",
"localhost:8123/def",
]
Expand Down
103 changes: 75 additions & 28 deletions aries_cloudagent/connections/base_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,30 @@
"""

import logging
from typing import List, Sequence, Tuple

from typing import Sequence, Tuple, List
from pydid import DIDDocument as ResolvedDocument
from pydid.doc.didcomm_service import DIDCommService
from pydid.doc.verification_method import VerificationMethod

from ..core.error import BaseError
from ..core.profile import ProfileSession
from ..ledger.base import BaseLedger
from ..protocols.connections.v1_0.messages.connection_invitation import (
ConnectionInvitation,
)
from ..protocols.coordinate_mediation.v1_0.models.mediation_record import (
MediationRecord,
)
from ..resolver.base import ResolverError
from ..resolver.did_resolver import DIDResolver
from ..storage.base import BaseStorage
from ..storage.error import StorageNotFoundError
from ..storage.record import StorageRecord
from ..wallet.base import BaseWallet, DIDInfo
from ..wallet.util import did_key_to_naked

from .models.conn_record import ConnRecord
from .models.connection_target import ConnectionTarget
from .models.diddoc import (
DIDDoc,
PublicKey,
PublicKeyType,
Service,
)
from .models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service


class BaseConnectionManagerError(BaseError):
Expand All @@ -42,6 +40,7 @@ class BaseConnectionManager:

RECORD_TYPE_DID_DOC = "did_doc"
RECORD_TYPE_DID_KEY = "did_key"
SUPPORTED_KEY_TYPES = (PublicKeyType.ED25519_SIG_2018.ver_type,)

def __init__(self, session: ProfileSession):
"""
Expand Down Expand Up @@ -209,18 +208,62 @@ async def remove_keys_for_did(self, did: str):
storage = self._session.inject(BaseStorage)
await storage.delete_all_records(self.RECORD_TYPE_DID_KEY, {"did": did})

async def _get_from_ledger(self, public_did: str) -> Tuple[str, Sequence[str]]:
"""Get endpoint and recipient keys for public DID from ledger."""
ledger = self._session.inject(BaseLedger, required=False)
if not ledger:
async def resolve_invitation(self, did: str):
"""
Resolve invitation with the DID Resolver.
Args:
did: Document ID to resolve
"""
if not did.startswith("did:"):
# DID is bare indy "nym"
# prefix with did:sov: for backwards compatibility
did = f"did:sov:{did}"

resolver = self._session.inject(DIDResolver)
try:
doc: ResolvedDocument = await resolver.resolve(self._session.profile, did)
except ResolverError as error:
raise BaseConnectionManagerError(
"Failed to resolve public DID in invitation"
) from error

if not doc.service:
raise BaseConnectionManagerError(
"Cannot connect via public DID that has no associated services"
)

didcomm_services = sorted(
[service for service in doc.service if isinstance(service, DIDCommService)],
key=lambda service: service.priority,
)

if not didcomm_services:
raise BaseConnectionManagerError(
f"Cannot resolve DID {public_did} without ledger instance"
"Cannot connect via public DID that has no associated DIDComm services"
)
async with ledger:
endpoint = await ledger.get_endpoint_for_did(public_did)
recipient_keys = [await ledger.get_key_for_did(public_did)]

return (endpoint, recipient_keys)
first_didcomm_service, *_ = didcomm_services

endpoint = first_didcomm_service.endpoint
recipient_keys: List[VerificationMethod] = [
doc.dereference(url) for url in first_didcomm_service.recipient_keys
]
routing_keys: List[VerificationMethod] = [
doc.dereference(url) for url in first_didcomm_service.routing_keys
]

for key in [*recipient_keys, *routing_keys]:
if key.suite.type not in self.SUPPORTED_KEY_TYPES:
raise BaseConnectionManagerError(
f"Key type {key.suite.type} is not supported"
)

return (
endpoint,
[key.material for key in recipient_keys],
[key.material for key in routing_keys],
)

async def fetch_connection_targets(
self, connection: ConnRecord
Expand Down Expand Up @@ -248,22 +291,26 @@ async def fetch_connection_targets(
invitation = await connection.retrieve_invitation(self._session)
if isinstance(invitation, ConnectionInvitation): # conn protocol invitation
if invitation.did:
# populate recipient keys, endpoint from ledger
(endpoint, recipient_keys) = await self._get_from_ledger(
invitation.did
)
routing_keys = []
did = invitation.did
(
endpoint,
recipient_keys,
routing_keys,
) = await self.resolve_invitation(did)

else:
endpoint = invitation.endpoint
recipient_keys = invitation.recipient_keys
routing_keys = invitation.routing_keys
else: # out-of-band invitation
if invitation.service_dids:
# populate recipient keys, endpoint from ledger
(endpoint, recipient_keys) = await self._get_from_ledger(
invitation.service_dids[0]
)
routing_keys = []
did = invitation.service_dids[0]
(
endpoint,
recipient_keys,
routing_keys,
) = await self.resolve_invitation(did)

else:
endpoint = invitation.service_blocks[0].service_endpoint
recipient_keys = [
Expand Down
2 changes: 1 addition & 1 deletion aries_cloudagent/connections/models/conn_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ async def metadata_get(
session (ProfileSession): session used for storage
key (str): key identifying metadata
default (Any): default value to get; type should be a JSON
compatible value.
compatible value.
Returns:
Any: metadata stored by key
Expand Down
5 changes: 5 additions & 0 deletions aries_cloudagent/core/plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ def register_plugin(self, module_name: str) -> ModuleType:
LOGGER.error(f"Module doesn't exist: {module_name}")
return None

# Any plugin with a setup method is considered valid.
if hasattr(mod, "setup"):
self._plugins[module_name] = mod
return mod

# Make an exception for non-protocol modules
# that contain admin routes and for old-style protocol
# modules without version support
Expand Down
22 changes: 19 additions & 3 deletions aries_cloudagent/core/tests/test_plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ async def test_register_plugin_no_definition(self):
ClassLoader, "load_module", async_mock.MagicMock()
) as load_module:
load_module.side_effect = [
async_mock.MagicMock(), # module
{}, # module
None, # routes
None, # message types
None, # definition
Expand All @@ -442,19 +442,35 @@ async def test_register_plugin_no_versions(self):
ClassLoader, "load_module", async_mock.MagicMock()
) as load_module:
load_module.side_effect = [
async_mock.MagicMock(), # module
{}, # module
None, # routes
None, # message types
"str-has-no-versions-attr", # definition without versions attr
]
assert self.registry.register_plugin("dummy") is None

async def test_register_plugin_has_setup(self):
class MODULE:
setup = "present"

obj = MODULE()
with async_mock.patch.object(
ClassLoader, "load_module", async_mock.MagicMock()
) as load_module:
load_module.side_effect = [
obj, # module
None, # routes
None, # message types
None, # definition without versions attr
]
assert self.registry.register_plugin("dummy") == obj

async def test_register_definitions_malformed(self):
with async_mock.patch.object(
ClassLoader, "load_module", async_mock.MagicMock()
) as load_module:
load_module.side_effect = [
async_mock.MagicMock(), # module
{}, # module
None, # routes
None, # message types
async_mock.MagicMock(versions="not-a-list"),
Expand Down
8 changes: 7 additions & 1 deletion aries_cloudagent/holder/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ class HolderModuleResponseSchema(OpenAPISchema):
class AttributeMimeTypesResultSchema(OpenAPISchema):
"""Result schema for credential attribute MIME type."""

results = fields.Dict(
keys=fields.Str(description="Attribute name"),
values=fields.Str(description="MIME type"),
allow_none=True,
)


class CredBriefSchema(OpenAPISchema):
"""Result schema with credential brief for credential query."""
Expand Down Expand Up @@ -263,7 +269,7 @@ async def credentials_attr_mime_types_get(request: web.BaseRequest):
credential_id = request.match_info["credential_id"]

holder = session.inject(IndyHolder)
return web.json_response(await holder.get_mime_type(credential_id))
return web.json_response({"results": await holder.get_mime_type(credential_id)})


@docs(tags=["credentials"], summary="Remove credential from wallet by id")
Expand Down
12 changes: 10 additions & 2 deletions aries_cloudagent/holder/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,21 @@ async def test_attribute_mime_types_get(self):
self.context.injector.bind_instance(
IndyHolder,
async_mock.MagicMock(
get_mime_type=async_mock.CoroutineMock(return_value=None)
get_mime_type=async_mock.CoroutineMock(
side_effect=[None, {"a": "application/jpeg"}]
)
),
)

with async_mock.patch.object(test_module.web, "json_response") as mock_response:
await test_module.credentials_attr_mime_types_get(self.request)
mock_response.assert_called_once_with(None)
mock_response.assert_called_once_with({"results": None})

with async_mock.patch.object(test_module.web, "json_response") as mock_response:
await test_module.credentials_attr_mime_types_get(self.request)
mock_response.assert_called_once_with(
{"results": {"a": "application/jpeg"}}
)

async def test_credentials_remove(self):
self.request.match_info = {"credential_id": "dummy"}
Expand Down
Loading

0 comments on commit ef092c0

Please sign in to comment.