清理全局角色残留并修复项目权限判断

This commit is contained in:
Cheng Zhou
2026-05-29 10:20:42 +08:00
parent 63457aab11
commit 44d69c2d7b
63 changed files with 739 additions and 443 deletions
+13 -17
View File
@@ -14,6 +14,7 @@ from sqlalchemy import delete as sa_delete, or_, select, update as sa_update
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_cra_site_scope
from app.core.deps import get_operator_role_label, is_system_admin
from app.core.project_permissions import role_has_api_permission
from app.crud import acknowledgement as acknowledgement_crud
from app.crud import distribution as distribution_crud
@@ -43,14 +44,11 @@ DOCUMENT_ACTION_PERMISSIONS = {
"ack": "documents:read",
"create_document": "documents:create",
"create_version": "documents:update",
"distribute": "documents:update",
"delete_document": "documents:delete",
}
def _role_value(user) -> str:
return user.role.value if hasattr(user.role, "value") else str(user.role)
def _audit_detail(before: dict | None, after: dict | None) -> str:
payload = {"before": before, "after": after}
return json.dumps(payload, ensure_ascii=True)
@@ -82,7 +80,7 @@ async def _ensure_study_access(db: AsyncSession, trial_id: uuid.UUID, current_us
study = await study_crud.get(db, trial_id)
if not study:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
if current_user.role == "ADMIN":
if is_system_admin(current_user):
return None
membership = await member_crud.get_member(db, trial_id, current_user.id)
if not membership or not membership.is_active:
@@ -98,7 +96,7 @@ async def _ensure_study_member(db: AsyncSession, trial_id: uuid.UUID, current_us
study = await study_crud.get(db, trial_id)
if not study:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
if current_user.role == "ADMIN":
if is_system_admin(current_user):
return None
membership = await member_crud.get_member(db, trial_id, current_user.id)
if not membership or not membership.is_active:
@@ -154,7 +152,7 @@ async def create_document(
action="DOCUMENT_CREATED",
detail=_audit_detail(None, _doc_snapshot(doc)),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
await db.commit()
@@ -345,7 +343,7 @@ async def create_version(
action="VERSION_CREATED",
detail=_audit_detail(None, _version_snapshot(version)),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
@@ -429,7 +427,7 @@ async def delete_version(
action="VERSION_DELETED",
detail=_audit_detail(before, None),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
await db.commit()
@@ -466,7 +464,7 @@ async def delete_document(
action="DOCUMENT_ARCHIVED",
detail=_audit_detail(before, _doc_snapshot(doc)),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
await db.commit()
@@ -511,7 +509,7 @@ async def create_distributions(
doc = await document_crud.get(db, version.document_id)
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文档不存在")
await _ensure_study_member(db, doc.trial_id, current_user)
await _ensure_study_access(db, doc.trial_id, current_user, action="distribute")
if version.status != DocumentVersionStatus.EFFECTIVE:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="只有生效版本可分发")
@@ -536,7 +534,7 @@ async def create_distributions(
action="DISTRIBUTION_CREATED",
detail=_audit_detail(None, {"version_id": str(version.id), "target_id": target.target_id}),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
await db.commit()
@@ -599,8 +597,7 @@ async def create_acknowledgement(
if distribution.target_type == DistributionTargetType.USER and distribution.target_id != str(current_user.id):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不在分发范围内")
if distribution.target_type == DistributionTargetType.ROLE:
role_value = _role_value(current_user)
if distribution.target_id not in (role_value, getattr(membership, "role_in_study", "")):
if distribution.target_id not in ("ADMIN" if is_system_admin(current_user) else "", getattr(membership, "role_in_study", "")):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不在分发范围内")
if payload.ack_type != AcknowledgementType.RECEIVED:
@@ -633,7 +630,7 @@ async def create_acknowledgement(
action="ACK_CREATED",
detail=_audit_detail(None, {"distribution_id": str(distribution.id), "ack_type": payload.ack_type}),
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, doc.trial_id, current_user),
)
)
await db.commit()
@@ -693,9 +690,8 @@ async def list_distribution_notifications(
skip: int = 0,
limit: int = 20,
) -> list[NotificationItem]:
role_value = _role_value(current_user)
membership = None
if role_value != "ADMIN":
if not is_system_admin(current_user):
membership = await member_crud.get_member(db, study_id, current_user.id)
role_in_study = membership.role_in_study if membership else None
+3 -8
View File
@@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.crud import document as document_crud
from app.crud import etmf as etmf_crud
from app.core.deps import get_operator_role_label
from app.models.document import Document, DocumentStatus
from app.models.etmf import EtmfNode
from app.models.audit_log import AuditLog
@@ -17,12 +18,6 @@ from app.schemas.document import DocumentCreate, DocumentSummary
from app.schemas.etmf import EtmfNodeCreate, EtmfNodeRead, EtmfNodeStatus, EtmfTreeNode, EtmfNodeUpdate
def _role_value(user) -> str:
if user is None:
return "SYSTEM"
return user.role.value if hasattr(user.role, "value") else str(user.role)
def calculate_node_status(node: EtmfNode, documents: Iterable[Document]) -> EtmfNodeStatus:
docs = list(documents)
if not node.is_active:
@@ -109,7 +104,7 @@ async def create_node(db: AsyncSession, payload: EtmfNodeCreate, current_user) -
action="ETMF_NODE_CREATED",
detail=f'{{"code":"{node.code}","name":"{node.name}"}}',
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, payload.study_id, current_user),
)
)
await db.commit()
@@ -138,7 +133,7 @@ async def update_node(db: AsyncSession, node_id: uuid.UUID, payload: EtmfNodeUpd
action="ETMF_NODE_UPDATED",
detail="{}",
operator_id=current_user.id,
operator_role=_role_value(current_user),
operator_role=await get_operator_role_label(db, node.study_id, current_user),
)
)
await db.commit()