Files
ctms/backend/app/core/project_permissions.py
T
Cheng Zhou 6cefa620e4 权限监控与管理界面全面美化
重新设计权限系统所有 UI 组件的视觉风格,统一配色、圆角、阴影和交互动效:
- 实时概览:统计卡片加图标和渐变色条,健康评分改为环形进度,告警改为卡片式
- 趋势分析:图表卡片加彩色图标标识,ECharts 配色升级为渐变面积填充
- 访问日志:指标卡片带图标,日志审计改为卡片式入口,IP排行前三高亮
- IP属地:工具栏重设计,排行列表前三渐变高亮,指标卡片统一新风格
- 系统级权限:从 el-table 改为自定义卡片列表,模块块独立圆角卡片
- 项目权限配置:空状态引导优化,成员表格加头像,工具栏加背景容器
- 角色概览卡片:加进度条可视化,hover 微动效
- 接口权限矩阵:工具栏分离布局,表格圆角包裹
- 角色管理抽屉:侧边栏选中态渐变,操作行 hover 高亮

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 14:36:35 +08:00

163 lines
5.0 KiB
Python

from __future__ import annotations
import uuid
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.api_endpoint_permission import ApiEndpointPermission
from app.models.study import Study
from app.core.api_permissions import API_ENDPOINT_PERMISSIONS, OPERATION_PREREQUISITES
from app.core.permission_cache import get_permission_cache
async def _get_project_permission_overrides(
db: AsyncSession,
study_id: uuid.UUID,
) -> dict[str, dict[str, bool]]:
cache = get_permission_cache()
cached_permissions = cache.get_project_permissions(study_id)
from app.core.permission_monitor import get_permission_monitor
monitor = get_permission_monitor()
if cached_permissions is not None:
monitor.record_cache_hit()
return cached_permissions
monitor.record_cache_miss()
result = await db.execute(
select(ApiEndpointPermission).where(
ApiEndpointPermission.study_id == study_id,
)
)
rows = result.scalars().all()
permissions: dict[str, dict[str, bool]] = {}
for row in rows:
permissions.setdefault(row.role, {})[row.endpoint_key] = row.allowed
cache.set_project_permissions(study_id, permissions)
return permissions
async def role_has_api_permission(
db: AsyncSession,
study_id: uuid.UUID,
role: str | None,
endpoint_key: str,
check_prerequisites: bool = True,
) -> bool:
"""检查角色是否有权访问特定接口"""
if role == "ADMIN":
return True
permissions = await _get_project_permission_overrides(db, study_id)
allowed = permissions.get(role or "", {}).get(endpoint_key)
if allowed is None:
return False
if not allowed:
return False
if check_prerequisites:
prerequisites = OPERATION_PREREQUISITES.get(endpoint_key, [])
for prereq_endpoint in prerequisites:
has_prereq = await role_has_api_permission(
db, study_id, role, prereq_endpoint, check_prerequisites=False
)
if not has_prereq:
return False
return True
async def get_missing_prerequisites(
db: AsyncSession,
study_id: uuid.UUID,
role: str | None,
endpoint_key: str,
) -> list[str]:
"""获取缺失的前置权限列表"""
if role == "ADMIN":
return []
missing = []
prerequisites = OPERATION_PREREQUISITES.get(endpoint_key, [])
for prereq_endpoint in prerequisites:
has_prereq = await role_has_api_permission(
db, study_id, role, prereq_endpoint, check_prerequisites=False
)
if not has_prereq:
missing.append(prereq_endpoint)
return missing
async def get_api_endpoint_permissions(
db: AsyncSession,
study_id: uuid.UUID,
) -> dict[str, dict[str, dict[str, bool]]]:
"""获取项目的接口级权限矩阵
返回格式: {role: {endpoint_key: {allowed: bool}}}
"""
overrides = await _get_project_permission_overrides(db, study_id)
study_result = await db.execute(select(Study).where(Study.id == study_id))
study = study_result.scalar_one_or_none()
active_roles = [role for role in (study.active_roles if study else []) if isinstance(role, str) and role.strip()]
roles = list(dict.fromkeys(["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA", *active_roles, *overrides.keys()]))
matrix: dict[str, dict[str, dict[str, bool]]] = {}
for role in roles:
matrix[role] = {}
for endpoint_key, config in API_ENDPOINT_PERMISSIONS.items():
default_allowed = role in config.get("default_roles", [])
matrix[role][endpoint_key] = {"allowed": default_allowed}
for role, endpoints in overrides.items():
if role not in matrix:
matrix[role] = {}
for endpoint_key, allowed in endpoints.items():
matrix[role][endpoint_key] = {"allowed": allowed}
return matrix
async def replace_api_endpoint_permissions(
db: AsyncSession,
study_id: uuid.UUID,
payload: dict[str, dict[str, bool]],
) -> dict[str, dict[str, dict[str, bool]]]:
"""替换项目的接口级权限矩阵"""
await db.execute(
delete(ApiEndpointPermission).where(
ApiEndpointPermission.study_id == study_id,
)
)
for role, endpoints in payload.items():
if role == "ADMIN":
continue
for endpoint_key, allowed in endpoints.items():
if endpoint_key not in API_ENDPOINT_PERMISSIONS:
continue
db.add(
ApiEndpointPermission(
study_id=study_id,
role=role,
endpoint_key=endpoint_key,
allowed=allowed,
)
)
await db.commit()
cache = get_permission_cache()
cache.invalidate_project_permissions(study_id)
cache.invalidate_all_member_roles(study_id)
from app.core.permission_monitor import get_permission_monitor
get_permission_monitor().record_cache_invalidation()
return await get_api_endpoint_permissions(db, study_id)