Skip to content

Commit

Permalink
wallet record access on settings endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: Shaanjot Gill <[email protected]>
  • Loading branch information
shaangill025 committed Jun 27, 2023
1 parent 9dc9f4d commit 0b52306
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 40 deletions.
16 changes: 15 additions & 1 deletion aries_cloudagent/admin/request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ def __init__(
profile: Profile,
*,
context: InjectionContext = None,
settings: Mapping[str, object] = None
settings: Mapping[str, object] = None,
root_profile: Profile = None,
metadata: dict = None
):
"""Initialize an instance of AdminRequestContext."""
self._context = (context or profile.context).start_scope("admin", settings)
self._profile = profile
self._root_profile = root_profile
self._metadata = metadata

@property
def injector(self) -> Injector:
Expand All @@ -39,6 +43,16 @@ def profile(self) -> Profile:
"""Accessor for the associated `Profile` instance."""
return self._profile

@property
def root_profile(self) -> Optional[Profile]:
"""Accessor for the associated root_profile instance."""
return self._root_profile

@property
def metadata(self) -> dict:
"""Accessor for the associated metadata."""
return self._metadata

@property
def settings(self) -> Settings:
"""Accessor for the context settings."""
Expand Down
23 changes: 21 additions & 2 deletions aries_cloudagent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ async def check_multitenant_authorization(request: web.Request, handler):
async def setup_context(request: web.Request, handler):
authorization_header = request.headers.get("Authorization")
profile = self.root_profile

meta_data = {}
# Multitenancy context setup
if self.multitenant_manager and authorization_header:
try:
Expand All @@ -397,6 +397,16 @@ async def setup_context(request: web.Request, handler):
profile = await self.multitenant_manager.get_profile_for_token(
self.context, token
)
(
walletid,
walletkey,
) = self.multitenant_manager.get_wallet_details_from_token(
token=token
)
meta_data = {
"wallet_id": walletid,
"wallet_key": walletkey,
}
except MultitenantManagerError as err:
raise web.HTTPUnauthorized(reason=err.roll_up)
except (jwt.InvalidTokenError, StorageNotFoundError):
Expand All @@ -411,7 +421,16 @@ async def setup_context(request: web.Request, handler):

# TODO may dynamically adjust the profile used here according to
# headers or other parameters
admin_context = AdminRequestContext(profile)
if self.multitenant_manager and authorization_header:
admin_context = AdminRequestContext(
profile=profile,
root_profile=self.root_profile,
metadata=meta_data,
)
else:
admin_context = AdminRequestContext(
profile=profile,
)

request["context"] = admin_context
request["outbound_message_router"] = responder.send
Expand Down
14 changes: 14 additions & 0 deletions aries_cloudagent/admin/tests/test_request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,26 @@ def setUp(self):
self.ctx = test_module.AdminRequestContext(InMemoryProfile.test_profile())
assert self.ctx.__class__.__name__ in str(self.ctx)

self.ctx_with_added_attrs = test_module.AdminRequestContext(
profile=InMemoryProfile.test_profile(),
root_profile=InMemoryProfile.test_profile(),
metadata={"test_attrib_key": "test_attrib_value"},
)
assert self.ctx_with_added_attrs.__class__.__name__ in str(
self.ctx_with_added_attrs
)

def test_session_transaction(self):
sesn = self.ctx.session()
assert isinstance(sesn, ProfileSession)
txn = self.ctx.transaction()
assert isinstance(txn, ProfileSession)

sesn = self.ctx_with_added_attrs.session()
assert isinstance(sesn, ProfileSession)
txn = self.ctx_with_added_attrs.transaction()
assert isinstance(txn, ProfileSession)

async def test_session_inject_x(self):
test_ctx = test_module.AdminRequestContext.test_context({Collector: None})
async with test_ctx.session() as test_sesn:
Expand Down
38 changes: 19 additions & 19 deletions aries_cloudagent/multitenant/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,31 @@
}

