feat: iam dependencies

IAM endpoints now use dependencies to perform most initial database get requests.

Issue #6
This commit is contained in:
Chris Milne 2026-05-27 11:11:19 +01:00
parent d4f1b73deb
commit c6a2b301dc
4 changed files with 100 additions and 61 deletions

View file

@ -1,5 +1,5 @@
""" """
Router dependencies for <this module> Router dependencies for the IAM module
Classes: Classes:
- List: Description - List: Description
@ -8,4 +8,49 @@ Classes:
Functions: Functions:
- List: Description - List: Description
- Functions: Description - Functions: Description
""" """
from typing import Annotated, Optional
from fastapi import Depends, Query
from src.database import db_dependency
from src.iam.models import Group
from src.iam.exceptions import GroupNotFoundException, PermNotFoundException
from src.iam.schemas import GroupIDMixin, PermIDMixin
def get_group_model_query(db: db_dependency, group_id: Annotated[int, Query(gt=0)]) -> type[Group]:
group_model = db.get(Group, group_id)
if group_model is None:
raise GroupNotFoundException(group_id)
return group_model
group_model_query_dependency = Annotated[type[Group], Depends(get_group_model_query)]
def get_group_model_body(db: db_dependency, request_model: Optional[GroupIDMixin] = None) -> type[Group]:
group_id = getattr(request_model, "group_id", None)
if group_id is None:
raise GroupNotFoundException()
group_model = db.get(Group, group_id)
if group_model is None:
raise GroupNotFoundException(group_id)
return group_model
group_model_body_dependency = Annotated[type[Group], Depends(get_group_model_body)]
def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Group]:
perm_id = getattr(request_model, "permission_id", None)
if perm_id is None:
raise PermNotFoundException
group_model = db.get(Group, perm_id)
if group_model is None:
raise PermNotFoundException(perm_id)
return group_model
perm_model_body_dependency = Annotated[type[Group], Depends(get_perm_model_body)]

View file

@ -1,7 +1,28 @@
""" """
Module specific exceptions for <this module> Module specific exceptions for the IAM module
Exceptions: Exceptions:
- List: Description - List: Description
- Exceptions: Description - Exceptions: Description
""" """
from typing import Optional
from fastapi import HTTPException, status
class GroupNotFoundException(HTTPException):
def __init__(self, group_id: Optional[int] = None) -> None:
detail = "Group not found" if group_id is None else f"User with ID '{group_id}' was not found."
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=detail,
)
class PermNotFoundException(HTTPException):
def __init__(self, perm_id: Optional[int] = None) -> None:
detail = "Permission not found" if perm_id is None else f"User with ID '{perm_id}' was not found."
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=detail,
)

View file

