Skip to content
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
30 changes: 30 additions & 0 deletions acapy_agent/admin/decorators/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Authentication decorators for the admin API."""

import functools
import re
from typing import Optional, Pattern

from aiohttp import web

Expand Down Expand Up @@ -48,6 +50,8 @@ def tenant_authentication(handler):
- check for a valid bearer token in the Autorization header if running
in multi-tenant mode
- check for a valid x-api-key header if running in single-tenant mode
- check if the base wallet has access to the requested path if running
in multi-tenant mode
"""

@functools.wraps(handler)
Expand All @@ -61,11 +65,15 @@ async def tenant_auth(request):
)
insecure_mode = bool(profile.settings.get("admin.admin_insecure_mode"))
multitenant_enabled = profile.settings.get("multitenant.enabled")
base_wallet_allowed_route = _base_wallet_route_access(
profile.settings.get("multitenant.base_wallet_routes"), request.path
)

# CORS fix: allow OPTIONS method access to paths without a token
if (
(multitenant_enabled and authorization_header)
or (not multitenant_enabled and valid_key)
or (multitenant_enabled and valid_key and base_wallet_allowed_route)
or insecure_mode
or request.method == "OPTIONS"
):
Expand All @@ -78,3 +86,25 @@ async def tenant_auth(request):
)

return tenant_auth


def _base_wallet_route_access(additional_routes: str, request_path: str) -> bool:
"""Check if request path matches additional routes."""
additional_routes_pattern = _build_additional_routes_pattern(additional_routes)
return _matches_additional_routes(additional_routes_pattern, request_path)


def _build_additional_routes_pattern(pattern_string: str) -> Optional[Pattern]:
"""Build pattern from space delimited list of paths."""
# create array and add word boundary to avoid false positives
if pattern_string:
paths = pattern_string.split(" ")
return re.compile("^((?:)" + "|".join(paths) + ")$")
return None


def _matches_additional_routes(pattern: Pattern, path: str) -> bool:
"""Matches request path to provided pattern."""
if pattern and path:
return bool(pattern.match(path))
return False
25 changes: 1 addition & 24 deletions acapy_agent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import re
import warnings
import weakref
from typing import Callable, Coroutine, Optional, Pattern, Sequence, cast
from typing import Callable, Coroutine, Optional

