清理全局角色残留并修复项目权限判断
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user