diff --git a/example.env b/example.env new file mode 100644 index 0000000..b2ab4c9 --- /dev/null +++ b/example.env @@ -0,0 +1,10 @@ +SECRET_KEY="" +OIDC_CONFIG="https://sso.sr2.uk/realms/sr2/.well-known/openid-configuration" +OIDC_ISSUER="https://sso.sr2.uk/realms/sr2" +OIDC_AUDIENCE="account" +CLIENT_ID="" + +DATABASE_NAME="cloud-api" +DATABASE_PORT="5432" +DATABASE_HOSTNAME="localhost" +DATABASE_CREDENTIALS="user:password" diff --git a/src/admin/config.py b/src/admin/config.py index 3139962..46e4142 100644 --- a/src/admin/config.py +++ b/src/admin/config.py @@ -1,7 +1,3 @@ """ Configurations for the admin module - -Configurations: - - List: Description - - Configs: Description """ \ No newline at end of file diff --git a/src/admin/constants.py b/src/admin/constants.py index 58a3b2a..c75163f 100644 --- a/src/admin/constants.py +++ b/src/admin/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for the admin module - -Constants: - - List: Description - - Consts: Description +Constants for the admin module """ \ No newline at end of file diff --git a/src/admin/dependencies.py b/src/admin/dependencies.py index cb4c147..aff00b3 100644 --- a/src/admin/dependencies.py +++ b/src/admin/dependencies.py @@ -1,11 +1,3 @@ """ -Router dependencies for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Dependencies for the admin module """ \ No newline at end of file diff --git a/src/admin/exceptions.py b/src/admin/exceptions.py index 644a8b0..513805c 100644 --- a/src/admin/exceptions.py +++ b/src/admin/exceptions.py @@ -1,7 +1,3 @@ """ -Module specific exceptions for the admin module - -Exceptions: - - List: Description - - Exceptions: Description +Custom exceptions for the admin module """ \ No newline at end of file diff --git a/src/admin/models.py b/src/admin/models.py index 1b60920..304e336 100644 --- a/src/admin/models.py +++ b/src/admin/models.py @@ -1,7 +1,3 @@ """ Database models for the admin module - -Models: - - List: Description - - Models: Description """ \ No newline at end of file diff --git a/src/admin/router.py b/src/admin/router.py index 13ac897..e0246a4 100644 --- a/src/admin/router.py +++ b/src/admin/router.py @@ -1,9 +1,8 @@ """ Router endpoints for the admin module -Endpoints: - - List: Description - - Endpoints: Description +Exports: + - router: fastapi.APIRouter """ from fastapi import APIRouter diff --git a/src/admin/schemas.py b/src/admin/schemas.py index 4490920..1289bcb 100644 --- a/src/admin/schemas.py +++ b/src/admin/schemas.py @@ -1,7 +1,3 @@ """ Pydantic models for the admin module - -Models: - - List: Description - - Models: Description """ \ No newline at end of file diff --git a/src/admin/service.py b/src/admin/service.py index 1cd5069..1db3599 100644 --- a/src/admin/service.py +++ b/src/admin/service.py @@ -1,11 +1,3 @@ """ Module specific business logic for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description """ \ No newline at end of file diff --git a/src/admin/utils.py b/src/admin/utils.py index 4e316d1..e570f14 100644 --- a/src/admin/utils.py +++ b/src/admin/utils.py @@ -1,11 +1,3 @@ """ Non-business logic reusable functions and classes for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description """ \ No newline at end of file diff --git a/src/auth/config.py b/src/auth/config.py index 37de6a7..82646d2 100644 --- a/src/auth/config.py +++ b/src/auth/config.py @@ -1,9 +1,8 @@ """ -Configurations for auth module, import auth_settings +Configurations for the auth module -Configurations: - - List: Description - - Configs: Description +Exports: + - auth_settings: Contains OIDC information """ from src.config import CustomBaseSettings diff --git a/src/auth/constants.py b/src/auth/constants.py index 1ab3fd6..faabd82 100644 --- a/src/auth/constants.py +++ b/src/auth/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for auth module - -Constants: - - List: Description - - Consts: Description +Constants for the auth module """ \ No newline at end of file diff --git a/src/auth/dependencies.py b/src/auth/dependencies.py index e85ef9c..f87064a 100644 --- a/src/auth/dependencies.py +++ b/src/auth/dependencies.py @@ -1,18 +1,17 @@ """ -Router dependencies for auth module +Auth dependencies -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - org_query_user_claims_dependency: bool: Verifies user belongs to org + - org_model_root_claim_query_dependency: org_model: verifies org exists and user is either root or su, gets org from query + - org_model_root_claim_body_dependency: org_model: verifies org exists and user is either root or su, gets org from body + - super_admin_dependency: user_model: verifies the user is a super admin """ from typing import Annotated from fastapi import Depends from src.user.dependencies import user_model_claims_dependency +from src.user.models import User from src.organisation.dependencies import org_model_query_dependency, org_model_body_dependency from src.organisation.models import Organisation as Org @@ -69,4 +68,4 @@ async def user_model_super_admin(user_model: user_model_claims_dependency): raise UnauthorizedException() -super_admin_dependency = Annotated[bool, Depends(user_model_super_admin)] +super_admin_dependency = Annotated[type[User], Depends(user_model_super_admin)] diff --git a/src/auth/exceptions.py b/src/auth/exceptions.py index 71aede1..613b166 100644 --- a/src/auth/exceptions.py +++ b/src/auth/exceptions.py @@ -1,9 +1,8 @@ """ -Module specific exceptions for auth module +Module specific exceptions for the auth module Exceptions: - - List: Description - - Exceptions: Description + - UnauthorizedException: Takes an optional message string """ from typing import Optional diff --git a/src/auth/models.py b/src/auth/models.py index 487b3d1..4717477 100644 --- a/src/auth/models.py +++ b/src/auth/models.py @@ -1,7 +1,3 @@ """ -Database models for auth module - -Models: - - List: Description - - Models: Description +Database models for the auth module """ \ No newline at end of file diff --git a/src/auth/router.py b/src/auth/router.py index 5e8871d..9cd7fad 100644 --- a/src/auth/router.py +++ b/src/auth/router.py @@ -1,8 +1,8 @@ """ -Router endpoints for auth module -Contains oauth registration +Router endpoints for the auth module -Endpoints: +Exports: + - router: fastapi.APIRouter """ from fastapi import APIRouter diff --git a/src/auth/schemas.py b/src/auth/schemas.py index 92afae5..279bb1b 100644 --- a/src/auth/schemas.py +++ b/src/auth/schemas.py @@ -1,7 +1,3 @@ """ -Pydantic models for auth module - -Models: - - List: Description - - Models: Description +Pydantic models for the auth module """ \ No newline at end of file diff --git a/src/auth/service.py b/src/auth/service.py index e0a764e..dce8217 100644 --- a/src/auth/service.py +++ b/src/auth/service.py @@ -1,8 +1,8 @@ """ -Module specific business logic for auth module +Module specific business logic for the auth module Exports: - - claims_dependency + - claims_dependency: Dict[str, Any] containing OIDC claims and database ID """ import json import requests diff --git a/src/auth/utils.py b/src/auth/utils.py index e913437..ed66e7c 100644 --- a/src/auth/utils.py +++ b/src/auth/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for auth module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the auth module """ \ No newline at end of file diff --git a/src/contact/config.py b/src/contact/config.py index 5e7f864..2253a68 100644 --- a/src/contact/config.py +++ b/src/contact/config.py @@ -1,7 +1,3 @@ """ -Configurations for contact module - -Configurations: - - List: Description - - Configs: Description +Configurations for the contact module """ \ No newline at end of file diff --git a/src/contact/constants.py b/src/contact/constants.py index 49898d6..41f6ded 100644 --- a/src/contact/constants.py +++ b/src/contact/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for contact module - -Constants: - - List: Description - - Consts: Description +Constants for the contact module """ \ No newline at end of file diff --git a/src/contact/dependencies.py b/src/contact/dependencies.py index 2450730..de1d404 100644 --- a/src/contact/dependencies.py +++ b/src/contact/dependencies.py @@ -1,11 +1,3 @@ """ -Router dependencies for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Dependencies for the contact module """ \ No newline at end of file diff --git a/src/contact/exceptions.py b/src/contact/exceptions.py index b3f8e11..6710bf3 100644 --- a/src/contact/exceptions.py +++ b/src/contact/exceptions.py @@ -1,9 +1,8 @@ """ -Module specific exceptions for contact module +Exceptions related to the contact module -Exceptions: - - List: Description - - Exceptions: Description +Exports: + - ContactNotFoundException: Takes an optional contact ID int """ from typing import Optional diff --git a/src/contact/models.py b/src/contact/models.py index e3d0d05..3369501 100644 --- a/src/contact/models.py +++ b/src/contact/models.py @@ -1,9 +1,9 @@ """ -Database models for contact module +Database models for the contact module Models: - Contact: id[pk], email, first_name, last_name, phonenumber, vat_number - street_address, post_office_box_number, address_locality, country_code, address_region, postal_code + street_address, street_address_line_2, post_office_box_number, address_locality, country_code, address_region, postal_code """ from sqlalchemy import Column, Integer, String, ForeignKey diff --git a/src/contact/router.py b/src/contact/router.py index 9528fd9..cdab37f 100644 --- a/src/contact/router.py +++ b/src/contact/router.py @@ -1,13 +1,5 @@ """ -Router endpoints for contact module - -Endpoints: - - [get]/{contact_id} - Returns non-address type details for contact - - [get]/{contact_id}/address - Returns address details for contact - - [get]/{contact_id}/orgs - Returns a list of orgs which the contact is assigned to, and what they are assigned as - - [post]/ - Creates a new contact - - [patch]/{contact_id} - Updates the details of an existing contact - - [delete]/{contact_id} - Deletes a contact by ID +Router endpoints for the contact module """ from fastapi import APIRouter diff --git a/src/contact/schemas.py b/src/contact/schemas.py index b5103f0..b008739 100644 --- a/src/contact/schemas.py +++ b/src/contact/schemas.py @@ -1,9 +1,9 @@ """ -Pydantic models for contact module +Pydantic models for the contact module Models: - - List: Description - - Models: Description + - ContactAddress + - ContactModel: Contains ContactAddress as a property """ from typing import Optional diff --git a/src/contact/service.py b/src/contact/service.py index ee4af6c..e04866a 100644 --- a/src/contact/service.py +++ b/src/contact/service.py @@ -1,11 +1,3 @@ """ -Module specific business logic for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Module specific business logic for the contact module """ \ No newline at end of file diff --git a/src/contact/utils.py b/src/contact/utils.py index 74ec5c5..6a1d14a 100644 --- a/src/contact/utils.py +++ b/src/contact/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the contact module """ \ No newline at end of file diff --git a/src/iam/config.py b/src/iam/config.py index 4be170e..165dc07 100644 --- a/src/iam/config.py +++ b/src/iam/config.py @@ -1,7 +1,3 @@ """ -Configurations for - -Configurations: - - List: Description - - Configs: Description +Configurations for the IAM module """ \ No newline at end of file diff --git a/src/iam/constants.py b/src/iam/constants.py index e1df957..0dc94e7 100644 --- a/src/iam/constants.py +++ b/src/iam/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for - -Constants: - - List: Description - - Consts: Description +Constants for the IAM module """ \ No newline at end of file diff --git a/src/iam/dependencies.py b/src/iam/dependencies.py index d5632c7..37b8e87 100644 --- a/src/iam/dependencies.py +++ b/src/iam/dependencies.py @@ -1,13 +1,10 @@ """ -Router dependencies for the IAM module +Dependencies for the IAM module -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - group_model_query_dependency: group_model: Gets group model from db, if it exists. Uses group_id from query param. + - group_model_body_dependency: group_model: Gets group model from db, if it exists. Uses group_id from request body. + - perm_model_body_dependency: perm_model: Gets perm model from db, if it exists. Uses perm_id from request body. """ from typing import Annotated, Optional @@ -15,7 +12,7 @@ from fastapi import Depends, Query from src.database import db_dependency -from src.iam.models import Group +from src.iam.models import Group, Permission from src.iam.exceptions import GroupNotFoundException, PermNotFoundException from src.iam.schemas import GroupIDMixin, PermIDMixin @@ -43,14 +40,14 @@ def get_group_model_body(db: db_dependency, request_model: Optional[GroupIDMixin 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]: +def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Permission]: 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: + perm_model = db.get(Permission, perm_id) + if perm_model is None: raise PermNotFoundException(perm_id) - return group_model + return perm_model -perm_model_body_dependency = Annotated[type[Group], Depends(get_perm_model_body)] +perm_model_body_dependency = Annotated[type[Permission], Depends(get_perm_model_body)] diff --git a/src/iam/exceptions.py b/src/iam/exceptions.py index 5c97b9a..84a77ed 100644 --- a/src/iam/exceptions.py +++ b/src/iam/exceptions.py @@ -1,9 +1,9 @@ """ -Module specific exceptions for the IAM module +Exceptions related to the IAM module Exceptions: - - List: Description - - Exceptions: Description + - GroupNotFoundException: Takes an optional group_id int + - PermNotFoundException: Takes an optional perm_id int """ from typing import Optional diff --git a/src/iam/models.py b/src/iam/models.py index 83d1ae2..ea12c0f 100644 --- a/src/iam/models.py +++ b/src/iam/models.py @@ -2,8 +2,11 @@ Database models for the IAM module Models: - - List: Description - - Models: Description + - Permission: + - id[PK], resource[U1], action[U1], service_id[FK][U1] + - service_rel: ORM relationship over service_id FK + - group_rel: ORM relationship backpops to Group.permission_rel + - service_name: Calc property service_rel.name """ from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship diff --git a/src/iam/router.py b/src/iam/router.py index 4b2bbb4..a6398a9 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -1,9 +1,19 @@ """ -Router endpoints for +Router endpoints for IAM Endpoints: - - List: Description - - Endpoints: Description + - [POST](/iam/can_act_on_resource): [API key & user claim]: Service access point to verify user permissions + - [GET](/iam/group/permissions): [root user]: Gets list of perms(service, resource, action) the given group(id) has + - [DELETE](/iam/group/permissions): [root user]: Removes a given perm(id) from the given group(id) + - [GET](/iam/group/users): [root user]: Gets a list of users(id, name, email) that are assigned to the given group(id) + - [POST](/iam/group): [root user]: Creates a new group for the given org(id) + - [PUT](/iam/group/permission): [root user]: Assigns a perm(id) to the given group(id) + - [PUT](/iam/group/user): [root user]: Assigns a user(id) to a group(id) + - [DELETE](/iam/group/user): [root user]: Removes a user(id) from the given group(id) + - [GET](/iam/permissions): [root user]: Gets a list of all permissions + - [POST](/iam/permission): [super admin]: Creates a new permission + - [DELETE](/iam/permission): [super admin]: Removes a permission + - [GET](/iam/permissions/search): [root user]: Returns a list of permissions matching a filter(service|resource|action) """ from fastapi import APIRouter, status from sqlalchemy.exc import IntegrityError @@ -25,10 +35,10 @@ from src.iam.service import service_key_dependency 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 from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResponse, IAMPostGroupRequest, \ - GroupResponse, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ + GroupSchema, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ IAMPutGroupUserRequest, IAMPutGroupUserResponse, IAMDeleteGroupPermissionRequest, IAMDeleteGroupPermissionResponse, \ IAMDeleteGroupUserRequest, IAMDeleteGroupUserResponse, IAMGetPermissionsResponse, IAMPostPermissionRequest, \ - IAMPostPermissionResponse, PermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse + IAMPostPermissionResponse, PermissionSchema, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse router = APIRouter( tags=["IAM"], @@ -91,7 +101,7 @@ async def create_group(db: db_dependency, org_model: org_model_root_claim_body_d except IntegrityError as e: if isinstance(e.orig, errors.UniqueViolation): raise Conflict("Group with this name already exists") - response = GroupResponse(**group_model.__dict__) + response = GroupSchema(**group_model.__dict__) db.commit() return {"group": response} @@ -107,7 +117,7 @@ async def add_group_permission(db: db_dependency, group_model: group_model_body_ group_model.permission_rel.append(perm_model) db.flush() - response = IAMPutGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), permissions=group_model.permission_rel) + response = IAMPutGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), permissions=group_model.permission_rel) db.commit() return response @@ -122,7 +132,7 @@ async def add_group_user(db: db_dependency, group_model: group_model_body_depend group_model.user_rel.append(user_model) db.flush() - response = IAMPutGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel) + response = IAMPutGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) db.commit() return response @@ -134,8 +144,8 @@ async def remove_group_permissions(db: db_dependency, group_model: group_model_b group_model.permission_rel.remove(perm_model) db.flush() - response = IAMDeleteGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), - permissions=group_model.permission_rel) + response = IAMDeleteGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), + permissions=group_model.permission_rel) db.commit() return response @@ -147,7 +157,7 @@ async def remove_group_user(db: db_dependency, group_model: group_model_body_dep user_model.group_rel.remove(group_model) db.flush() - response = IAMDeleteGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel) + response = IAMDeleteGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) db.commit() return response @@ -169,7 +179,7 @@ async def create_new_permission(db: db_dependency, su: super_admin_dependency, r if isinstance(e.orig, errors.UniqueViolation): raise Conflict(message="Permission already exists") db.flush() - response = IAMPostPermissionResponse(permission=PermissionResponse(**perm_model.__dict__)) + response = IAMPostPermissionResponse(permission=PermissionSchema(**perm_model.__dict__)) db.commit() return response diff --git a/src/iam/schemas.py b/src/iam/schemas.py index f6cd7bb..3f34390 100644 --- a/src/iam/schemas.py +++ b/src/iam/schemas.py @@ -1,9 +1,10 @@ """ Pydantic models for the IAM module -Models: - - List: Description - - Models: Description +Models follow the nomenclature of: +- Sub-models: "Schema" +- Mixins: "Mixin" +- Models: "" ie "IAMGetGroupPermissionsResponse" """ from typing import Optional @@ -14,20 +15,20 @@ from src.schemas import CustomBaseModel from user.schemas import UserIDMixin -class UserResponse(CustomBaseModel): +class UserSchema(CustomBaseModel): id: int first_name: str last_name: str email: EmailStr -class PermissionResponse(CustomBaseModel): +class PermissionSchema(CustomBaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") service_name: str resource: str action: str -class GroupResponse(CustomBaseModel): +class GroupSchema(CustomBaseModel): id: int name: str @@ -38,47 +39,47 @@ class PermIDMixin(CustomBaseModel): permission_id: int class IAMGetGroupPermissionsResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] class IAMGetGroupUsersResponse(CustomBaseModel): - users : list[UserResponse] + users : list[UserSchema] class IAMPostGroupRequest(OrgIDMixin): name: str class IAMPostGroupResponse(CustomBaseModel): - group: GroupResponse + group: GroupSchema class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin): pass class IAMPutGroupPermissionResponse(CustomBaseModel): - group: GroupResponse - permissions: list[PermissionResponse] + group: GroupSchema + permissions: list[PermissionSchema] class IAMPutGroupUserRequest(GroupIDMixin, UserIDMixin): pass class IAMPutGroupUserResponse(CustomBaseModel): - group: GroupResponse - users: list[UserResponse] + group: GroupSchema + users: list[UserSchema] class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin): pass class IAMDeleteGroupPermissionResponse(CustomBaseModel): - group: GroupResponse - permissions: list[PermissionResponse] + group: GroupSchema + permissions: list[PermissionSchema] class IAMDeleteGroupUserRequest(GroupIDMixin, UserIDMixin): pass class IAMDeleteGroupUserResponse(CustomBaseModel): - group: GroupResponse - users: list[UserResponse] + group: GroupSchema + users: list[UserSchema] class IAMGetPermissionsResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] class IAMPostPermissionRequest(CustomBaseModel): service_id: int @@ -86,7 +87,7 @@ class IAMPostPermissionRequest(CustomBaseModel): action: str class IAMPostPermissionResponse(CustomBaseModel): - permission: PermissionResponse + permission: PermissionSchema class IAMDeletePermissionRequest(PermIDMixin): pass @@ -97,4 +98,4 @@ class IAMGetPermissionsSearchRequest(CustomBaseModel): action: Optional[str] = None class IAMGetPermissionsSearchResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] diff --git a/src/iam/service.py b/src/iam/service.py index 1607cd0..b1d416b 100644 --- a/src/iam/service.py +++ b/src/iam/service.py @@ -1,7 +1,8 @@ """ -Module specific business logic for +Business logic reusable functions related to IAM -Exports service_key_dependency +Exports: + - service_key_dependency: bool: verifies request headers contain the correct api key for the service """ from typing import Annotated diff --git a/src/iam/utils.py b/src/iam/utils.py index 5afbb54..d948f09 100644 --- a/src/iam/utils.py +++ b/src/iam/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the IAM module """ diff --git a/src/organisation/config.py b/src/organisation/config.py index 51f5dd9..e24ca5b 100644 --- a/src/organisation/config.py +++ b/src/organisation/config.py @@ -1,7 +1,3 @@ """ -Configurations for organisation module - -Configurations: - - List: Description - - Configs: Description +Configurations for the organisation module """ \ No newline at end of file diff --git a/src/organisation/constants.py b/src/organisation/constants.py index 6cae1fd..ced0682 100644 --- a/src/organisation/constants.py +++ b/src/organisation/constants.py @@ -1,5 +1,5 @@ """ -Constants and error codes for organisation module +Constants for the organisation module Classes: - Status(StrEnum): PARTIAL, SUBMITTED, REMEDIATION, APPROVED, REJECTED, REMOVED diff --git a/src/organisation/dependencies.py b/src/organisation/dependencies.py index 3edd8be..51ee569 100644 --- a/src/organisation/dependencies.py +++ b/src/organisation/dependencies.py @@ -1,13 +1,9 @@ """ -Router dependencies for organisation module +Dependencies related to the organisation module -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - org_model_query_dependency: org_model: Gets org model from db, if it exists. Uses org_id from query param. Also verifies if the org has been approved. + - org_model_body_dependency: org_model: Gets org model from db, if it exists. Uses org_id from request body. Also verifies if the org has been approved. """ from typing import Annotated, Optional @@ -26,7 +22,7 @@ def get_org_model(db, request: Request, org_id: int): if org_model is None: raise OrgNotFoundException(org_id) - pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id"] + pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id", "GET/org/contact", "PATCH/org/contact"] current_request = f"{request.method}{request.url.path}" if current_request not in pre_approval_endpoints and org_model.status != OrgStatus.APPROVED: raise AwaitingApprovalException(org_id) diff --git a/src/organisation/exceptions.py b/src/organisation/exceptions.py index c7d6a0c..8fe61cc 100644 --- a/src/organisation/exceptions.py +++ b/src/organisation/exceptions.py @@ -1,9 +1,9 @@ """ -Module specific exceptions for organisation module +Exceptions related to the organisation module Exceptions: - - List: Description - - Exceptions: Description + - OrgNotFoundException: Takes an optional org_id int + - AwaitingApprovalException: Takes an optional org_id int """ from typing import Optional diff --git a/src/organisation/models.py b/src/organisation/models.py index f696824..3663f2c 100644 --- a/src/organisation/models.py +++ b/src/organisation/models.py @@ -2,9 +2,16 @@ Database models for organisation module Models: - - Organisation: id[pk], name, status, intake_questionnaire, - billing_contact_id[fk], security_contact_id[fk], owner_contact_id[fk] - - OrgUsers: org_id[fk][cpk], user_id[fk][cpk], is_admin + - Organisation: + - id[PK], name, status, intake_questionnaire, root_user_id[FK], billing_contact_id[FK], security_contact_id[FK], owner_contact_id[FK] + - user_rel: ORM relationship to User via OrgUsers relationship table + - group_rel: ORM relationship to Group, backprops Group.org_rel + - root_user_rel: ORM relationship to User with root_user_id FK + - root_user_email: Calc property root_user_rel.email + - billing_contact_rel: ORM relationship to Contact with billing_contact FK + - security_contact_rel: ORM relationship to Contact with security_contact FK + - owner_contact_rel: ORM relationship to Contact with owner_contact FK + - OrgUsers: org_id[FK][PK], user_id[FK][PK] """ from sqlalchemy import Column, Integer, String, ForeignKey, JSON from sqlalchemy.orm import relationship diff --git a/src/organisation/router.py b/src/organisation/router.py index c26bc2f..2e71fba 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -2,15 +2,18 @@ Router endpoints for organisation module Endpoints: - - [get]/id/{org_id} - Retrieves an organisation by its ID - - [post]/ - Creates a new organisation - - [patch]/{org_id}/questionnaire - Updates the questionnaire data for an organisation (can be partial or final submission) - - [patch]/{org_id}/status - Updates the status of an organisation - - [patch]/{org_id}/contact - Assigns a contact to an organisation (as billing, security, or owner) - - [get]/{org_id}/users - Retrieves all users associated with an organisation - - [post]/{org_id}/users - Adds a new user to an organisation - - [delete]/{org_id} - Deletes an organisation by ID - - [get]/{org_id}/contact/{contact_type} - Retrieves the contact of a specific type (owner, billing, security) for an organisation + - [GET](/org/id): [root user]: Get details about an organisation(id) + - [POST](/org/): [oidc claim]: Creates an organisation, adds the current user as a user and sets them to be the root user + - [PATCH](/org/questionnaire): [root user]: Updates the org's intake questionnaire and optionally be submitted for review + - [PATCH](/org/status): [super admin]: Allows a super admin to update an org(id) status(Status enum) + - [GET](/org/users): [root user]: Gets a list of the org(id) users(email) + - [POST](/org/users): [root user]: Adds a new user(id) to the org(id) + - [DELETE](/org/): [super admin]: Deletes an organisation(id) + - [PATCH](/org/root_user): [super admin]: Updates an org(id) root user(id) + - [GET](/org/groups): [root user]: Gets a list of the org(id) groups(name) + - [DELETE](/org/user): [root user]: Removes a user(id) from an org(id) + - [GET](/org/contact): [root user]: Gets the (contact_type) contact for an org(id) + - [PATCH](/org/contact): [root user]: Updates the (contact_type) contact for an org(id). Any number of details can be changed. """ from typing import Annotated, Optional diff --git a/src/organisation/schemas.py b/src/organisation/schemas.py index d727b98..0fb13c5 100644 --- a/src/organisation/schemas.py +++ b/src/organisation/schemas.py @@ -1,9 +1,10 @@ """ Pydantic models for organisation module -Models: - - List: Description - - Models: Description +Models follow the nomenclature of: +- Sub-models: "Schema" +- Mixins: "Mixin" +- Models: "" ie "OrgPostOrgRequest" """ from typing import Optional @@ -16,7 +17,7 @@ from src.user.schemas import UserIDMixin from src.organisation.constants import Status, ContactType -class OrgQuestionnaire(CustomBaseModel): +class Questionnaire(CustomBaseModel): question_one: str question_two: str question_three: str @@ -27,10 +28,10 @@ class OrgIDMixin(CustomBaseModel): class OrgPostOrgRequest(CustomBaseModel): name: str - intake_questionnaire: Optional[OrgQuestionnaire] = None + intake_questionnaire: Optional[Questionnaire] = None class OrgPatchQuestionnaireRequest(OrgIDMixin): - intake_questionnaire: OrgQuestionnaire + intake_questionnaire: Questionnaire partial: bool class OrgPatchStatusRequest(OrgIDMixin): diff --git a/src/organisation/service.py b/src/organisation/service.py index b401681..6d73399 100644 --- a/src/organisation/service.py +++ b/src/organisation/service.py @@ -1,11 +1,3 @@ """ -Module specific business logic for organisation module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Reusable business logic functions for the organisation module """ \ No newline at end of file diff --git a/src/organisation/utils.py b/src/organisation/utils.py index 32b2842..ead22ca 100644 --- a/src/organisation/utils.py +++ b/src/organisation/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for organisation module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the organisation module """ \ No newline at end of file diff --git a/src/service/router.py b/src/service/router.py index 9bef22b..f53d666 100644 --- a/src/service/router.py +++ b/src/service/router.py @@ -17,7 +17,7 @@ from src.service.models import Service from src.service.utils import generate_api_key from src.service.dependencies import service_model_body_dependency from src.service.schemas import ServiceGetServiceResponse, ServicePostServiceRequest, ServicePostServiceResponse, \ - ServiceWithKeyResponse, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest + ServiceWithKeySchema, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest router = APIRouter( tags=["Service"], @@ -42,7 +42,7 @@ async def register_service(db: db_dependency, su: super_admin_dependency, servic if isinstance(e.orig, UniqueViolation): raise Conflict(message="Service with this name already exists") db.commit() - response = ServiceWithKeyResponse(**service_model.__dict__) + response = ServiceWithKeySchema(**service_model.__dict__) db.commit() return {"service": response} @@ -52,7 +52,7 @@ async def regenerate_api_key(db: db_dependency, su: super_admin_dependency, serv service_model.api_key = key db.flush() - response = ServiceWithKeyResponse(**service_model.__dict__) + response = ServiceWithKeySchema(**service_model.__dict__) db.commit() return {"service": response} diff --git a/src/service/schemas.py b/src/service/schemas.py index deef4e3..d6f2882 100644 --- a/src/service/schemas.py +++ b/src/service/schemas.py @@ -12,29 +12,29 @@ from src.schemas import CustomBaseModel class ServiceIDMixin(CustomBaseModel): service_id: int -class ServiceResponse(CustomBaseModel): +class ServiceSchema(CustomBaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") id: int name: str -class ServiceWithKeyResponse(ServiceResponse): +class ServiceWithKeySchema(ServiceSchema): api_key: str class ServiceGetServiceResponse(CustomBaseModel): - services: list[ServiceResponse] + services: list[ServiceSchema] class ServicePostServiceRequest(CustomBaseModel): name: str class ServicePostServiceResponse(CustomBaseModel): - service: ServiceWithKeyResponse + service: ServiceWithKeySchema class ServicePatchKeyRequest(ServiceIDMixin): pass class ServicePatchKeyResponse(CustomBaseModel): - service: ServiceWithKeyResponse + service: ServiceWithKeySchema class ServiceDeleteServiceRequest(ServiceIDMixin): pass