refactor: rebuild backend around role-site authorization model

This commit is contained in:
Chris
2026-04-02 23:58:13 +08:00
parent e2dd3ce106
commit 1e1d913103
46 changed files with 1645 additions and 2289 deletions

View File

@@ -3,11 +3,11 @@ from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.db.session import get_db
from app.repositories.permissions_repo import PermissionsRepository
from app.schemas.internal import InternalUpsertUserBySubResponse
from app.repositories.users_repo import UsersRepository
from app.repositories.user_sites_repo import UserSitesRepository
from app.schemas.idp_admin import KeycloakEnsureUserRequest, KeycloakEnsureUserResponse
from app.schemas.permissions import PermissionSnapshotResponse
from app.schemas.internal import InternalUpsertUserBySubResponse, InternalUserRoleItem, InternalUserRoleResponse
from app.schemas.permissions import RoleSnapshotResponse
from app.schemas.users import UserUpsertBySubRequest
from app.security.api_client_auth import require_api_client
from app.services.idp_admin_service import KeycloakAdminService
@@ -28,32 +28,84 @@ def upsert_user_by_sub(
email=payload.email,
display_name=payload.display_name,
is_active=payload.is_active,
status=payload.status,
)
return InternalUpsertUserBySubResponse(
id=user.id,
user_sub=user.user_sub,
idp_user_id=user.idp_user_id,
username=user.username,
email=user.email,
display_name=user.display_name,
is_active=user.is_active,
status=user.status,
)
return {
"id": user.id,
"user_sub": user.user_sub,
"idp_user_id": user.idp_user_id,
"username": user.username,
"email": user.email,
"display_name": user.display_name,
"is_active": user.is_active,
}
@router.get("/permissions/{user_sub}/snapshot", response_model=PermissionSnapshotResponse)
def get_permission_snapshot(
user_sub: str,
db: Session = Depends(get_db),
) -> PermissionSnapshotResponse:
def _build_user_role_rows(db: Session, user_sub: str) -> list[tuple[str, str, str, str, str, str, str, str, str]]:
users_repo = UsersRepository(db)
perms_repo = PermissionsRepository(db)
user_sites_repo = UserSitesRepository(db)
user = users_repo.get_by_sub(user_sub)
if user is None:
return PermissionSnapshotResponse(user_sub=user_sub, permissions=[])
return []
permissions = perms_repo.list_by_user(user.id, user.user_sub)
return PermissionService.build_snapshot(user_sub=user_sub, permissions=permissions)
rows = user_sites_repo.get_user_role_rows(user.id)
return [
(
site.site_key,
site.display_name,
company.company_key,
company.display_name,
system.system_key,
system.name,
role.role_key,
role.name,
role.idp_role_name,
)
for site, company, role, system in rows
]
@router.get("/users/{user_sub}/roles", response_model=InternalUserRoleResponse)
def get_user_roles(user_sub: str, db: Session = Depends(get_db)) -> InternalUserRoleResponse:
rows = _build_user_role_rows(db, user_sub)
return InternalUserRoleResponse(
user_sub=user_sub,
roles=[
InternalUserRoleItem(
site_key=site_key,
site_display_name=site_display_name,
company_key=company_key,
company_display_name=company_display_name,
system_key=system_key,
system_name=system_name,
role_key=role_key,
role_name=role_name,
idp_role_name=idp_role_name,
)
for (
site_key,
site_display_name,
company_key,
company_display_name,
system_key,
system_name,
role_key,
role_name,
idp_role_name,
) in rows
],
)
@router.get("/permissions/{user_sub}/snapshot", response_model=RoleSnapshotResponse)
def get_permission_snapshot(
user_sub: str,
db: Session = Depends(get_db),
) -> RoleSnapshotResponse:
rows = _build_user_role_rows(db, user_sub)
return PermissionService.build_role_snapshot(user_sub=user_sub, rows=rows)
@router.post("/idp/users/ensure", response_model=KeycloakEnsureUserResponse)
@@ -73,17 +125,17 @@ def ensure_idp_user(
)
users_repo = UsersRepository(db)
resolved_sub = payload.user_sub or ""
if sync_result.user_sub:
resolved_sub = sync_result.user_sub
resolved_sub = payload.user_sub or sync_result.user_sub or ""
if not resolved_sub:
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="idp_missing_sub")
users_repo.upsert_by_sub(
user_sub=resolved_sub,
username=payload.username,
email=payload.email,
display_name=payload.display_name,
is_active=payload.is_active,
status="active",
idp_user_id=sync_result.user_id,
)
return KeycloakEnsureUserResponse(idp_user_id=sync_result.user_id, action=sync_result.action)