Skip to content

Commit 794b84a

Browse files
committed
auth5
1 parent b3989af commit 794b84a

File tree

17 files changed

+252
-39
lines changed

17 files changed

+252
-39
lines changed

src/app/endpoints/config.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Handler for REST API call to retrieve service configuration."""
22

33
import logging
4-
from typing import Any
4+
from typing import Annotated, Any
55

6-
from fastapi import APIRouter, Request
6+
from fastapi import APIRouter, Request, Depends
77

8+
from auth.interface import AuthTuple
9+
from auth import get_auth_dependency
810
from authorization.middleware import authorize
911
from configuration import configuration
1012
from models.config import Action, Configuration
@@ -13,6 +15,8 @@
1315
logger = logging.getLogger(__name__)
1416
router = APIRouter(tags=["config"])
1517

18+
auth_dependency = get_auth_dependency()
19+
1620

1721
get_config_responses: dict[int | str, dict[str, Any]] = {
1822
200: {
@@ -58,7 +62,10 @@
5862

5963
@router.get("/config", responses=get_config_responses)
6064
@authorize(Action.GET_CONFIG)
61-
def config_endpoint_handler(_request: Request) -> Configuration:
65+
async def config_endpoint_handler(
66+
auth: Annotated[AuthTuple, Depends(auth_dependency)],
67+
_request: Request,
68+
) -> Configuration:
6269
"""
6370
Handle requests to the /config endpoint.
6471
@@ -68,6 +75,9 @@ def config_endpoint_handler(_request: Request) -> Configuration:
6875
Returns:
6976
Configuration: The loaded service configuration object.
7077
"""
78+
# Used only for authorization
79+
_ = auth
80+
7181
# ensure that configuration is loaded
7282
check_configuration_loaded(configuration)
7383

src/app/endpoints/conversations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def simplify_session_data(session_data: dict) -> list[dict[str, Any]]:
148148

149149

150150
@router.get("/conversations", responses=conversations_list_responses)
151-
def get_conversations_list_endpoint_handler(
151+
async def get_conversations_list_endpoint_handler(
152152
auth: Any = Depends(auth_dependency),
153153
) -> ConversationsListResponse:
154154
"""Handle request to retrieve all conversations for the authenticated user."""

src/app/endpoints/feedback.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async def assert_feedback_enabled(_request: Request) -> None:
8282

8383
@router.post("", responses=feedback_response)
8484
@authorize(Action.FEEDBACK)
85-
def feedback_endpoint_handler(
85+
async def feedback_endpoint_handler(
8686
feedback_request: FeedbackRequest,
8787
auth: Annotated[AuthTuple, Depends(auth_dependency)],
8888
_ensure_feedback_enabled: Any = Depends(assert_feedback_enabled),

src/app/endpoints/info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
@router.get("/info", responses=get_info_responses)
3232
@authorize(Action.INFO)
33-
def info_endpoint_handler(
33+
async def info_endpoint_handler(
3434
auth: Annotated[AuthTuple, Depends(auth_dependency)],
3535
_request: Request,
3636
) -> InfoResponse:

src/app/endpoints/metrics.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
"""Handler for REST API call to provide metrics."""
22

3+
from typing import Annotated
34
from fastapi.responses import PlainTextResponse
4-
from fastapi import APIRouter, Request
5+
from fastapi import APIRouter, Request, Depends
56
from prometheus_client import (
67
generate_latest,
78
CONTENT_TYPE_LATEST,
89
)
910

11+
from auth.interface import AuthTuple
12+
from auth import get_auth_dependency
1013
from authorization.middleware import authorize
1114
from models.config import Action
1215
from metrics.utils import setup_model_metrics
1316

1417
router = APIRouter(tags=["metrics"])
1518

19+
auth_dependency = get_auth_dependency()
20+
1621

1722
@router.get("/metrics", response_class=PlainTextResponse)
1823
@authorize(Action.GET_METRICS)
19-
async def metrics_endpoint_handler(_request: Request) -> PlainTextResponse:
24+
async def metrics_endpoint_handler(
25+
auth: Annotated[AuthTuple, Depends(auth_dependency)],
26+
_request: Request,
27+
) -> PlainTextResponse:
2028
"""
2129
Handle request to the /metrics endpoint.
2230
@@ -27,6 +35,9 @@ async def metrics_endpoint_handler(_request: Request) -> PlainTextResponse:
2735
set up, then responds with the current metrics snapshot in
2836
Prometheus format.
2937
"""
38+
# Used only for authorization
39+
_ = auth
40+
3041
# Setup the model metrics if not already done. This is a one-time setup
3142
# and will not be run again on subsequent calls to this endpoint
3243
await setup_model_metrics()

src/app/endpoints/root.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@
779779

780780
@router.get("/", response_class=HTMLResponse)
781781
@authorize(Action.INFO)
782-
def root_endpoint_handler(
782+
async def root_endpoint_handler(
783783
auth: Annotated[AuthTuple, Depends(auth_dependency)],
784784
_request: Request,
785785
) -> HTMLResponse:

src/auth/jwk_token.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import logging
44
from asyncio import Lock
55
from typing import Any, Callable
6-
import json
76

87
from fastapi import Request, HTTPException, status
98
from authlib.jose import JsonWebKey, KeySet, jwt, Key
@@ -189,4 +188,4 @@ async def __call__(self, request: Request) -> tuple[str, str, str]:
189188

190189
logger.info("Successfully authenticated user %s (ID: %s)", username, user_id)
191190

192-
return user_id, username, json.dumps(claims)
191+
return user_id, username, user_token

src/authorization/engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def __init__(self, access_rules: list[AccessRule]):
145145

146146
async def check_access(self, action: Action, user_roles: UserRoles) -> bool:
147147
"""Check if the user has access to the specified action based on their roles."""
148-
if action != Action.ADMIN and self.check_access(action.ADMIN, user_roles):
148+
if action != Action.ADMIN and await self.check_access(Action.ADMIN, user_roles):
149149
# Recurse to check if the roles allow the user to perform the admin action,
150150
# if they do, then we allow any action
151151
return True

src/authorization/middleware.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
from functools import wraps, lru_cache
55
from typing import Any, Callable, Tuple
6-
6+
import asyncio
77
from fastapi import HTTPException, status
88

99
from authorization.engine import (
@@ -73,7 +73,7 @@ async def _perform_authorization_check(action: Action, kwargs: dict[str, Any]) -
7373
detail="Internal server error",
7474
) from exc
7575

76-
if not access_resolver.check_access(
76+
if not await access_resolver.check_access(
7777
action, await role_resolver.resolve_roles(auth)
7878
):
7979
raise HTTPException(
@@ -89,7 +89,6 @@ def decorator(func: Callable) -> Callable:
8989
@wraps(func)
9090
async def wrapper(*args: Any, **kwargs: Any) -> Any:
9191
await _perform_authorization_check(action, kwargs)
92-
return await func(*args, **kwargs)
9392

9493
return wrapper
9594

tests/unit/app/endpoints/test_config.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@
77
from configuration import AppConfig
88

99

10-
def test_config_endpoint_handler_configuration_not_loaded(mocker):
10+
@pytest.mark.asyncio
11+
async def test_config_endpoint_handler_configuration_not_loaded(mocker):
1112
"""Test the config endpoint handler."""
13+
# Mock authorization resolvers
14+
mock_resolvers = mocker.patch(
15+
"authorization.middleware.get_authorization_resolvers"
16+
)
17+
mock_role_resolver = mocker.AsyncMock()
18+
mock_access_resolver = mocker.AsyncMock()
19+
mock_role_resolver.resolve_roles.return_value = []
20+
mock_access_resolver.check_access.return_value = True
21+
mock_resolvers.return_value = (mock_role_resolver, mock_access_resolver)
22+
1223
mocker.patch(
1324
"app.endpoints.config.configuration._configuration",
1425
new=None,
@@ -20,14 +31,27 @@ def test_config_endpoint_handler_configuration_not_loaded(mocker):
2031
"type": "http",
2132
}
2233
)
23-
with pytest.raises(HTTPException) as e:
24-
config_endpoint_handler(request)
25-
assert e.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
26-
assert e.detail["response"] == "Configuration is not loaded"
34+
auth = ("test_user", "token", {})
35+
with pytest.raises(HTTPException) as exc_info:
36+
await config_endpoint_handler(auth=auth, _request=request)
2737

38+
assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
39+
assert exc_info.value.detail["response"] == "Configuration is not loaded"
2840

29-
def test_config_endpoint_handler_configuration_loaded():
41+
42+
@pytest.mark.asyncio
43+
async def test_config_endpoint_handler_configuration_loaded(mocker):
3044
"""Test the config endpoint handler."""
45+
# Mock authorization resolvers
46+
mock_resolvers = mocker.patch(
47+
"authorization.middleware.get_authorization_resolvers"
48+
)
49+
mock_role_resolver = mocker.AsyncMock()
50+
mock_access_resolver = mocker.AsyncMock()
51+
mock_role_resolver.resolve_roles.return_value = []
52+
mock_access_resolver.check_access.return_value = True
53+
mock_resolvers.return_value = (mock_role_resolver, mock_access_resolver)
54+
3155
config_dict = {
3256
"name": "foo",
3357
"service": {
@@ -49,15 +73,21 @@ def test_config_endpoint_handler_configuration_loaded():
4973
"authentication": {
5074
"module": "noop",
5175
},
76+
"authorization": {"access_rules": []},
5277
"customization": None,
5378
}
5479
cfg = AppConfig()
5580
cfg.init_from_dict(config_dict)
81+
82+
# Mock configuration
83+
mocker.patch("app.endpoints.config.configuration", cfg)
84+
5685
request = Request(
5786
scope={
5887
"type": "http",
5988
}
6089
)
61-
response = config_endpoint_handler(request)
90+
auth = ("test_user", "token", {})
91+
response = await config_endpoint_handler(auth=auth, _request=request)
6292
assert response is not None
6393
assert response == cfg.configuration

0 commit comments

Comments
 (0)