Files
server-core/app/rbac.py
T
Pine d754d6ca4e feat: 业务层 services + 领域层 domain
- 领域层 domain/rules.py:RBAC 判定规则纯函数(role/permission/scope),接口层守卫改调领域规则
- 业务层 services/task_service.py:统一任务状态机(grab/bid/deliver/win/review),修复 deliver 无状态预检缺陷
- rbac_opc 抢单/投标/交付改走 TaskService
2026-08-23 23:56:39 +08:00

93 lines
3.1 KiB
Python

# -*- coding: utf-8 -*-
"""接口层 RBAC 守卫工厂:角色 / 权限 / 数据范围守卫 + 审计写入。
判定语义在领域层 ``domain/rules.py``;本文件只把规则映射为 HTTP 异常。
"""
from __future__ import annotations
from collections.abc import Callable
from fastapi import Depends, HTTPException, Request
from .api.dependencies import get_current_user
from .domain.rules import (
is_government,
permission_granted,
region_in_scope,
role_allowed,
scope_level_ok,
sub_role_allowed,
)
from .infrastructure.repositories import Database
def require_roles(*roles: str) -> Callable:
"""要求当前用户业务角色 ∈ roles;否则 403。"""
def dep(user: dict = Depends(get_current_user)) -> dict:
if not role_allowed(user, *roles):
raise HTTPException(status_code=403, detail="Forbidden: insufficient role")
return user
return dep
def require_sub_roles(*sub_roles: str) -> Callable:
"""要求当前用户子角色 ∈ sub_roles(如 op_super_admin);否则 403。"""
def dep(user: dict = Depends(get_current_user)) -> dict:
if not sub_role_allowed(user, *sub_roles):
raise HTTPException(status_code=403, detail="Forbidden: insufficient sub-role")
return user
return dep
def require_permission(perm: str) -> Callable:
"""要求当前用户拥有权限码 ``perm``;否则 403。"""
def dep(user: dict = Depends(get_current_user)) -> dict:
if not permission_granted(user, perm):
raise HTTPException(status_code=403, detail=f"Forbidden: missing permission {perm}")
return user
return dep
def require_scope(min_level: str | None = None) -> Callable:
"""政务数据范围守卫:要求 government 且区域层级不低于 min_level。"""
def dep(user: dict = Depends(get_current_user)) -> dict:
if not is_government(user):
raise HTTPException(status_code=403, detail="Forbidden: not government")
if not user.get("region_id"):
raise HTTPException(status_code=403, detail="Forbidden: no region scope")
if not scope_level_ok(user, min_level):
raise HTTPException(status_code=403, detail=f"Forbidden: scope below {min_level}")
return user
return dep
def scope_covers(region_id: str | None) -> Callable:
"""要求 ``region_id`` 落在当前用户数据范围内;否则 403。"""
def dep(user: dict = Depends(get_current_user)) -> dict:
if not region_in_scope(user, region_id):
raise HTTPException(status_code=403, detail="Forbidden: out of data scope")
return user
return dep
async def write_audit(
db: Database,
*,
action: str,
resource: str,
resource_id: str = "",
detail: str = "",
user: dict | None = None,
request: Request | None = None,
) -> None:
"""写入一条审计日志(异步)。"""
ip = request.client.host if request is not None and request.client else ""
await db.audit.add(
action=action,
resource=resource,
resource_id=resource_id,
detail=detail,
ip=ip,
user_id=(user or {}).get("id"),
)