Skip to content

Commit

Permalink
Merge branch 'main' into feat/pagination-ordering
Browse files Browse the repository at this point in the history
Signed-off-by: ff137 <[email protected]>
  • Loading branch information
ff137 committed Dec 2, 2024
2 parents f1626b2 + 27edc6e commit 63b1c84
Show file tree
Hide file tree
Showing 390 changed files with 21,908 additions and 20,015 deletions.
1 change: 1 addition & 0 deletions .devcontainer/post-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ markers = [
"postgres: Tests relating to the postgres storage plugin for Indy"]
junit_family = "xunit1"
asyncio_mode = auto
asyncio_default_fixture_loop_scope = module
EOF
2 changes: 1 addition & 1 deletion .github/actions/run-unit-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
- name: Tests
shell: bash
run: |
poetry run pytest --cov=acapy_agent --cov-report term-missing --cov-report xml --ignore-glob=/tests/* --ignore-glob=demo/* --ignore-glob=docker/* --ignore-glob=docs/* --ignore-glob=scripts/* --ignore-glob=scenarios/* 2>&1 | tee pytest.log
poetry run pytest -n auto --cov=acapy_agent --cov-report term-missing --cov-report xml --ignore-glob=/tests/* --ignore-glob=demo/* --ignore-glob=docker/* --ignore-glob=docs/* --ignore-glob=scripts/* --ignore-glob=scenarios/* 2>&1 | tee pytest.log
PYTEST_EXIT_CODE=${PIPESTATUS[0]}
if grep -Eq "RuntimeWarning: coroutine .* was never awaited" pytest.log; then
echo "Failure: Detected unawaited coroutine warning in pytest output."
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ jobs:
- name: Ruff Format and Lint Check
uses: chartboost/ruff-action@v1
with:
version: 0.5.7
version: 0.8.0
args: "format --check"
17 changes: 11 additions & 6 deletions .github/workflows/nigthly.yml → .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ name: Nightly Publish

on:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"
workflow_dispatch:

jobs:
tests:
if: github.repository_owner == 'openwallet-foundation' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.12"]
if: github.repository == 'openwallet-foundation/acapy' || github.event_name == 'workflow_dispatch'

steps:
- name: checkout
- name: Checkout
uses: actions/checkout@v4
- name: Tests

- name: Run Tests
uses: ./.github/actions/run-unit-tests
with:
python-version: ${{ matrix.python-version }}
Expand All @@ -30,13 +32,16 @@ jobs:
outputs:
commits_today: ${{ steps.commits.outputs.commits_today }}
date: ${{ steps.date.outputs.date }}
if: github.repository_owner == 'openwallet-foundation' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: print latest_commit
- name: Print Latest Commit
run: echo ${{ github.sha }}
- name: Get new commits

- name: Get New Commits
id: commits
run: echo "commits_today=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_OUTPUT

- name: Get Date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
test-reports/
test.lock

# Translations
*.mo
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
repos:
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.16.0
rev: v9.18.0
hooks:
- id: commitlint
stages: [commit-msg]
args: ["--config", ".commitlint.config.js"]
additional_dependencies: ['@commitlint/config-conventional']
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ensure this is synced with pyproject.toml
rev: v0.5.7
rev: v0.8.0
hooks:
# Run the linter
- id: ruff
Expand Down
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
7 changes: 2 additions & 5 deletions acapy_agent/admin/request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
from ..config.injector import InjectionError, Injector, InjectType
from ..config.settings import Settings
from ..core.profile import Profile, ProfileSession
from ..utils.classloader import DeferLoad

IN_MEM = DeferLoad("acapy_agent.core.in_memory.InMemoryProfile")


class AdminRequestContext:
Expand Down Expand Up @@ -112,10 +109,10 @@ def update_settings(self, settings: Mapping[str, object]):

@classmethod
def test_context(
cls, session_inject: Optional[dict] = None, profile: Optional[Profile] = None
cls, session_inject: dict, profile: Profile
) -> "AdminRequestContext":
"""Quickly set up a new admin request context for tests."""
ctx = AdminRequestContext(profile or IN_MEM.resolved.test_profile())
ctx = AdminRequestContext(profile)
setattr(ctx, "session_inject", {} if session_inject is None else session_inject)
setattr(ctx, "session", ctx._test_session)
return ctx
Expand Down
114 changes: 57 additions & 57 deletions acapy_agent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,35 @@
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
from aiohttp import web
from aiohttp_apispec import setup_aiohttp_apispec, validation_middleware
from uuid_utils import uuid4

from acapy_agent.wallet import singletons

from ..config.injection_context import InjectionContext
from ..config.logging import context_wallet_id
from ..core.event_bus import Event, EventBus
from ..core.plugin_registry import PluginRegistry
from ..core.profile import Profile
from ..ledger.error import LedgerConfigError, LedgerTransactionError
from ..messaging.responder import BaseResponder
from ..multitenant.base import BaseMultitenantManager, MultitenantManagerError
from ..multitenant.base import BaseMultitenantManager
from ..multitenant.error import InvalidTokenError, MultitenantManagerError
from ..storage.base import BaseStorage
from ..storage.error import StorageNotFoundError
from ..storage.type import RECORD_TYPE_ACAPY_UPGRADING
from ..transport.outbound.message import OutboundMessage
from ..transport.outbound.status import OutboundSendStatus
from ..transport.queue.basic import BasicMessageQueue
from ..utils import general as general_utils
from ..utils.extract_validation_error import extract_validation_error_message
from ..utils.stats import Collector
from ..utils.task_queue import TaskQueue
from ..version import __version__
from ..wallet import singletons
from ..wallet.anoncreds_upgrade import check_upgrade_completion_loop
from .base_server import BaseAdminServer
from .error import AdminSetupError
Expand Down Expand Up @@ -67,6 +68,8 @@
anoncreds_wallets = singletons.IsAnoncredsSingleton().wallets
in_progress_upgrades = singletons.UpgradeInProgressSingleton()

status_paths = ("/status/live", "/status/ready")


class AdminResponder(BaseResponder):
"""Handle outgoing messages from message handlers."""
Expand Down Expand Up @@ -133,36 +136,56 @@ def send_fn(self) -> Coroutine:
async def ready_middleware(request: web.BaseRequest, handler: Coroutine):
"""Only continue if application is ready to take work."""

if str(request.rel_url).rstrip("/") in (
"/status/live",
"/status/ready",
) or request.app._state.get("ready"):
try:
return await handler(request)
except (LedgerConfigError, LedgerTransactionError) as e:
# fatal, signal server shutdown
LOGGER.error("Shutdown with %s", str(e))
request.app._state["ready"] = False
request.app._state["alive"] = False
raise
except web.HTTPFound as e:
# redirect, typically / -> /api/doc
LOGGER.info("Handler redirect to: %s", e.location)
raise
except asyncio.CancelledError:
# redirection spawns new task and cancels old
LOGGER.debug("Task cancelled")
raise
except Exception as e:
# some other error?
LOGGER.error("Handler error with exception: %s", str(e))
import traceback

print("\n=================")
traceback.print_exc()
raise

raise web.HTTPServiceUnavailable(reason="Shutdown in progress")
is_status_check = str(request.rel_url).rstrip("/") in status_paths
is_app_ready = request.app._state.get("ready")

if not (is_status_check or is_app_ready):
raise web.HTTPServiceUnavailable(reason="Shutdown in progress")

try:
return await handler(request)
except web.HTTPFound as e:
# redirect, typically / -> /api/doc
LOGGER.info("Handler redirect to: %s", e.location)
raise
except asyncio.CancelledError:
# redirection spawns new task and cancels old
LOGGER.debug("Task cancelled")
raise
except (web.HTTPUnauthorized, jwt.InvalidTokenError, InvalidTokenError) as e:
LOGGER.info(
"Unauthorized access during %s %s: %s", request.method, request.path, e
)
raise web.HTTPUnauthorized(reason=str(e)) from e
except (web.HTTPBadRequest, MultitenantManagerError) as e:
LOGGER.info("Bad request during %s %s: %s", request.method, request.path, e)
raise web.HTTPBadRequest(reason=str(e)) from e
except (web.HTTPNotFound, StorageNotFoundError) as e:
LOGGER.info(
"Not Found error occurred during %s %s: %s",
request.method,
request.path,
e,
)
raise web.HTTPNotFound(reason=str(e)) from e
except web.HTTPUnprocessableEntity as e:
validation_error_message = extract_validation_error_message(e)
LOGGER.info(
"Unprocessable Entity occurred during %s %s: %s",
request.method,
request.path,
validation_error_message,
)
raise
except (LedgerConfigError, LedgerTransactionError) as e:
# fatal, signal server shutdown
LOGGER.critical("Shutdown with %s", str(e))
request.app._state["ready"] = False
request.app._state["alive"] = False
raise
except Exception as e:
LOGGER.exception("Handler error with exception:", exc_info=e)
raise


@web.middleware
Expand Down Expand Up @@ -257,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
Loading

0 comments on commit 63b1c84

Please sign in to comment.