ACAPY_LIFECYCLE_CONFIG_FLAG_ARGS_MAP = {
"log_level": "log.level",
"invite_public": "debug.invite_public",
"public_invites": "public_invites",
"auto_accept_invites": "debug.auto_accept_invites",
"auto_accept_requests": "debug.auto_accept_requests",
"auto_ping_connection": "auto_ping_connection",
"monitor_ping": "debug.monitor_ping",
"auto_respond_messages": "debug.auto_respond_messages",
"auto_respond_credential_offer": "debug.auto_respond_credential_offer",
"auto_respond_credential_request": "debug.auto_respond_credential_request",
"auto_verify_presentation": "debug.auto_verify_presentation",
"notify_revocation": "revocation.notify",
"auto_request_endorsement": "endorser.auto_request",
"auto_write_transactions": "endorser.auto_write",
"auto_create_revocation_transactions": "endorser.auto_create_rev_reg",
"endorser_protocol_role": "endorser.protocol_role",
"log-level": "log.level",
"invite-public": "debug.invite_public",
"public-invites": "public_invites",
"auto-accept-invites": "debug.auto_accept_invites",
"auto-accept-requests": "debug.auto_accept_requests",
"auto-ping-connection": "auto_ping_connection",
"monitor-ping": "debug.monitor_ping",
"auto-respond-messages": "debug.auto_respond_messages",
"auto-respond-credential-offer": "debug.auto_respond_credential_offer",
"auto-respond-credential-request": "debug.auto_respond_credential_request",
"auto-verify-presentation": "debug.auto_verify_presentation",
"notify-revocation": "revocation.notify",
"auto-request-endorsement": "endorser.auto_request",
"auto-write-transactions": "endorser.auto_write",
"auto-create-revocation-transactions": "endorser.auto_create_rev_reg",
"endorser-protocol-role": "endorser.protocol_role",
}

ACAPY_ENDORSER_FLAGS_DEPENDENT_ON_AUTHOR_ROLE = [
"ACAPY_AUTO_REQUEST_ENDORSEMENT",
"ACAPY_AUTO_WRITE_TRANSACTIONS",
"ACAPY_CREATE_REVOCATION_TRANSACTIONS",
"auto_request_endorsement",
"auto_write_transactions",
"auto_create_revocation_transactions",
"auto-request-endorsement",
"auto-write-transactions",
"auto-create-revocation-transactions",
]


Expand Down
24 changes: 23 additions & 1 deletion aries_cloudagent/multitenant/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
import logging
from typing import Iterable, List, Optional, cast
from typing import Iterable, List, Optional, cast, Tuple

import jwt

Expand Down Expand Up @@ -318,6 +318,28 @@ async def create_auth_token(

return token

def get_wallet_details_from_token(self, token: str) -> Tuple[str, str]:
"""Get the wallet_id and wallet_key from provided token."""
jwt_secret = self._profile.context.settings.get("multitenant.jwt_secret")
token_body = jwt.decode(token, jwt_secret, algorithms=["HS256"])
wallet_id = token_body.get("wallet_id")
wallet_key = token_body.get("wallet_key")
return wallet_id, wallet_key

async def get_wallet_and_profile(
self, context: InjectionContext, wallet_id: str, wallet_key: str
) -> Tuple[WalletRecord, Profile]:
"""Get the wallet_record and profile associated with wallet id and key."""
extra_settings = {}
async with self._profile.session() as session:
wallet = await WalletRecord.retrieve_by_id(session, wallet_id)
if wallet.requires_external_key:
if not wallet_key:
raise WalletKeyMissingError()
extra_settings["wallet.key"] = wallet_key
profile = await self.get_wallet_profile(context, wallet, extra_settings)
return (wallet, profile)

async def get_profile_for_token(
self, context: InjectionContext, token: str
) -> Profile:
Expand Down
58 changes: 58 additions & 0 deletions aries_cloudagent/multitenant/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,64 @@ async def test_create_auth_token_unmanaged(self):
assert wallet_record.jwt_iat == iat
assert expected_token == token

async def test_get_wallet_details_from_token(self):
self.profile.settings["multitenant.jwt_secret"] = "very_secret_jwt"
wallet_record = WalletRecord(
key_management_mode=WalletRecord.MODE_MANAGED,
settings={"wallet.type": "indy", "wallet.key": "wallet_key"},
jwt_iat=100,
)
session = await self.profile.session()
await wallet_record.save(session)
token = jwt.encode(
{"wallet_id": wallet_record.wallet_id, "iat": 100},
"very_secret_jwt",
algorithm="HS256",
)
ret_wallet_id, ret_wallet_key = self.manager.get_wallet_details_from_token(
token
)
assert ret_wallet_id == wallet_record.wallet_id
assert not ret_wallet_key

token = jwt.encode(
{
"wallet_id": wallet_record.wallet_id,
"iat": 100,
"wallet_key": "wallet_key",
},
"very_secret_jwt",
algorithm="HS256",
)
ret_wallet_id, ret_wallet_key = self.manager.get_wallet_details_from_token(
token
)
assert ret_wallet_id == wallet_record.wallet_id
assert ret_wallet_key == "wallet_key"

async def test_get_wallet_and_profile(self):
self.profile.settings["multitenant.jwt_secret"] = "very_secret_jwt"
wallet_record = WalletRecord(
key_management_mode=WalletRecord.MODE_MANAGED,
settings={"wallet.type": "indy", "wallet.key": "wallet_key"},
jwt_iat=100,
)

session = await self.profile.session()
await wallet_record.save(session)

with async_mock.patch.object(
self.manager, "get_wallet_profile"
) as get_wallet_profile:
mock_profile = InMemoryProfile.test_profile()
get_wallet_profile.return_value = mock_profile

wallet, profile = await self.manager.get_wallet_and_profile(
self.profile.context, wallet_record.wallet_id, "wallet_key"
)
assert wallet == wallet_record
assert profile == mock_profile

async def test_get_profile_for_token_invalid_token_raises(self):
self.profile.settings["multitenant.jwt_secret"] = "very_secret_jwt"

Expand Down
60 changes: 51 additions & 9 deletions aries_cloudagent/settings/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
from marshmallow import fields

from ..admin.request_context import AdminRequestContext
from ..multitenant.base import BaseMultitenantManager
from ..core.error import BaseError
from ..messaging.models.openapi import OpenAPISchema
from ..multitenant.admin.routes import get_extra_settings_dict_per_tenant
from ..multitenant.admin.routes import (
get_extra_settings_dict_per_tenant,
ACAPY_LIFECYCLE_CONFIG_FLAG_ARGS_MAP,
)

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -41,6 +45,16 @@ class ProfileSettingsSchema(OpenAPISchema):
)