import aiohttp_cors
import jwt
Expand Down Expand Up @@ -280,29 +280,6 @@ def __init__(
self.websocket_queues = {}
self.site = None
self.multitenant_manager = context.inject_or(BaseMultitenantManager)
self._additional_route_pattern: Optional[Pattern] = None

@property
def additional_routes_pattern(self) -> Optional[Pattern]:
"""Pattern for configured additional routes to permit base wallet to access."""
if self._additional_route_pattern:
return self._additional_route_pattern

base_wallet_routes = self.context.settings.get("multitenant.base_wallet_routes")
base_wallet_routes = cast(Sequence[str], base_wallet_routes)
if base_wallet_routes:
self._additional_route_pattern = re.compile(
"^(?:" + "|".join(base_wallet_routes) + ")"
)
return None

def _matches_additional_routes(self, path: str) -> bool:
"""Path matches additional_routes_pattern."""
pattern = self.additional_routes_pattern
if pattern:
return bool(pattern.match(path))

return False

async def make_application(self) -> web.Application:
"""Get the aiohttp application instance."""
Expand Down
24 changes: 24 additions & 0 deletions acapy_agent/admin/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,27 @@ async def test_multi_tenant_valid_auth_header(self):
decor_func = tenant_authentication(self.decorated_handler)
await decor_func(self.request)
self.decorated_handler.assert_called_once_with(self.request)

async def test_base_wallet_additional_route_allowed(self):
self.profile.settings["multitenant.base_wallet_routes"] = "/extra-route"
self.request = mock.MagicMock(
__getitem__=lambda _, k: self.request_dict[k],
headers={"x-api-key": "admin_api_key"},
method="POST",
path="/extra-route",
)
decor_func = tenant_authentication(self.decorated_handler)
await decor_func(self.request)
self.decorated_handler.assert_called_once_with(self.request)

async def test_base_wallet_additional_route_denied(self):
self.profile.settings["multitenant.base_wallet_routes"] = "/extra-route"
self.request = mock.MagicMock(
__getitem__=lambda _, k: self.request_dict[k],
headers={"x-api-key": "admin_api_key"},
method="POST",
path="/extra-route-wrong",
)
decor_func = tenant_authentication(self.decorated_handler)
with self.assertRaises(web.HTTPUnauthorized):
await decor_func(self.request)
6 changes: 3 additions & 3 deletions acapy_agent/anoncreds/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
from ..config.injection_context import InjectionContext
from ..core.error import BaseError
from ..core.profile import Profile
from .models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult
from .models.anoncreds_revocation import (
from .models.credential_definition import CredDef, CredDefResult, GetCredDefResult
from .models.revocation import (
GetRevListResult,
GetRevRegDefResult,
RevList,
RevListResult,
RevRegDef,
RevRegDefResult,
)
from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult
from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult

T = TypeVar("T")

Expand Down
6 changes: 3 additions & 3 deletions acapy_agent/anoncreds/default/did_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
from ....config.injection_context import InjectionContext
from ....core.profile import Profile
from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver
from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult
from ...models.anoncreds_revocation import (
from ...models.credential_definition import CredDef, CredDefResult, GetCredDefResult
from ...models.revocation import (
GetRevListResult,
GetRevRegDefResult,
RevList,
RevListResult,
RevRegDef,
RevRegDefResult,
)
from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult
from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult

LOGGER = logging.getLogger(__name__)

Expand Down
6 changes: 3 additions & 3 deletions acapy_agent/anoncreds/default/did_web/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
from ....config.injection_context import InjectionContext
from ....core.profile import Profile
from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver
from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult
from ...models.anoncreds_revocation import (
from ...models.credential_definition import CredDef, CredDefResult, GetCredDefResult
from ...models.revocation import (
GetRevListResult,
GetRevRegDefResult,
RevList,
RevListResult,
RevRegDef,
RevRegDefResult,
)
from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult
from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult


class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar):
Expand Down
2 changes: 1 addition & 1 deletion acapy_agent/anoncreds/default/legacy_indy/recover.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import indy_vdr
from anoncreds import RevocationRegistry, RevocationRegistryDefinition

from ...models.anoncreds_revocation import RevList
from ...models.revocation import RevList

LOGGER = logging.getLogger(__name__)

Expand Down
6 changes: 3 additions & 3 deletions acapy_agent/anoncreds/default/legacy_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@
)
from ...events import RevListFinishedEvent
from ...issuer import CATEGORY_CRED_DEF, AnonCredsIssuer, AnonCredsIssuerError
from ...models.anoncreds_cred_def import (
from ...models.credential_definition import (
CredDef,
CredDefResult,
CredDefState,
CredDefValue,
GetCredDefResult,
)
from ...models.anoncreds_revocation import (
from ...models.revocation import (
GetRevListResult,
GetRevRegDefResult,
RevList,
Expand All @@ -74,7 +74,7 @@
RevRegDefState,
RevRegDefValue,
)
from ...models.anoncreds_schema import (
from ...models.schema import (
AnonCredsSchema,
GetSchemaResult,
SchemaResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
import pytest
from anoncreds import RevocationRegistryDefinition

from acapy_agent.tests import mock

from ....models.anoncreds_revocation import RevList, RevRegDef, RevRegDefValue
from .....tests import mock
from ....models.revocation import RevList, RevRegDef, RevRegDefValue
from ..recover import (
RevocRecoveryException,
_check_tails_hash_for_inconsistency,
Expand Down
34 changes: 15 additions & 19 deletions acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,6 @@
from .....anoncreds.base import AnonCredsSchemaAlreadyExists
from .....anoncreds.default.legacy_indy import registry as test_module
from .....anoncreds.issuer import AnonCredsIssuer
from .....anoncreds.models.anoncreds_cred_def import (
CredDef,
CredDefResult,
CredDefValue,
CredDefValuePrimary,
)
from .....anoncreds.models.anoncreds_revocation import (
RevList,
RevListResult,
RevRegDef,
RevRegDefResult,
RevRegDefState,
RevRegDefValue,
)
from .....anoncreds.models.anoncreds_schema import (
AnonCredsSchema,
GetSchemaResult,
SchemaResult,
)
from .....askar.profile_anon import (
AskarAnoncredsProfileSession,
)
Expand All @@ -55,6 +36,21 @@
)
from .....tests import mock
from .....utils.testing import create_test_profile
from ....models.credential_definition import (
CredDef,
CredDefResult,
CredDefValue,
CredDefValuePrimary,
)
from ....models.revocation import (
RevList,
RevListResult,
RevRegDef,
RevRegDefResult,
RevRegDefState,
RevRegDefValue,
)
from ....models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult

B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii")
INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$"
Expand Down
2 changes: 1 addition & 1 deletion acapy_agent/anoncreds/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import NamedTuple, Optional

from ..core.event_bus import Event
from .models.anoncreds_revocation import RevRegDef
from .models.revocation import RevRegDef

CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished"
REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished"
Expand Down
4 changes: 2 additions & 2 deletions acapy_agent/anoncreds/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from pyld.jsonld import JsonLdProcessor
from uuid_utils import uuid4

from ..anoncreds.models.anoncreds_schema import AnonCredsSchema
from ..askar.profile_anon import AskarAnoncredsProfile
from ..core.error import BaseError
from ..core.profile import Profile
Expand All @@ -33,7 +32,8 @@
from ..vc.vc_ld import VerifiableCredential
from ..wallet.error import WalletNotFoundError
from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from .models.anoncreds_cred_def import CredDef
from .models.credential_definition import CredDef
from .models.schema import AnonCredsSchema
from .registry import AnonCredsRegistry

LOGGER = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions acapy_agent/anoncreds/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from .base import AnonCredsSchemaAlreadyExists, BaseAnonCredsError
from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from .events import CredDefFinishedEvent
from .models.anoncreds_cred_def import CredDef, CredDefResult
from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState
from .models.credential_definition import CredDef, CredDefResult
from .models.schema import AnonCredsSchema, SchemaResult, SchemaState
from .registry import AnonCredsRegistry

LOGGER = logging.getLogger(__name__)
Expand Down
Loading