权限模型
概述
Open ACE 将基于角色的访问控制(RBAC)与多租户边界强制结合在一起:
- 6 个角色构成角色宇宙(
app/models/user.py:14-22的UserRole):user、readonly、manager、tenant_admin、platform_admin,以及遗留的admin - 19 个权限通过 4 个内置 RBAC 角色定义分配(
app/services/permission_service.py:72-113的DEFAULT_ROLES) - 10 个认证装饰器(见下方清单),覆盖登录、各管理层级、机器访问和租户边界
- 一个严格模式开关
OPENACE_PLATFORM_ADMIN_STRICT_MODE,用于收紧"谁算平台管理员"
按端点逐一查看权限要求,请参阅同目录的 API_PERMISSION_MATRIX.md(由 scripts/generate_permission_matrix.py 生成)。
角色宇宙
权威来源是 app/models/user.py:14-30 的 UserRole / ADMIN_ROLES / PERSISTABLE_ROLES,以及 app/constants/role_groups.py 的 ROLE_GROUPS。
| 角色 | 租户范围 | 可持久化? | 说明 |
|---|---|---|---|
platform_admin | 所有租户 | 是 | 平台管理员;可管理所有租户(遗留 admin 是否也算平台管理员取决于严格模式,见下文) |
tenant_admin | 仅本租户 | 是 | 租户管理员;必须有非空 tenant_id(数据库约束 chk_2332_tenant_admin_requires_tenant) |
admin(遗留) | 兼容用途的系统级 | 否 —— 不在 PERSISTABLE_ROLES 中;存量账户继续可用,但 create/update/restore 操作拒绝该值 | 为向后兼容保留的超级管理员(Issue #2286、#2332);应迁移为 platform_admin |
manager | — | 是 | 团队管理者:11 个权限(消息、分析、用户、配额、审计、内容过滤的查看与导出) |
user | — | 是 | 普通用户:4 个权限(view_dashboard、view_messages、view_analysis、view_quota) |
readonly | — | 是 | 仅仪表盘查看(view_dashboard) |
ADMIN_ROLES = frozenset({"admin", "platform_admin", "tenant_admin"}) 定义了整个代码库中"是管理员"的含义(User.is_admin、所有权检查绕过均基于它)。在统计展示时,角色折叠为三组(app/constants/role_groups.py):admin(admin/platform_admin/tenant_admin)、manager、user(user/readonly)。
内置 RBAC 权限矩阵(19 个权限,来自 app/services/permission_service.py:72-113 的 DEFAULT_ROLES):
| 权限 | admin | manager | user | readonly |
|---|---|---|---|---|
| view_dashboard | ✓ | ✓ | ✓ | ✓ |
| view_messages | ✓ | ✓ | ✓ | |
| export_messages | ✓ | ✓ | ||
| view_analysis | ✓ | ✓ | ✓ | |
| run_analysis | ✓ | ✓ | ||
| export_analysis | ✓ | ✓ | ||
| view_users | ✓ | ✓ | ||
| create_user / edit_user / delete_user | ✓ | |||
| manage_permissions | ✓ | |||
| view_quota | ✓ | ✓ | ✓ | |
| manage_quota | ✓ | |||
| view_audit_logs / export_audit_logs | ✓ | ✓ | ||
| view_content_filter | ✓ | ✓ | ||
| manage_content_filter | ✓ | |||
| admin_access / system_config | ✓ |
admin_access 是超级用户绕过——持有该权限的角色自动通过所有权限检查(Role.has_permission)。
注意:就装饰器判定而言,tenant_admin / platform_admin 属于管理员层级(满足 ADMIN_ROLES);上面的 DEFAULT_ROLES 矩阵覆盖非管理员层级,以及拥有全部 19 个权限的遗留 admin 条目。
认证装饰器(10 个)
除 machine_access_required / machine_admin_required(app/routes/remote.py:1059-1088)和 api_key_admin_required(app/auth/decorators.py:1984)外,其余定义都在 app/auth/decorators.py。使用计数为撰写本文时 app/ 下的调用点数量:
| 装饰器 | 使用数 | 一句话职责 |
|---|---|---|
@admin_required | 91 | 接受任意管理员角色(admin/platform_admin/tenant_admin),凭 session token 或 WebUI URL token 认证;已弃用(Issue #2332)——应迁移到 any_admin_required 或 platform_admin_required |
@auth_required | 51 | 要求有效登录;可选 ownership='session' 或 ownership='machine' 所有权检查,管理员角色绕过 |
@platform_admin_required | 25 | 按严格模式要求平台管理员;遗留 admin 被接受时会记录弃用告警 |
@same_tenant_or_platform_admin | 8 | 租户范围资源访问:资源所属租户的 tenant_admin,或任意 platform_admin(租户敏感词治理端点) |
@machine_access_required | 9 | 远程机器访问:系统管理员或被分配到该机器的用户 |
@same_tenant_user_required | 8 | 叠放在管理装饰器之下;将面向用户的管理端点(重置密码、更新、删除等)限制在操作者租户内,并防止租户管理员操作平台级账户 |
@machine_admin_required | 4 | 远程机器管理:系统管理员或该机器指定的管理员 |
@api_key_admin_required | 4 | API Key 管理专用授权(Issue #2327):解析经授权的目标租户并设置 g.actor_scope;见下文专节 |
@any_admin_required | 4 | 任意管理员角色,用于非跨租户操作(本租户内用户管理、租户中立配置、审计访问) |
@tenant_member_required | 1 | 管理员角色外加 manager,用于租户范围的告警端点(Issue #3082) |
另有两个不计入认证装饰器的标记:@public_endpoint 为安全扫描器标记有意公开的路由;@security_annotated(reason=...) 标记所有权检查以行内方式(而非装饰器)实现的端点。
对于接收 tenant_id 过滤或操作非用户资源的管理端点,装饰器会与同模块的辅助函数配合:resolve_tenant_scope() / require_tenant_scope()(非管理员被限制在租户范围内,无法解析出租户时直接拒绝——按 Issue #1775 fail closed)、enforce_requested_tenant_scope()、enforce_resource_tenant_scope() 和 enforce_target_user_tenant()。它们在租户 id 缺失或不一致时一律 fail closed,而不是悄悄放大范围。
平台管理员严格模式
OPENACE_PLATFORM_ADMIN_STRICT_MODE(app/auth/permissions.py:19-46)控制谁算平台管理员:
- 关闭(默认):
platform_admin和遗留admin都能通过is_platform_admin_role() - 开启:只有
platform_admin通过;遗留admin账户不再是平台管理员,访问平台管理端点会得到 403
运维规则:
- 该值在启动时读取一次并缓存整个进程生命周期——修改后必须重启。
create_app()会调用init_platform_admin_strict_mode(),确保模式在首个请求前冻结并记入日志 - 开启严格模式之前,先把所有
role='admin'账户迁移为platform_admin;warn_if_strict_mode_locks_out_legacy_admins()会在启动时大声警告严格模式将锁死遗留账户 - 该开关只影响经由
is_platform_admin_role()/User.is_platform_admin()的检查(platform_admin_required、same_tenant_or_platform_admin、resolve_admin_tenant_scope)。直接比较角色字符串的代码(如app/routes/mapping_rules.py)不受影响——开启开关收窄授权,但不会让遗留角色消失
迁移手册见 docs/dev-notes/2332-admin-role-migration-runbook.md。
认证
登录流程
- 客户端发送
POST /api/auth/login,包含{username, password} - 服务端验证凭证 —— bcrypt,12 轮(
app/routes/auth.py:72的hash_password(),admin.py/tenant.py相同) - 创建带过期时间的会话记录
- 设置
session_tokenCookie(HttpOnly,HTTPS 时为 Secure) - 返回含角色和
tenant_id的用户资料
速率限制
失败的登录尝试记录在 login_attempts 表中(锁定阈值来自安全设置,默认最多 5 次)。安全设置缓存 60 秒。
Token 提取
Session token 只从安全来源提取 —— cookie → Authorization: Bearer 头。按 Issue #1896,查询参数中的 session token 会被拒绝。少量路径前缀白名单(/api/admin/、/api/remote/terminal/、/api/remote/vscode/、/api/workspace/、/api/projects)接受特殊的 URL token(WebUI v1/v2、proxy、browser token),按格式校验并始终写审计日志。
强制改密
用户被标记 must_change_password=true 时,所有装饰器都会拦截请求,仅放行最小必要端点:认证检查、个人资料、修改密码、登出和密码策略。
路由保护
- 管理模式(
/manage/*)—— 所有管理页面要求管理员角色;普通用户和机器管理员无法访问 - API 路由 —— 大多数
/api/*blueprint 通过装饰器或before_request认证;敏感操作叠加上述管理层级装饰器 - 公开路由:
/—— SPA 全局捕获(提供 index.html)/api/auth/login—— 登录端点/api/auth/check—— 认证状态检查/livez、/readyz、/metrics、/security-status—— 探针与指标(/health已废弃,委托给/readyz)
自定义权限
在内置角色之外,可以按用户授予自定义权限:
PermissionService.grant_permission(user_id, 'export_analysis', granted_by=admin_id)
has_perm = PermissionService.has_permission(user_id, 'export_analysis')
自定义权限存储在 user_permissions 表中,与用户的角色权限合并计算。
多租户隔离
启用多租户模式时:
- 用户通过
tenant_id关联到租户 - 租户配额强制执行每个租户的 token 和请求限制;
QuotaEnforcementScheduler每 60 秒运行一次,终止超额会话并生成告警 - 租户感知边界覆盖用户、项目、工作区会话、会话消息、每日用量聚合、审计日志、远程机器、机器权限和配额
- 非管理员用户可见 API 会把认证租户范围应用到会话、项目、用量和审计查询
- 平台管理员有意保留全局运维可见性,用于支持和故障处理;每次跨租户访问都写审计日志(
ADMIN_CROSS_TENANT_ACCESS)
API Key 管理租户授权(Issue #2327)
API Key 管理接口的 tenant_id 参数是目标选择器,不是授权凭据。路由层守卫是 @api_key_admin_required,它调用 resolve_authorized_target_tenant(actor, requested_tenant_id):
- tenant_admin:目标租户始终来自认证上下文(
g.tenant_id);不提供tenant_id时使用 actor 租户(向后兼容),指定其他租户返回 403 - platform_admin / 遗留 admin:必须显式提供
tenant_id;缺失时返回 400(fail closed,不返回全局列表),跨租户操作被允许但写审计日志 PUT/DELETE /api/api-keys/<key_id>的所有权校验对目标租户之外的 key 返回 403(绝不返回 404),避免探测 key id
Service/Repository 层接收不可变的 ActorScope(由 @require_actor_scope() 校验),因此直接调用更深层也无法绕过授权。一切 fail closed:不回退默认租户、不静默忽略无效 tenant_id、不返回全局列表。
密钥存储、轮换与代理语义见 KEY_MANAGEMENT.md;完整的按端点权限表见 API_PERMISSION_MATRIX.md。