def _get_filtered_settings_dict(wallet_settings: dict):
"""Get filtered settings dict to display."""
filter_param_list = list(ACAPY_LIFECYCLE_CONFIG_FLAG_ARGS_MAP.values())
settings_dict = {}
for param in filter_param_list:
if param in wallet_settings:
settings_dict[param] = wallet_settings.get(param)
return settings_dict


@docs(
tags=["settings"],
summary="Update settings or config associated with the profile.",
Expand All @@ -55,21 +69,34 @@ async def update_profile_settings(request: web.BaseRequest):
request: aiohttp request object
"""
context: AdminRequestContext = request["context"]
root_profile = context.root_profile or context.profile
try:
body = await request.json()
extra_setting = get_extra_settings_dict_per_tenant(
extra_settings = get_extra_settings_dict_per_tenant(
body.get("extra_settings") or {}
)
context.profile.settings.update(extra_setting)
result = context.profile.settings
async with root_profile.session() as session:
multitenant_mgr = session.inject_or(BaseMultitenantManager)
if multitenant_mgr:
wallet_id = context.metadata.get("wallet_id")
wallet_record = await multitenant_mgr.update_wallet(
wallet_id, extra_settings
)
wallet_settings = wallet_record.settings
settings_dict = _get_filtered_settings_dict(wallet_settings)
else:
root_profile.context.update_settings(extra_settings)
settings_dict = _get_filtered_settings_dict(
(context.profile.settings).to_dict()
)
except BaseError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err
return web.json_response(result.to_dict())
return web.json_response(settings_dict)


@docs(
tags=["settings"],
summary="Get the settings or config associated with the profile.",
summary="Get the configurable settings associated with the profile.",
)
@response_schema(ProfileSettingsSchema(), 200, description="")
async def get_profile_settings(request: web.BaseRequest):
Expand All @@ -80,12 +107,27 @@ async def get_profile_settings(request: web.BaseRequest):
request: aiohttp request object
"""
context: AdminRequestContext = request["context"]

root_profile = context.root_profile or context.profile
try:
result = context.profile.settings
async with root_profile.session() as session:
multitenant_mgr = session.inject_or(BaseMultitenantManager)
if multitenant_mgr:
wallet_id = context.metadata.get("wallet_id")
wallet_key = context.metadata.get("wallet_key")
wallet_record, profile = await multitenant_mgr.get_wallet_and_profile(
root_profile.context, wallet_id, wallet_key
)
profile_settings = profile.settings.to_dict()
wallet_settings = wallet_record.settings
all_settings = {**profile_settings, **wallet_settings}
settings_dict = _get_filtered_settings_dict(all_settings)
else:
settings_dict = _get_filtered_settings_dict(
(root_profile.settings).to_dict()
)
except BaseError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err
return web.json_response(result.to_dict())
return web.json_response(settings_dict)


async def register(app: web.Application):
Expand Down
Loading

0 comments on commit 0b52306

Please sign in to comment.