feat: improved caor request model
All checks were successful
ci / lint_and_test (push) Successful in 14s

Issue: #23
This commit is contained in:
Chris Milne 2026-06-10 09:32:02 +01:00
parent 768a3881ef
commit 939abaefe9
4 changed files with 44 additions and 17 deletions

View file

@ -24,7 +24,6 @@ from src.organisation.exceptions import OrgNotFoundException
from src.service.exceptions import ServiceNotFoundException from src.service.exceptions import ServiceNotFoundException
from src.exceptions import ConflictException from src.exceptions import ConflictException
from src.database import db_dependency from src.database import db_dependency
from src.schemas import ResourceName
from src.auth.exceptions import UnauthorizedException from src.auth.exceptions import UnauthorizedException
from src.auth.service import claims_dependency from src.auth.service import claims_dependency
from src.auth.dependencies import ( from src.auth.dependencies import (
@ -55,10 +54,11 @@ from src.iam.dependencies import (
perm_model_query_dependency, perm_model_query_dependency,
) )
from src.iam.schemas import ( from src.iam.schemas import (
GroupSchema,
IAMCAoRRequest,
IAMGetGroupPermissionsResponse, IAMGetGroupPermissionsResponse,
IAMGetGroupUsersResponse, IAMGetGroupUsersResponse,
IAMPostGroupRequest, IAMPostGroupRequest,
GroupSchema,
IAMPostGroupResponse, IAMPostGroupResponse,
IAMPutGroupPermissionRequest, IAMPutGroupPermissionRequest,
IAMPutGroupPermissionResponse, IAMPutGroupPermissionResponse,
@ -87,10 +87,11 @@ async def can_act_on_resource(
valid_key: service_key_dependency, valid_key: service_key_dependency,
db: db_dependency, db: db_dependency,
user_claims: claims_dependency, user_claims: claims_dependency,
rn: ResourceName, request_model: IAMCAoRRequest,
action: str,
) -> bool: ) -> bool:
try: try:
rn = request_model.rn
action = request_model.action
user_id = user_claims["db_id"] user_id = user_claims["db_id"]
rn_org = rn.organisation rn_org = rn.organisation
rn_service = rn.service rn_service = rn.service

View file

@ -12,6 +12,7 @@ from pydantic import EmailStr, ConfigDict, Field
from src.schemas import ( from src.schemas import (
CustomBaseModel, CustomBaseModel,
ResourceName,
ServiceIDMixin, ServiceIDMixin,
OrgIDMixin, OrgIDMixin,
UserIDMixin, UserIDMixin,
@ -43,6 +44,11 @@ class GroupSchema(CustomBaseModel):
name: str name: str
class IAMCAoRRequest(CustomBaseModel):
action: str
rn: ResourceName
class IAMGetGroupPermissionsResponse(CustomBaseModel): class IAMGetGroupPermissionsResponse(CustomBaseModel):
permissions: list[PermissionSchema] permissions: list[PermissionSchema]

View file

@ -8,9 +8,9 @@ Exports:
from typing import Annotated from typing import Annotated
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from src.iam.schemas import IAMCAoRRequest
from src.service.models import Service from src.service.models import Service
from src.database import db_dependency from src.database import db_dependency
from src.schemas import ResourceName
from src.auth.exceptions import UnauthorizedException from src.auth.exceptions import UnauthorizedException
from src.utils import send_email, generate_jwt from src.utils import send_email, generate_jwt
@ -18,7 +18,10 @@ from src.utils import send_email, generate_jwt
from fastapi import Request, Depends from fastapi import Request, Depends
def valid_service_key(db: db_dependency, request: Request, rn: ResourceName) -> bool: def valid_service_key(
db: db_dependency, request: Request, request_model: IAMCAoRRequest
) -> bool:
rn = request_model.rn
api_key = request.headers.get("X-API-Key", None) api_key = request.headers.get("X-API-Key", None)
if not api_key: if not api_key:
raise UnauthorizedException("Missing API key") raise UnauthorizedException("Missing API key")

View file

@ -18,16 +18,20 @@ pytestmark = [
@pytest.mark.anyio @pytest.mark.anyio
async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient): async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient):
body = { body = {
"rn": {
"service": "Test Service", "service": "Test Service",
"organisation": "Test Org", "organisation": "Test Org",
"resource": "test_resource", "resource": "test_resource",
"instance": None,
},
"action": "read",
} }
headers = { headers = {
"Authorization": "Bearer not_checked_when_auth_is_disabled", "Authorization": "Bearer not_checked_when_auth_is_disabled",
"X-API-Key": "123456789", "X-API-Key": "123456789",
} }
resp = await default_client.post( resp = await default_client.post(
"/iam/can_act_on_resource?action=read", json=body, headers=headers "/iam/can_act_on_resource", json=body, headers=headers
) )
data = resp.json() data = resp.json()
@ -43,13 +47,20 @@ async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient
async def test_act_on_resource_wrong_key( async def test_act_on_resource_wrong_key(
default_client: AsyncClient, db_session, service: str, api_key: str default_client: AsyncClient, db_session, service: str, api_key: str
): ):
body = {"service": service, "organisation": "Test Org", "resource": "test_resource"} body = {
"rn": {
"service": service,
"organisation": "Test Org",
"resource": "test_resource",
},
"action": "read",
}
headers = { headers = {
"Authorization": "Bearer not_checked_when_auth_is_disabled", "Authorization": "Bearer not_checked_when_auth_is_disabled",
"X-API-Key": api_key, "X-API-Key": api_key,
} }
resp = await default_client.post( resp = await default_client.post(
"/iam/can_act_on_resource?action=read", json=body, headers=headers "/iam/can_act_on_resource", json=body, headers=headers
) )
data = resp.json() data = resp.json()
@ -60,9 +71,12 @@ async def test_act_on_resource_wrong_key(
@pytest.mark.anyio @pytest.mark.anyio
async def test_act_on_resource_missing_key(default_client: AsyncClient): async def test_act_on_resource_missing_key(default_client: AsyncClient):
body = { body = {
"rn": {
"service": "Test Service", "service": "Test Service",
"organisation": "Test Org", "organisation": "Test Org",
"resource": "test_resource", "resource": "test_resource",
},
"action": "read",
} }
headers = {"Authorization": "Bearer not_checked_when_auth_is_disabled"} headers = {"Authorization": "Bearer not_checked_when_auth_is_disabled"}
resp = await default_client.post( resp = await default_client.post(
@ -120,13 +134,16 @@ async def test_act_on_resource_logic(
action, action,
expected_response: bool, expected_response: bool,
): ):
body = {"service": service, "organisation": org, "resource": resource} body = {
"rn": {"service": service, "organisation": org, "resource": resource},
"action": action,
}
headers = { headers = {
"Authorization": "Bearer not_checked_when_auth_is_disabled", "Authorization": "Bearer not_checked_when_auth_is_disabled",
"X-API-Key": "123456789", "X-API-Key": "123456789",
} }
resp = await default_client.post( resp = await default_client.post(
f"/iam/can_act_on_resource?action={action}", json=body, headers=headers "/iam/can_act_on_resource", json=body, headers=headers
) )
data = resp.json() data = resp.json()