diff --git a/.env.development b/.env.development index 0e2afa4..cd341cc 100644 --- a/.env.development +++ b/.env.development @@ -21,6 +21,4 @@ AUTHENTIK_USERINFO_ENDPOINT=https://auth.ose.tw/application/o/userinfo/ PUBLIC_FRONTEND_ORIGINS=http://127.0.0.1:5173,http://localhost:5173 INTERNAL_SHARED_SECRET=CHANGE_ME -ADMIN_ALLOWLIST_EMAILS=chris@ose.tw -ADMIN_ALLOWLIST_SUBS=17a35b0a03a752d60617cf2de2bef2aaf0f0f0f53f24e5bf33c3e7abb6c06e87 -ADMIN_REQUIRED_GROUPS= +ADMIN_REQUIRED_GROUPS=member-admin diff --git a/.env.example b/.env.example index e28c7a4..8ff5dc4 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,4 @@ AUTHENTIK_USERINFO_ENDPOINT= PUBLIC_FRONTEND_ORIGINS=https://member.ose.tw,https://mkt.ose.tw,https://admin.ose.tw INTERNAL_SHARED_SECRET=CHANGE_ME -ADMIN_ALLOWLIST_EMAILS= -ADMIN_ALLOWLIST_SUBS= -ADMIN_REQUIRED_GROUPS= +ADMIN_REQUIRED_GROUPS=member-admin diff --git a/app/api/admin.py b/app/api/admin.py index fa9115f..5e5b81e 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -4,7 +4,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from app.db.session import get_db -from app.models.api_client import ApiClient from app.repositories.companies_repo import CompaniesRepository from app.repositories.modules_repo import ModulesRepository from app.repositories.permissions_repo import PermissionsRepository @@ -17,7 +16,6 @@ from app.schemas.permissions import ( PermissionGrantRequest, PermissionRevokeRequest, ) -from app.security.api_client_auth import require_api_client from app.security.admin_guard import require_admin_principal router = APIRouter( @@ -67,7 +65,6 @@ def _resolve_scope_ids(db: Session, scope_type: str, scope_id: str) -> tuple[str @router.post("/permissions/grant") def grant_permission( payload: PermissionGrantRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, str]: users_repo = UsersRepository(db) @@ -96,7 +93,6 @@ def grant_permission( @router.post("/permissions/revoke") def revoke_permission( payload: PermissionRevokeRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, int | str]: users_repo = UsersRepository(db) @@ -121,7 +117,6 @@ def revoke_permission( @router.get("/permissions/direct", response_model=DirectPermissionListResponse) def list_direct_permissions( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), keyword: str | None = Query(default=None), scope_type: str | None = Query(default=None), @@ -146,7 +141,6 @@ def list_direct_permissions( @router.delete("/permissions/direct/{permission_id}") def delete_direct_permission( permission_id: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, int | str]: try: diff --git a/app/api/admin_catalog.py b/app/api/admin_catalog.py index 881ac79..3d32ec7 100644 --- a/app/api/admin_catalog.py +++ b/app/api/admin_catalog.py @@ -4,7 +4,6 @@ from sqlalchemy.orm import Session from app.core.keygen import generate_key from app.core.config import get_settings from app.db.session import get_db -from app.models.api_client import ApiClient from app.repositories.companies_repo import CompaniesRepository from app.repositories.modules_repo import ModulesRepository from app.repositories.permission_groups_repo import PermissionGroupsRepository @@ -39,7 +38,6 @@ from app.schemas.catalog import ( SystemUpdateRequest, ) from app.schemas.permissions import PermissionGrantRequest, PermissionRevokeRequest -from app.security.api_client_auth import require_api_client from app.security.admin_guard import require_admin_principal from app.services.authentik_admin_service import AuthentikAdminService @@ -126,7 +124,6 @@ def _sync_member_to_authentik( @router.get("/systems") def list_systems( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), @@ -139,7 +136,6 @@ def list_systems( @router.post("/systems", response_model=SystemItem) def create_system( payload: SystemCreateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> SystemItem: repo = SystemsRepository(db) @@ -152,7 +148,6 @@ def create_system( def update_system( system_key: str, payload: SystemUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> SystemItem: repo = SystemsRepository(db) @@ -165,7 +160,6 @@ def update_system( @router.get("/modules") def list_modules( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), limit: int = Query(default=200, ge=1, le=500), offset: int = Query(default=0, ge=0), @@ -191,7 +185,6 @@ def list_modules( @router.post("/modules", response_model=ModuleItem) def create_module( payload: ModuleCreateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> ModuleItem: systems_repo = SystemsRepository(db) @@ -213,7 +206,6 @@ def create_module( def update_module( module_key: str, payload: ModuleUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> ModuleItem: modules_repo = ModulesRepository(db) @@ -227,7 +219,6 @@ def update_module( @router.get("/systems/{system_key}/groups") def list_system_groups( system_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: systems_repo = SystemsRepository(db) @@ -246,7 +237,6 @@ def list_system_groups( @router.get("/systems/{system_key}/members") def list_system_members( system_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: systems_repo = SystemsRepository(db) @@ -270,7 +260,6 @@ def list_system_members( @router.get("/modules/{module_key}/groups") def list_module_groups( module_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: modules_repo = ModulesRepository(db) @@ -290,7 +279,6 @@ def list_module_groups( @router.get("/modules/{module_key}/members") def list_module_members( module_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: modules_repo = ModulesRepository(db) @@ -314,7 +302,6 @@ def list_module_members( @router.get("/companies") def list_companies( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), keyword: str | None = Query(default=None), limit: int = Query(default=100, ge=1, le=500), @@ -328,7 +315,6 @@ def list_companies( @router.post("/companies", response_model=CompanyItem) def create_company( payload: CompanyCreateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> CompanyItem: repo = CompaniesRepository(db) @@ -341,7 +327,6 @@ def create_company( def update_company( company_key: str, payload: CompanyUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> CompanyItem: repo = CompaniesRepository(db) @@ -355,7 +340,6 @@ def update_company( @router.get("/companies/{company_key}/sites") def list_company_sites( company_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: companies_repo = CompaniesRepository(db) @@ -380,7 +364,6 @@ def list_company_sites( @router.get("/sites") def list_sites( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), company_key: str | None = Query(default=None), keyword: str | None = Query(default=None), @@ -420,7 +403,6 @@ def list_sites( @router.post("/sites", response_model=SiteItem) def create_site( payload: SiteCreateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> SiteItem: companies_repo = CompaniesRepository(db) @@ -437,7 +419,6 @@ def create_site( def update_site( site_key: str, payload: SiteUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> SiteItem: companies_repo = CompaniesRepository(db) @@ -462,7 +443,6 @@ def update_site( @router.get("/members") def list_members( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), keyword: str | None = Query(default=None), limit: int = Query(default=100, ge=1, le=500), @@ -476,7 +456,6 @@ def list_members( @router.post("/members/upsert", response_model=MemberItem) def upsert_member( payload: MemberUpsertRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> MemberItem: users_repo = UsersRepository(db) @@ -517,7 +496,6 @@ def upsert_member( def update_member( authentik_sub: str, payload: MemberUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> MemberItem: users_repo = UsersRepository(db) @@ -558,7 +536,6 @@ def update_member( @router.get("/members/{authentik_sub}/permission-groups", response_model=MemberPermissionGroupsResponse) def get_member_permission_groups( authentik_sub: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> MemberPermissionGroupsResponse: users_repo = UsersRepository(db) @@ -574,7 +551,6 @@ def get_member_permission_groups( def set_member_permission_groups( authentik_sub: str, payload: MemberPermissionGroupsUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> MemberPermissionGroupsResponse: users_repo = UsersRepository(db) @@ -596,7 +572,6 @@ def set_member_permission_groups( @router.get("/permission-groups") def list_permission_groups( - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), @@ -609,7 +584,6 @@ def list_permission_groups( @router.get("/permission-groups/{group_key}/permissions") def list_permission_group_permissions( group_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, list[dict]]: repo = PermissionGroupsRepository(db) @@ -636,7 +610,6 @@ def list_permission_group_permissions( @router.get("/permission-groups/{group_key}/bindings", response_model=GroupBindingSnapshot) def get_permission_group_bindings( group_key: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> GroupBindingSnapshot: repo = PermissionGroupsRepository(db) @@ -658,7 +631,6 @@ def get_permission_group_bindings( def replace_permission_group_bindings( group_key: str, payload: GroupBindingUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> GroupBindingSnapshot: repo = PermissionGroupsRepository(db) @@ -715,7 +687,6 @@ def replace_permission_group_bindings( @router.post("/permission-groups", response_model=PermissionGroupItem) def create_permission_group( payload: PermissionGroupCreateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> PermissionGroupItem: repo = PermissionGroupsRepository(db) @@ -728,7 +699,6 @@ def create_permission_group( def update_permission_group( group_key: str, payload: PermissionGroupUpdateRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> PermissionGroupItem: repo = PermissionGroupsRepository(db) @@ -743,7 +713,6 @@ def update_permission_group( def add_group_member( group_key: str, authentik_sub: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, str]: groups_repo = PermissionGroupsRepository(db) @@ -758,7 +727,6 @@ def add_group_member( def remove_group_member( group_key: str, authentik_sub: str, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, int | str]: groups_repo = PermissionGroupsRepository(db) @@ -773,7 +741,6 @@ def remove_group_member( def grant_group_permission( group_key: str, payload: PermissionGrantRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, str]: groups_repo = PermissionGroupsRepository(db) @@ -798,7 +765,6 @@ def grant_group_permission( def revoke_group_permission( group_key: str, payload: PermissionRevokeRequest, - _: ApiClient = Depends(require_api_client), db: Session = Depends(get_db), ) -> dict[str, int | str]: groups_repo = PermissionGroupsRepository(db) diff --git a/app/core/config.py b/app/core/config.py index cc8964e..64afa21 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -30,8 +30,6 @@ class Settings(BaseSettings): public_frontend_origins: Annotated[list[str], NoDecode] = ["https://member.ose.tw"] internal_shared_secret: str = "" - admin_allowlist_emails: Annotated[list[str], NoDecode] = [] - admin_allowlist_subs: Annotated[list[str], NoDecode] = [] admin_required_groups: Annotated[list[str], NoDecode] = [] @field_validator("public_frontend_origins", mode="before") @@ -43,7 +41,7 @@ class Settings(BaseSettings): return [] return [origin.strip() for origin in value.split(",") if origin.strip()] - @field_validator("admin_allowlist_emails", "admin_allowlist_subs", "admin_required_groups", mode="before") + @field_validator("admin_required_groups", mode="before") @classmethod def parse_csv(cls, value: str | list[str]) -> list[str]: if isinstance(value, list): diff --git a/app/security/admin_guard.py b/app/security/admin_guard.py index 117b936..f669858 100644 --- a/app/security/admin_guard.py +++ b/app/security/admin_guard.py @@ -9,18 +9,14 @@ def require_admin_principal( principal: AuthentikPrincipal = Depends(require_authenticated_principal), ) -> AuthentikPrincipal: settings = get_settings() - allowed_emails = {email.lower() for email in settings.admin_allowlist_emails} - allowed_subs = set(settings.admin_allowlist_subs) required_groups = {group.lower() for group in settings.admin_required_groups} - if not allowed_emails and not allowed_subs and not required_groups: + if not required_groups: raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="admin_policy_not_configured") - email_ok = bool(principal.email and principal.email.lower() in allowed_emails) - sub_ok = principal.sub in allowed_subs principal_groups = {group.lower() for group in principal.groups} group_ok = bool(required_groups.intersection(principal_groups)) - if not (email_ok or sub_ok or group_ok): + if not group_ok: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="admin_forbidden") return principal