清理全局角色残留并修复项目权限判断
This commit is contained in:
@@ -269,19 +269,6 @@ API_ENDPOINT_PERMISSIONS = {
|
||||
"description": "删除立项记录",
|
||||
"default_roles": ["PM"],
|
||||
},
|
||||
# 项目权限管理
|
||||
"permissions:read": {
|
||||
"module": "project_members",
|
||||
"action": "read",
|
||||
"description": "查询项目权限矩阵",
|
||||
"default_roles": ["PM"],
|
||||
},
|
||||
"permissions:update": {
|
||||
"module": "project_members",
|
||||
"action": "write",
|
||||
"description": "更新项目权限矩阵",
|
||||
"default_roles": ["PM"],
|
||||
},
|
||||
# 项目概览
|
||||
"project_overview:read": {
|
||||
"module": "project_overview",
|
||||
|
||||
+47
-17
@@ -12,6 +12,7 @@ from app.core.exceptions import AppException
|
||||
from app.core.security import decode_token, oauth2_scheme
|
||||
from app.crud import user as user_crud
|
||||
from app.crud import member as member_crud
|
||||
from app.core.api_permissions import SYSTEM_PERMISSIONS
|
||||
from app.core.project_permissions import role_has_api_permission, get_missing_prerequisites
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.study_member import StudyMember
|
||||
@@ -53,12 +54,15 @@ async def get_current_user(
|
||||
return user
|
||||
|
||||
|
||||
def is_system_admin(user) -> bool:
|
||||
return bool(getattr(user, "is_admin", False))
|
||||
|
||||
|
||||
def require_roles(roles: Iterable[str]) -> Callable:
|
||||
roles_set = set(roles)
|
||||
|
||||
async def dependency(current_user=Depends(get_current_user)):
|
||||
current_role = current_user.role.value if hasattr(current_user.role, "value") else str(current_user.role)
|
||||
if current_role not in roles_set:
|
||||
if "ADMIN" not in roles_set or not is_system_admin(current_user):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="权限不足",
|
||||
@@ -68,10 +72,6 @@ def require_roles(roles: Iterable[str]) -> Callable:
|
||||
return dependency
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
return user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
|
||||
|
||||
async def list_active_pm_study_ids(db: AsyncSession, user_id: uuid.UUID) -> set[uuid.UUID]:
|
||||
result = await db.execute(
|
||||
select(StudyMember.study_id).where(
|
||||
@@ -95,12 +95,21 @@ async def is_active_project_pm(db: AsyncSession, user_id: uuid.UUID, study_id: u
|
||||
return result.scalar_one_or_none() is not None
|
||||
|
||||
|
||||
async def get_operator_role_label(db: AsyncSession, study_id: uuid.UUID | None, current_user) -> str:
|
||||
if is_system_admin(current_user):
|
||||
return "ADMIN"
|
||||
if not study_id:
|
||||
return ""
|
||||
membership = await member_crud.get_member(db, study_id, current_user.id)
|
||||
return membership.role_in_study if membership and membership.is_active else ""
|
||||
|
||||
|
||||
def require_admin_or_any_project_pm() -> Callable:
|
||||
async def dependency(
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
if _role_value(current_user) == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return current_user
|
||||
if await list_active_pm_study_ids(db, current_user.id):
|
||||
return current_user
|
||||
@@ -112,13 +121,38 @@ def require_admin_or_any_project_pm() -> Callable:
|
||||
return dependency
|
||||
|
||||
|
||||
def require_system_permission(permission_key: str) -> Callable:
|
||||
async def dependency(
|
||||
study_id: uuid.UUID,
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
permission = SYSTEM_PERMISSIONS.get(permission_key)
|
||||
if not permission:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="权限不足",
|
||||
)
|
||||
|
||||
allowed_roles = set(permission.get("roles", []))
|
||||
if is_system_admin(current_user) and "ADMIN" in allowed_roles:
|
||||
return current_user
|
||||
if "PM" in allowed_roles and await is_active_project_pm(db, current_user.id, study_id):
|
||||
return current_user
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="权限不足",
|
||||
)
|
||||
|
||||
return dependency
|
||||
|
||||
|
||||
async def get_study_member(
|
||||
study_id: uuid.UUID,
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
role_value = _role_value(current_user)
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return None
|
||||
return await member_crud.get_member(db, study_id, current_user.id)
|
||||
|
||||
@@ -129,8 +163,7 @@ def require_study_member():
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
role_value = _role_value(current_user)
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return current_user
|
||||
membership = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not membership or not membership.is_active:
|
||||
@@ -152,8 +185,7 @@ def require_study_roles(roles: Iterable[str], *, allow_system_admin: bool = True
|
||||
current_user=Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
role_value = _role_value(current_user)
|
||||
if allow_system_admin and role_value == "ADMIN":
|
||||
if allow_system_admin and is_system_admin(current_user):
|
||||
return current_user
|
||||
membership = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not membership or not membership.is_active or membership.role_in_study not in roles_set:
|
||||
@@ -183,8 +215,7 @@ def require_api_permission(endpoint_key: str, *, allow_system_admin: bool = True
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
from app.core.permission_monitor import get_permission_monitor
|
||||
role_value = _role_value(current_user)
|
||||
if allow_system_admin and role_value == "ADMIN":
|
||||
if allow_system_admin and is_system_admin(current_user):
|
||||
_enqueue_permission_log(
|
||||
study_id, current_user.id, endpoint_key, "ADMIN", True, 0.0, request
|
||||
)
|
||||
@@ -275,8 +306,7 @@ async def get_cra_site_scope(
|
||||
) -> tuple[set[uuid.UUID], set[str]] | None:
|
||||
from app.crud import site as site_crud
|
||||
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return None
|
||||
membership = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not membership or not membership.is_active:
|
||||
|
||||
@@ -30,8 +30,7 @@ ROLE_ACTIONS: dict[str, set[str]] = {
|
||||
|
||||
|
||||
def is_allowed(action: str, user, membership) -> bool:
|
||||
role_value = user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
if role_value == "ADMIN":
|
||||
if getattr(user, "is_admin", False):
|
||||
return action in ROLE_ACTIONS["ADMIN"]
|
||||
member_role = getattr(membership, "role_in_study", None)
|
||||
if member_role:
|
||||
|
||||
@@ -17,7 +17,6 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", scheme_name=
|
||||
def create_access_token(
|
||||
*,
|
||||
user_id: str,
|
||||
role: str,
|
||||
expires_minutes: Optional[int] = None,
|
||||
session_start: Optional[datetime] = None,
|
||||
) -> str:
|
||||
@@ -26,7 +25,6 @@ def create_access_token(
|
||||
session_start_time = session_start or now
|
||||
to_encode: Dict[str, Any] = {
|
||||
"sub": user_id,
|
||||
"role": role,
|
||||
"exp": expire,
|
||||
"iat": int(now.timestamp()),
|
||||
"orig_iat": int(session_start_time.timestamp()),
|
||||
|
||||
Reference in New Issue
Block a user