feat(auth): 新增 IsCompanyAdminUser 权限类并修复待审列表跨租户泄漏

- permissions.py 新增 IsCompanyAdminUser(admin:company / admin:tenant / admin:super)

- ListPendingApplications 移除被静默忽略的 TenantUUID 形参,改为显式 AllowedCompanyUUIDs

- 过滤在 SQL 侧用子查询完成,规避 UUIDObj 与 bytes 混比恒为 False 的坑
This commit is contained in:
TermiNexus committed 2026-09-28 18:23:08 +08:00
1 parent 4b074e565a
commit 94cb71e82a
2 files changed
+45 -3

No files matched your search

+23 -3
View File
@@ -3257,12 +3257,32 @@ class AppManager:
return True
@staticmethod
def ListPendingApplications(TenantUUID):
def ListPendingApplications(AllowedCompanyUUIDs=None):
"""待审批的个人授权申请(ApplyStatus=1,按申请时间倒序)
说明:UserApp 表无租户列,租户隔离由上层承担(保持既有行为)。
数据范围:UserApp 表无租户/公司列,作用域按「申请人所属公司」反查
(UserRole.CompanyUUID)。由调用方按管理员级别显式传参:
- ``AllowedCompanyUUIDs=None`` → 不限制(平台管理员 admin:super)
- ``AllowedCompanyUUIDs=[]`` → 无任何可管辖公司,返回空
- ``AllowedCompanyUUIDs=[...]`` → 仅申请人的公司命中该集合的申请
注意:此前签名是 ``(TenantUUID)`` 但**传入值被完全忽略**,导致任何租户
管理员都能看到全平台待审申请(跨租户泄漏)。现改为显式范围参数,不再
接受会被静默忽略的租户参数。
"""
return list(UserApp.objects.filter(ApplyStatus=1).order_by('-ApplyTime'))
qs = UserApp.objects.filter(ApplyStatus=1).order_by('-ApplyTime')
if AllowedCompanyUUIDs is not None:
allowed = [c for c in AllowedCompanyUUIDs if c]
if not allowed:
return []
# 用子查询在 SQL 侧完成「申请人属于可管辖公司」过滤,避免 Python 侧
# UUIDObj/bytes 混比(两者 hash 不同,曾因此导致判定恒为 False)
qs = qs.filter(
UserUUID__in=UserRole.objects.filter(
CompanyUUID__in=allowed,
).values('UserUUID')
)
return list(qs)
# ==================== 应用配置(AppConfig 键值表) ====================
+22
View File
@@ -53,6 +53,28 @@ class IsAdminUser(BasePermission):
return IsAdmin(request)
class IsCompanyAdminUser(BasePermission):
"""仅允许公司管理员及以上访问(admin:company / admin:tenant / admin:super)
三级管理员体系(由窄到宽):
- ``admin:company``:公司管理员,按 RolePermission.CompanyUUID 绑定到具体公司后生效
- ``admin:tenant`` :租户内管理员,自动继承公司管理员能力
- ``admin:super`` :跨租户管理员,天然具备
判定委托给 ``gvsdsdk.auth.admin_service.IsCompanyAdmin``。本类只负责"能不能进",
数据范围收窄(本公司/本租户/全部)由视图按 IsSuperAdmin/IsTenantAdmin 另行计算。
"""
def has_permission(self, request, view):
user = request.user
if not user or not user.is_authenticated:
return False
if not isinstance(user, _GetUserModel()):
return False
from gvsdsdk.auth.admin_service import IsCompanyAdmin
return IsCompanyAdmin(request)
class IsCompanyMember(BasePermission):
"""验证用户当前角色属于目标数据所在公司