From ec41d1ed054190f7dd7649f32406a9333af63841 Mon Sep 17 00:00:00 2001 From: luxferre Date: Wed, 10 Jun 2026 16:16:56 +0100 Subject: [PATCH] feat: caor docs and response model --- src/auth/service.py | 2 +- src/iam/router.py | 41 +++++++++++++++++++++++++++++++++++------ src/iam/schemas.py | 7 +++++++ test/test_iam.py | 4 ++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/auth/service.py b/src/auth/service.py index a27d421..b9f436b 100644 --- a/src/auth/service.py +++ b/src/auth/service.py @@ -28,7 +28,7 @@ oidc_dependency = Annotated[str, Depends(oidc)] def get_dev_user(): - return {"db_id": 1} + return {"db_id": 1, "email": "chris@sr2.uk"} async def get_current_user( diff --git a/src/iam/router.py b/src/iam/router.py index 5a50f42..9cd0439 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -21,7 +21,7 @@ from sqlalchemy.exc import IntegrityError from src.iam.exceptions import GroupNotFoundException from src.organisation.exceptions import OrgNotFoundException -from src.schemas import GroupSummary, OrgSummary +from src.schemas import GroupSummary, OrgSummary, ResourceName from src.service.exceptions import ServiceNotFoundException from src.exceptions import ConflictException, ForbiddenException from src.database import db_dependency @@ -74,6 +74,7 @@ from src.iam.schemas import ( IAMGetPermissionsSearchResponse, IAMPutGroupInvitationRequest, IAMPutGroupInvitationAcceptRequest, + IAMCAoRResponse, ) from src.utils import verify_email_token @@ -83,13 +84,35 @@ router = APIRouter( ) -@router.post("/can_act_on_resource") +@router.post( + path="/can_act_on_resource", + summary="Used for services to check user access permission", + status_code=status.HTTP_200_OK, + response_model=IAMCAoRResponse, + responses={ + status.HTTP_401_UNAUTHORIZED: { + "description": "API Key missing or invalid | Issue verifying user OIDC claims" + }, + }, +) async def can_act_on_resource( valid_key: service_key_dependency, db: db_dependency, user_claims: claims_dependency, request_model: IAMCAoRRequest, -) -> bool: +): + """ + This endpoint is not meant for the Hub frontend to interact with. + Services accessing this endpoint must be already registered within the Hub and been issued an API key. + Resource Names have an instance property but permissions do not presently have that level of granularity. + """ + response = { + "allowed": False, + "rn": ResourceName(organisation="", service="", resource=""), + "action": "", + "user": {"id": 0, "email": ""}, + } + try: rn = request_model.rn action = request_model.action @@ -98,6 +121,10 @@ async def can_act_on_resource( rn_service = rn.service rn_resource = rn.resource + response["user"] = {"id": user_id, "email": user_claims["email"]} + response["action"] = action + response["rn"] = rn + result = ( db.query(Perm) .join(Service, Service.id == Perm.service_id) @@ -114,11 +141,13 @@ async def can_act_on_resource( ).first() if result: - return True + response["allowed"] = True else: - return False + response["allowed"] = False except Exception: - return False + response["allowed"] = False + + return response @router.get("/group/permissions", response_model=IAMGetGroupPermissionsResponse) diff --git a/src/iam/schemas.py b/src/iam/schemas.py index 66e3954..1cae77e 100644 --- a/src/iam/schemas.py +++ b/src/iam/schemas.py @@ -52,6 +52,13 @@ class IAMCAoRRequest(CustomBaseModel): rn: ResourceName +class IAMCAoRResponse(CustomBaseModel): + allowed: bool + user: UserSummary + action: str + rn: ResourceName + + class IAMGetGroupPermissionsResponse(CustomBaseModel): organisation: OrgSummary group: GroupSummary diff --git a/test/test_iam.py b/test/test_iam.py index 7f99de9..a8f46f6 100644 --- a/test/test_iam.py +++ b/test/test_iam.py @@ -36,7 +36,7 @@ async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient data = resp.json() assert resp.status_code == 200 - assert data is True + assert data["allowed"] is True @pytest.mark.parametrize( @@ -148,7 +148,7 @@ async def test_act_on_resource_logic( data = resp.json() assert resp.status_code == 200 - assert data == expected_response + assert data["allowed"] == expected_response @pytest.mark.anyio