from typing import Optional from uuid import UUID from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.models.permission_template import PermissionTemplate, PermissionTemplateVersion, TemplateType from app.models.api_endpoint_permission import ApiEndpointPermission from app.core.api_permissions import API_ENDPOINT_PERMISSIONS, PROJECT_PERMISSION_ROLES from app.schemas.permission_template import PermissionTemplateCreate, PermissionTemplateUpdate class PermissionTemplateService: """权限模板服务""" @staticmethod async def create_template( db: AsyncSession, payload: PermissionTemplateCreate, created_by: UUID, ) -> PermissionTemplate: """创建权限模板""" if payload.template_type != TemplateType.CUSTOM: raise ValueError("只能新增自定义角色") # 验证权限配置 await PermissionTemplateService._validate_permissions(payload.permissions) template = PermissionTemplate( name=payload.name, description=payload.description, template_type=payload.template_type, is_system=False, created_by=created_by, permissions=payload.permissions, tags=payload.tags, category=payload.category, recommended_roles=payload.recommended_roles, ) db.add(template) await db.flush() # 获取 template.id # 创建版本 1 version = PermissionTemplateVersion( template_id=template.id, version=1, permissions=payload.permissions, change_log="初始版本", ) db.add(version) await db.commit() await db.refresh(template) return template @staticmethod async def get_template(db: AsyncSession, template_id: UUID) -> Optional[PermissionTemplate]: """获取权限模板""" result = await db.execute( select(PermissionTemplate) .options(selectinload(PermissionTemplate.versions)) .where(PermissionTemplate.id == template_id) ) return result.scalar_one_or_none() @staticmethod async def list_templates( db: AsyncSession, template_type: Optional[TemplateType] = None, category: Optional[str] = None, skip: int = 0, limit: int = 100, ) -> list[PermissionTemplate]: """列表权限模板""" query = select(PermissionTemplate).options(selectinload(PermissionTemplate.versions)) if template_type: query = query.where(PermissionTemplate.template_type == template_type) if category: query = query.where(PermissionTemplate.category == category) elif template_type == TemplateType.ROLE: query = query.where( (PermissionTemplate.is_system.is_(False)) | (PermissionTemplate.category.in_(PROJECT_PERMISSION_ROLES)) ) query = query.offset(skip).limit(limit) result = await db.execute(query) return result.scalars().all() @staticmethod async def update_template( db: AsyncSession, template_id: UUID, payload: PermissionTemplateUpdate, ) -> PermissionTemplate: """更新权限模板""" template = await PermissionTemplateService.get_template(db, template_id) if not template: raise ValueError(f"模板 {template_id} 不存在") # 系统预设模板只允许更新展示元数据,权限配置和分类等结构字段保持受保护。 if template.is_system: protected_fields = {"permissions", "tags", "category", "recommended_roles"} if protected_fields.intersection(payload.model_fields_set): raise ValueError("不允许修改系统预设模板") # 验证权限配置 if payload.permissions: await PermissionTemplateService._validate_permissions(payload.permissions) # 更新字段 if payload.name: template.name = payload.name if payload.description is not None: template.description = payload.description if payload.permissions: # 加载 versions 以获取最新版本号 await db.refresh(template, ["versions"]) latest_version = max([v.version for v in template.versions], default=0) new_version = PermissionTemplateVersion( template_id=template.id, version=latest_version + 1, permissions=payload.permissions, change_log="", ) db.add(new_version) template.permissions = payload.permissions if payload.tags is not None: template.tags = payload.tags if payload.category is not None: template.category = payload.category if payload.recommended_roles is not None: template.recommended_roles = payload.recommended_roles await db.commit() await db.refresh(template) return template @staticmethod async def delete_template(db: AsyncSession, template_id: UUID) -> None: """删除权限模板""" template = await PermissionTemplateService.get_template(db, template_id) if not template: raise ValueError(f"模板 {template_id} 不存在") # 不允许删除系统预设模板 if template.is_system: raise ValueError("不允许删除系统预设模板") await db.delete(template) await db.commit() @staticmethod async def apply_template( db: AsyncSession, study_id: UUID, template_id: UUID, roles: Optional[list[str]] = None, override: bool = True, ) -> dict: """应用权限模板到项目""" template = await PermissionTemplateService.get_template(db, template_id) if not template: raise ValueError(f"模板 {template_id} 不存在") # 确定要应用的角色 if roles is None: if template.recommended_roles: roles = template.recommended_roles.split(",") else: roles = list(template.permissions.keys()) # 删除现有权限(如果覆盖) if override: await db.execute( delete(ApiEndpointPermission).where( ApiEndpointPermission.study_id == study_id, ApiEndpointPermission.role.in_(roles), ) ) # 应用模板权限 for role in roles: if role not in template.permissions: continue role_permissions = template.permissions[role] for endpoint_key, allowed in role_permissions.items(): # 检查权限是否存在 if endpoint_key not in API_ENDPOINT_PERMISSIONS: continue # 检查是否已存在 result = await db.execute( select(ApiEndpointPermission).where( ApiEndpointPermission.study_id == study_id, ApiEndpointPermission.role == role, ApiEndpointPermission.endpoint_key == endpoint_key, ) ) perm = result.scalar_one_or_none() if perm: perm.allowed = allowed else: perm = ApiEndpointPermission( study_id=study_id, role=role, endpoint_key=endpoint_key, allowed=allowed, ) db.add(perm) await db.commit() # 返回应用后的权限矩阵 from app.core.project_permissions import get_api_endpoint_permissions permissions = await get_api_endpoint_permissions(db, study_id) return { "study_id": study_id, "applied_roles": roles, "permissions": permissions, } @staticmethod async def _validate_permissions(permissions: dict) -> None: """验证权限配置""" for role, role_permissions in permissions.items(): if not isinstance(role_permissions, dict): raise ValueError(f"角色 {role} 的权限配置格式错误") for endpoint_key, allowed in role_permissions.items(): if endpoint_key.startswith("_"): # 跳过元数据字段 continue if endpoint_key not in API_ENDPOINT_PERMISSIONS: raise ValueError(f"权限操作 {endpoint_key} 不存在") if not isinstance(allowed, bool): raise ValueError(f"权限 {endpoint_key} 的值必须是布尔类型")