@ -17,6 +17,7 @@ from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResp
IAMPostPermissionResponse, PermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse IAMPostPermissionResponse, PermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse
from src.schemas import ResourceName from src.schemas import ResourceName
from src.auth.service import claims_dependency from src.auth.service import claims_dependency
from src.user.exceptions import UserNotFoundException
from src.user.models import User from src.user.models import User
from src.organisation.models import Organisation as Org from src.organisation.models import Organisation as Org
from src.service.models import Service from src.service.models import Service
@ -24,6 +25,7 @@ from src.organisation.dependencies import org_model_dependency
from src.iam.service import service_key_dependency from src.iam.service import service_key_dependency
from src.iam.models import Permission as Perm, GroupPermissions as GPerms, Group, UserGroups from src.iam.models import Permission as Perm, GroupPermissions as GPerms, Group, UserGroups
from src.iam.dependencies import group_model_query_dependency, group_model_body_dependency, perm_model_body_dependency
router = APIRouter( router = APIRouter(
tags=["IAM"], tags=["IAM"],
@ -64,28 +66,21 @@ async def can_act_on_resource(valid_key: service_key_dependency, db: db_dependen
@router.get("/group/permissions", response_model=IAMGetGroupPermissionsResponse) @router.get("/group/permissions", response_model=IAMGetGroupPermissionsResponse)
async def get_group_permissions(db: db_dependency, group_id: Annotated[int, Query(gt=0)]): async def get_group_permissions(db: db_dependency, group_model: group_model_query_dependency):
# TODO: root_user_dependency & org_id query param # TODO: root_user_dependency
group_model = db.get(Group, group_id)
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
return {"permissions": group_model.permission_rel} return {"permissions": group_model.permission_rel}
@router.get("/group/users", response_model=IAMGetGroupUsersResponse) @router.get("/group/users", response_model=IAMGetGroupUsersResponse)
async def get_group_users(db: db_dependency, group_id: Annotated[int, Query(gt=0)]): async def get_group_users(db: db_dependency, group_model: group_model_query_dependency):
# TODO: root_user_dependency & org_id query param # TODO: root_user_dependency
group_model = db.get(Group, group_id)
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
return {"users": group_model.user_rel} return {"users": group_model.user_rel}
@router.post("/group", response_model=IAMPostGroupResponse) @router.post("/group", response_model=IAMPostGroupResponse)
async def create_group(db: db_dependency, group_request: IAMPostGroupRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def create_group(db: db_dependency, group_request: IAMPostGroupRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]):
# TODO: root_user_dependency # TODO: root_user_dependency
# TODO: get org ID from dependency instead of query (needs updated dep first)
group_model = Group(name=group_request.name, org_id=org_id) group_model = Group(name=group_request.name, org_id=org_id)
db.add(group_model) db.add(group_model)
@ -96,15 +91,8 @@ async def create_group(db: db_dependency, group_request: IAMPostGroupRequest, or
@router.put("/group/permission", response_model=IAMPutGroupPermissionResponse) @router.put("/group/permission", response_model=IAMPutGroupPermissionResponse)
async def add_group_permission(db: db_dependency, request_model: IAMPutGroupPermissionRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def add_group_permission(db: db_dependency, group_model: group_model_body_dependency, perm_model: perm_model_body_dependency, request_model: IAMPutGroupPermissionRequest):
# TODO: root_user_dependency # TODO: root_user_dependency
group_model = db.get(Group, request_model.group_id)
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
perm_model = db.get(Perm, request_model.permission_id)
if perm_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Permission not found")
group_model.permission_rel.append(perm_model) group_model.permission_rel.append(perm_model)
db.flush() db.flush()
@ -114,14 +102,12 @@ async def add_group_permission(db: db_dependency, request_model: IAMPutGroupPerm
@router.put("/group/user") @router.put("/group/user")
async def add_group_user(db: db_dependency, request_model: IAMPutGroupUserRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def add_group_user(db: db_dependency, group_model: group_model_body_dependency, request_model: IAMPutGroupUserRequest):
# TODO: root_user_dependency # TODO: root_user_dependency
group_model = db.get(Group, request_model.group_id) # TODO: user_model_dependency
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
user_model = db.get(User, request_model.user_id) user_model = db.get(User, request_model.user_id)
if user_model is None: if user_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") raise UserNotFoundException(user_id=request_model.user_id)
group_model.user_rel.append(user_model) group_model.user_rel.append(user_model)
db.flush() db.flush()
@ -131,15 +117,8 @@ async def add_group_user(db: db_dependency, request_model: IAMPutGroupUserReques
@router.delete("/group/permissions") @router.delete("/group/permissions")
async def remove_group_permissions(db: db_dependency, request_model: IAMDeleteGroupPermissionRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def remove_group_permissions(db: db_dependency, group_model: group_model_body_dependency, perm_model: perm_model_body_dependency, request_model: IAMDeleteGroupPermissionRequest):
# TODO: root_user_dependency # TODO: root_user_dependency
group_model = db.get(Group, request_model.group_id)
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
perm_model = db.get(Perm, request_model.permission_id)
if perm_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Permission not found")
group_model.permission_rel.remove(perm_model) group_model.permission_rel.remove(perm_model)
db.flush() db.flush()
response = IAMDeleteGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), response = IAMDeleteGroupPermissionResponse(group=GroupResponse(**group_model.__dict__),
@ -149,11 +128,9 @@ async def remove_group_permissions(db: db_dependency, request_model: IAMDeleteGr
@router.delete("/group/user") @router.delete("/group/user")
async def remove_group_user(db: db_dependency, request_model: IAMDeleteGroupUserRequest, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def remove_group_user(db: db_dependency, group_model: group_model_body_dependency, request_model: IAMDeleteGroupUserRequest):
# TODO: root_user_dependency # TODO: root_user_dependency
group_model = db.get(Group, request_model.group_id) # TODO: User model dependency
if group_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
user_model = db.get(User, request_model.user_id) user_model = db.get(User, request_model.user_id)
if user_model is None: if user_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
@ -167,7 +144,7 @@ async def remove_group_user(db: db_dependency, request_model: IAMDeleteGroupUser
@router.get("/permissions", response_model=IAMGetPermissionsResponse) @router.get("/permissions", response_model=IAMGetPermissionsResponse)
async def get_permissions(db: db_dependency, org_model: org_model_dependency, org_id: Annotated[int, Query(gt=0)]): async def get_permissions(db: db_dependency):
# TODO: root_user_dependency # TODO: root_user_dependency
permission_models = db.query(Perm).all() permission_models = db.query(Perm).all()
@ -187,12 +164,8 @@ async def create_new_permission(db: db_dependency, request_mode: IAMPostPermissi
@router.delete("/permission", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/permission", status_code=status.HTTP_204_NO_CONTENT)
async def delete_permission(db: db_dependency, request_model: IAMDeletePermissionRequest): async def delete_permission(db: db_dependency, perm_model: perm_model_body_dependency, request_model: IAMDeletePermissionRequest):
# TODO: super_admin_dependency # TODO: super_admin_dependency
perm_model = db.query(Perm).filter(Perm.service_id==request_model.service_id, Perm.resource==request_model.resource, Perm.action==request_model.action).first()
if perm_model is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Permission not found")
db.delete(perm_model) db.delete(perm_model)
db.commit() db.commit()

View file

@ -30,6 +30,12 @@ class GroupResponse(CustomBaseModel):
id: int id: int
name: str name: str
class GroupIDMixin(CustomBaseModel):
group_id: int
class PermIDMixin(CustomBaseModel):
permission_id: int
class IAMGetGroupPermissionsResponse(CustomBaseModel): class IAMGetGroupPermissionsResponse(CustomBaseModel):
permissions: list[PermissionResponse] permissions: list[PermissionResponse]
@ -42,32 +48,28 @@ class IAMPostGroupRequest(CustomBaseModel):
class IAMPostGroupResponse(CustomBaseModel): class IAMPostGroupResponse(CustomBaseModel):
group: GroupResponse group: GroupResponse
class IAMPutGroupPermissionRequest(CustomBaseModel): class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin):
group_id: int pass
permission_id: int
class IAMPutGroupPermissionResponse(CustomBaseModel): class IAMPutGroupPermissionResponse(CustomBaseModel):
group: GroupResponse group: GroupResponse
permissions: list[PermissionResponse] permissions: list[PermissionResponse]
class IAMPutGroupUserRequest(CustomBaseModel): class IAMPutGroupUserRequest(GroupIDMixin):
group_id: int
user_id: int user_id: int
class IAMPutGroupUserResponse(CustomBaseModel): class IAMPutGroupUserResponse(CustomBaseModel):
group: GroupResponse group: GroupResponse
users: list[UserResponse] users: list[UserResponse]
class IAMDeleteGroupPermissionRequest(CustomBaseModel): class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin):
group_id: int pass
permission_id: int
class IAMDeleteGroupPermissionResponse(CustomBaseModel): class IAMDeleteGroupPermissionResponse(CustomBaseModel):
group: GroupResponse group: GroupResponse
permissions: list[PermissionResponse] permissions: list[PermissionResponse]
class IAMDeleteGroupUserRequest(CustomBaseModel): class IAMDeleteGroupUserRequest(GroupIDMixin):
group_id: int
user_id: int user_id: int
class IAMDeleteGroupUserResponse(CustomBaseModel): class IAMDeleteGroupUserResponse(CustomBaseModel):
@ -85,10 +87,8 @@ class IAMPostPermissionRequest(CustomBaseModel):
class IAMPostPermissionResponse(CustomBaseModel): class IAMPostPermissionResponse(CustomBaseModel):
permission: PermissionResponse permission: PermissionResponse
class IAMDeletePermissionRequest(CustomBaseModel): class IAMDeletePermissionRequest(PermIDMixin):
service_id: int pass
resource: str
action: str
class IAMGetPermissionsSearchRequest(CustomBaseModel): class IAMGetPermissionsSearchRequest(CustomBaseModel):
service_id: Optional[int] = None service_id: Optional[int] = None