首页 产品 为什么选 OCTO 解决方案 文档 关于
文档中心 / 架构设计 / 权限模型与鉴权体系
← 返回文档中心

权限模型与鉴权体系

OCTO 文档中心 · 架构设计

  • 00-inbox/product-bot-answers/2026-09-22-P1-r2-Q10-权限模型与鉴权全景回答.md(源码确认级·22条确认结论)
  • octo-server/modules/space, modules/group, modules/oidc, modules/bot_api, modules/app_bot, pkg/authtree, pkg/auth, pkg/space

OCTO 权限模型与鉴权体系 v0.1

文档定位:跨模块架构文档(架构·安全·售前口径三合一)。所有售前/交付/API集成人员在回答客户权限/租户/SSO问题前必须先读本页顶部三条硬口径。

源码证据范围:`octo-server` 全仓(`modules/space`、`modules/group`、`modules/oidc`、`modules/bot_api`、`modules/app_bot`、`pkg/authtree`、`pkg/auth`、`pkg/space`),证据级别=源码确认级(confidence: high)。

关联文档:

- 02-architecture/OCTO总体架构.md(§授权层宏观描述,本页为权威细化)

- 02-architecture/安全权限与鉴权-v0.1.md(早期v0.1推断版,本页替代其核心结论,旧版待归档)

- 06-api-integration/Bot-API概览.md(Bot token使用侧)

- 01-product/核心能力概览.md(产品能力描述侧)


🔴🔴🔴 三条售前安全硬口径(必读 · 红色安全红线)

⚠️ 以下三条为源码级确认的安全红线,任何售前方案、POC承诺、客户答复、招标应答均不得违背。若客户需求明确要求相反能力(例如物理隔离、自定义RBAC、独立organization层级),必须走"产品定制/规划中能力"路径,不得口头承诺已支持。

🔴 硬口径①:无 organization 概念,租户边界唯一是 Space

Octo 源码里根本没有 "organization"(组织)作为一级租户实体。全仓 `org_id` 字段搜索结果为 0 命中。租户边界、成员管理、权限隔离的唯一锚点是 Space(团队空间)。

- Space = organization = workspace 三词同义,代码只使用 "Space" 一词。

- 售前/交付材料中若出现 "组织/organization",实际指向都是 Space。

- 不存在"组织下多Space"的层级,Space 就是最顶层租户单元。

源码证据:全仓 `grep -r "org_id" modules/ pkg/` 返回 0 命中;`modules/space/model.go` 定义 SpaceModel/MemberModel 为顶层实体。

🔴 硬口径②:不支持自定义 RBAC(角色硬编码枚举)

权限角色是代码硬编码枚举,没有"管理员自定义角色""细粒度权限点配置""可视化权限矩阵"等通用 RBAC 能力。

- `marketAdmin` 角色的代码注释明确写着 `"before general manager RBAC exists"`——通用 RBAC 系统尚不存在,是规划项而非已交付项。

- 客户若要求"自定义角色/自定义权限点/按岗位授权",必须走产品规划沟通,不得承诺当前版本支持。

源码证据:`pkg/auth/manager_roles.go`、`modules/user/role_service.go`、`modules/group/const.go` 角色枚举硬编码;`modules/market/...` 注释原文。

🔴 硬口径③:多租户是同库逻辑隔离(space_id 过滤),非物理隔离

Octo 的多租户是逻辑隔离:所有 Space 的数据存于同一个数据库实例、同一套表、同一套 OpenSearch 索引,通过 `space_id` 字段在查询层强制过滤实现隔离。

- ❌ 不是独立数据库 / 独立 Schema / 独立集群 / 物理隔离部署。

- ✅ 跨 Space 泄露由多层 middleware + handler + 搜索层 fail-closed guard 显式封堵(历史漏洞 PR#713 已修复,见§四)。

- 客户若有"金融/政企强隔离"合规需求,必须采用"一客户一部署实例"方案(独立数据库+独立服务+独立OpenSearch),不是产品内置能力。

源码证据:`pkg/space/middleware.go`、`modules/messages_search/space_scope.go`(强制 term filter + fail-closed)、`pkg/authtree/authtree.go`。


§一、总览:用户-空间-群数据模型

1.1 实体层级关系

┌─────────────────────────────────────────────────────────────────┐
│  系统平台(全局)                                                  │
│   - 全局唯一 superAdmin/admin/dashboardReader/marketAdmin        │
│   - uk_* API Key(可绑定单一Space)                                │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐         ┌──────────────┐     │
│  │   Space A    │  │   Space B    │  ...    │   Space N    │     │
│  │ (租户隔离单元)│  │ (租户隔离单元)│         │ (租户隔离单元)│     │
│  │              │  │              │         │              │     │
│  │ ┌──────────┐ │  │ ┌──────────┐ │         │ User × Space │     │
│  │ │ Group G1 │ │  │ │ Group G3 │ │         │ = 多对多     │     │
│  │ │ (默认1:1 │ │  │ │ (默认1:1)│ │         │ space_member │     │
│  │ │ 归属Space)│ │  │ │          │ │         │ (uid,space_id)│    │
│  │ └──────────┘ │  │ └──────────┘ │         │              │     │
│  │ ┌──────────┐ │  │              │         │              │     │
│  │ │ Group G2 │ │  │  外部群:      │         │              │     │
│  │ │is_external│ │  │ is_external_ │         │              │     │
│  │ │ _group=1 │ │  │ group=1 时    │         │              │     │
│  │ │(跨Space)  │ │  │ 跨Space成员   │         │              │     │
│  │ └──────────┘ │  │ source_space_id│        │              │     │
│  └──────────────┘  └──────────────┘         └──────────────┘     │
│                                                                  │
│  P2P 私聊:不归属任何 Space(直接 User ↔ User / Bot)               │
└─────────────────────────────────────────────────────────────────┘

1.2 核心关系规则(源码确认)

关系 规则 源码位置
User ⇄ Space 多对多,通过 space_member 关联,唯一键 (space_id, uid)(非 uid) modules/space/model.go MemberModel
Group → Space 默认 1:1 强归属,group.space_id NOT NULL;建群/加人强制校验 Space 成员资格 modules/group/sql/*_group_legacy01.sql、docs/external-group-design.md v1.1、PR#1167
P2P 私聊 不绑定 Space(1:1 单聊没有 space_id,是独立通道) modules/group/ p2p 分支
跨 Space 协作 走"外部群"机制:邀请外部 Space 用户入群时,群标 is_external_group=1,外部成员标 source_space_id;跨第三方 Space 邀请天然不可能(通讯录隔离) docs/external-group-design.md
Bot User Bot(bf_)/App Bot(app_) 均无 space_member 行(无 Space 座位),通过 token 作用域+authtree 路由守卫获得访问权(详见§三) modules/bot_api/auth.go、modules/app_bot/app_bot.go

1.3 关键推论

  • 一个用户可同时属于多个 Space(例如员工同时在公司Space和客户协作外部群所属Space),但每个Space下的身份/角色独立。
  • Group 是 Space 内的沟通单元,默认不跨 Space;跨 Space 场景必须显式启用外部群并打标。
  • P2P 私聊跳出 Space 边界,属于用户层直连,不参与 Space 成员过滤(但仍受用户层面的好友/黑名单/策略控制)。

§二、角色与权限体系

2.1 三套独立角色

① Space 成员角色(租户内身份)
值 角色 典型权限
0 普通成员 (member) 日常消息、文件、建群(受Space策略)
1 管理员 (admin) 成员管理、Space设置、部分管理操作
2 拥有者 (owner) Space所有权、最高权限、转让/解散
② 群成员角色(频道/群内身份)
值 角色 典型权限
0 普通群成员 收发消息、@人、文件
1 群主 (owner) 群设置、成员管理、解散群
2 管理者 (admin) 协助群主管理(加人/踢人/公告等)
— bot_admin 标志 指定群内Bot管理权限(单独布尔位,非role值)
③ 系统平台角色(全局身份)
值 角色 说明
admin 平台管理员 平台级管理权限
superAdmin 超级管理员 最高系统权限
dashboardReader 看板只读 受限角色:仅能读数据看板
marketAdmin 市场发布 受限角色:仅能发布到技能/插件市场。源码注释:"before general manager RBAC exists"(通用RBAC尚不存在)

2.2 ❌ 不支持自定义 RBAC

  • 以上三套角色均为代码硬编码枚举。
  • 没有角色自定义、权限点粒度拆分、可视化权限矩阵、按岗位授权等能力。
  • 售前/交付遇到"自定义角色""精细到API/功能点授权"类需求,必须标记为规划项,不得声称已支持。
  • marketAdmin 注释 "before general manager RBAC exists" 是源码级明证——通用 RBAC 是未来规划,当前未交付。

2.3 鉴权执行:middleware + handler 双层检查

HTTP 请求
  ↓
[1] 认证层 (pkg/auth)
    - 解析 token(user_token / bot_token / app_token / uk_* API Key / obo_*)
    - 解析系统角色(token快照 + Redis `user_role:{uid}` TTL 60s)
  ↓
[2] Space 中间件 (pkg/space/middleware.go → SpaceMiddleware)
    - 从请求路径/参数提取 space_id
    - 查 Redis 成员缓存(正向命中60s / 否定命中30s)
    - 未命中则回源 DB space_member 表
    - 非成员→直接拒绝(anti-enumeration:统一返NOT_FOUND,见§四)
  ↓
[3] Handler 层细粒度检查
    - Space owner/admin/member 具体操作级检查(散在各handler)
    - 群角色(群主/管理员/普通/bot_admin)检查
    - Bot authtree 路由级守卫(见§三)
  ↓
业务逻辑执行

2.4 缓存策略

缓存对象 Redis Key TTL 位置
Space 成员资格(正向) SpaceMiddleware 内部缓存 60s pkg/space/middleware.go
Space 成员资格(否定/非成员) SpaceMiddleware 内部缓存 30s pkg/space/middleware.go
用户系统角色 user_role:{uid} 60s pkg/auth/manager_roles.go
Bot Token tombstone(吊销) tombstone 缓存 短TTL modules/bot_api/

⚠️ 潜在风险:权限变更最长有 60s 传播延迟(移除某人管理员权限后,最坏情况下60s内仍可能执行管理操作)。高安全场景需评估。


§三、🔴 Bot 权限边界(安全章节)

🔴 售前必知:Bot 不是普通用户,不占 Space 座位,权限模型与人类用户根本不同。错误配置 Bot token 权限是最常见的集成安全事故源。

3.1 Bot 两类身份对照

维度 User Bot(用户机器人) App Bot(应用机器人)
Token 前缀 bf_ app_
存储表 robot 表 app_bot 表
创建入口 BotFather App管理后台/API
典型用途 群内助手、技能专家、工作流Bot 企业自建应用、OBO调用、第三方集成
Scope 粒度 受群成员资格控制 platform(全平台)/space(单Space)两档粗粒度
Space 座位(space_member行) ❌ 无 ❌ 无
群端点访问 ✅ 受active成员资格门控 ❌ 完全拒绝群端点,仅限DM
DM端点访问 ✅ ✅
OBO(on-behalf-of)支持 有限 ✅ obo_* token

3.2 Bot 关键安全规则(源码确认)

  1. Bot 无 `space_member` 行——Bot 不占 Space 座位,不占用"人头数",不通过 Space 成员表鉴权;Bot 在 Space 的存在/权限通过 token scope + authtree 路由守卫单独判定。
  1. App Bot 完全拒绝群端点——`pkg/authtree/authtree.go` TreeBotToken 分支对 App Bot 调用群相关 API 直接拒绝访问(返回权限错误),App Bot 只能用 DM(私聊)通道。售前若承诺"App Bot 群聊能力"是错误的。
  1. User Bot 受 active 群成员资格门控——User Bot 若要读/发某群消息,必须是该群的 active 成员(被拉入群且未被踢出)。被踢出后立即失去该群访问权(tombstone缓存短TTL内收敛)。
  1. 新成员看历史消息受 `group.allow_view_history_msg` 开关控制——群级别开关,决定新加入成员(含Bot)是否能看到入群前的历史消息。
  1. 文件URL签名安全:
  • 下载URL:使用 ScopeUnscoped(对调用方提供的 key 签名),用于已授权后访问对象存储。
  • 上传(presigned POST/PUT):ScopeRouteGuard 拒绝调用方自定义 object key——key 由服务端铸造(防路径穿越/覆盖/越权写)。
  • 源码:modules/bot_api/space_principal.go 签名scope分支。
  1. ⚠️ 无标准 OAuth2 scope 体系:
  • App Bot 只有 platform / space 两档粗粒度,没有"只读消息""只发消息""管文件不管用户"这类细粒度scope。
  • OBO(on-behalf-of)token(obo_*前缀)代表某用户执行,权限等同于该用户+应用授权上下文,但也不是标准scope矩阵。
  • 售前若被问"是否支持OAuth2细粒度scope授权"——答案是不支持标准scope体系,仅有platform/space粗粒度+OBO。

3.3 Bot 权限配置待确认项

  • 群主/管理员 UI 侧"只发不读历史"开关是否暴露给 Bot 配置?(后端有allow_view_history_msg但UI入口未确认)
  • User Bot 能否主动退群/被禁言?
  • App Bot DM 对象范围(全平台任意用户/仅同Space)边界未逐端点验证。

§四、🔴 数据隔离机制

🔴 售前/交付必读:Octo 多租户不是物理隔离。所有隔离保证均来自代码层 guard,任何 guard 漏洞=跨租户数据泄露。本章节说明隔离机制及历史漏洞修复情况。

4.1 隔离模型:逻辑隔离(同库 + space_id 过滤)

维度 隔离方式 说明
数据库 同库同表 所有Space数据共用MySQL/PostgreSQL实例,业务表携带space_id字段
对象存储 同bucket(默认) MinIO/S3按bucket划分业务域,不按Space分bucket;单bucket内按key前缀区分
消息队列 同topic 消息投递不按Space物理隔离
OpenSearch 同索引 所有Space消息/文件在同一索引,查询强制注入space_id term filter
缓存(Redis) 同实例 key中编码space_id/uid做逻辑区分

🔴 强合规场景(金融/政企/涉密) 必须采用"一客户一部署"方案:独立数据库实例 + 独立 OpenSearch 集群 + 独立 MinIO bucket(或独立MinIO)+ 独立服务副本。不要在同实例内承诺"强物理隔离"。

4.2 多层隔离Guard(fail-closed)

  1. SpaceMiddleware 边界守卫(`pkg/space/middleware.go`)
  • 所有携带 space_id 的请求必经中间件;
  • 未通过成员资格校验直接中断。
  1. OpenSearch 强制 term filter(`modules/messages_search/space_scope.go`)
  • 搜索请求强制注入 payload.space_id term filter;
  • 环境变量 OCTO_SEARCH_REQUIRE_SPACE_ID 开启 fail-closed(未带space_id直接拒查);
  • 这是防数据泄露的最后一道关键防线——即便上层遗漏鉴权,搜索层也不会返回其他Space数据。
  1. Anti-enumeration(防枚举)(`authz.go`)
  • 非成员访问任何Space内资源(查群/查用户/查消息)统一返回 NOT_FOUND(404)而不是 FORBIDDEN(403);
  • 防止攻击者通过"403 vs 404"差异枚举Space/群/用户的存在性。
  1. API Key 绑定 Space(`uk_*`前缀)
  • uk_* API Key 创建时冻结绑定单一Space;
  • 使用该Key的所有请求自动限定在该Space内,无法访问其他Space。
  1. Authtree 路由守卫(`pkg/authtree/authtree.go`)
  • Bot/App Bot 请求走树形路由权限判定;
  • 注释明确:"Space remains the only security boundary"。

4.3 🔴 历史修复记录:PR#713 跨租户读漏洞

⚠️ 历史安全事件,用于向客户说明Octo团队的漏洞响应能力及当前状态。

  • 问题:PR#713 代码审计发现 4 处跨租户读取漏洞——/users/:uid 端点的 group_no 查询参数未校验调用者是否在该 group 所属 Space 内,导致可通过构造参数读取其他Space的群信息。
  • 修复:4处漏洞已逐条pin(加space_id断言)/strip(移除非必要跨Space查询路径),修复后authtree.go新增注释明确Space为唯一安全边界。
  • 当前状态:已修复并入主干;本知识基于修复后版本源码确认。
  • 交付说明:部署时必须使用包含PR#713修复的版本(v2026.09之后版本均含),不要部署过旧版本。

4.4 隔离机制已知局限

项 状态 说明
group/thread 搜索 部分确认 group/thread 搜索靠 channel_id 内编码Space(前缀/嵌入),非独立 space_id term;未逐行验证所有搜索路径
OpenSearch mapping 待确认 payload.space_id 字段索引定义文件未逐行读到(由 space_scope.go 反证存在且被强制注入)
文件URL签名 已确认 下载ScopeUnscoped、上传服务端铸造key,无路径穿越风险
跨Space邀请 已确认 通讯录隔离天然阻止跨第三方Space邀请

§五、SSO / 身份集成

5.1 已支持协议(源码确认)

协议 Kind 常量 说明
OIDC(OpenID Connect) KindOIDC 标准 OIDC:Discovery(.well-known/openid-configuration)+ id_token 验证 + JWKS 动态密钥拉取
OAuth2(纯授权码) KindOAuth2 标准 OAuth2 authorization_code 流程(无OIDC id_token时可用)
  • 内置对接 Aegis(明略自研 IdP),通过 DM_OIDC_AEGIS_* 环境变量配置。
  • 协议层不绑厂商,可对接任意标准 OIDC Provider:Google / Okta / Keycloak / Azure AD(通用OIDC档)/ Auth0 / 自研IdP 等。
  • 自动关联:支持 AutoLinkByEmail / AutoLinkByPhone 通过邮箱/手机号自动绑定现有账号。
  • 新用户控制:AllowNewUser 开关控制SSO登录时是否自动创建新用户。
DM_OIDC_ENABLED=true/false
DM_OIDC_PROVIDER_ISSUER=
DM_OIDC_PROVIDER_CLIENT_ID=
DM_OIDC_PROVIDER_CLIENT_SECRET=
DM_OIDC_PROVIDER_REDIRECT_URI=
DM_OIDC_PROVIDER_SCOPES=openid,profile,email
DM_OIDC_PROVIDER_KIND=oidc|oauth2
DM_OIDC_AEGIS_*=

5.2 ❌ 明确不支持(售前硬口径)

能力 状态 说明
SAML ❌ 不支持 SAML 仅在文档/测试中有对比性提及,无生产实现;客户要求SAML必须走定制/IdP代理(如用Keycloak做SAML→OIDC桥接)
LDAP / AD 直连 ❌ 不支持 无LDAP bind/查询代码;需用LDAP→OIDC桥接(如Keycloak LDAP federation)
企业微信扫码 ❌ 独立集成不支持 scanlogin_* 是Octo自有IM扫码登录(移动端App扫Web码),不是企业微信/钉钉/飞书扫码
钉钉扫码 ❌ 独立集成不支持 同上
飞书扫码 ❌ 独立集成不支持 同上
SCIM 自动provisioning/deprovisioning ❌ 不支持 SCIM 仅1处设计对比文档提及,无实现;sync_worker.go 只做:① refresh_token 轮转 ② 失败吊销+踢线 ③ 实名claims 同步,不做账号生命周期自动创建/禁用/删除
JIT Provisioning 细粒度 部分 仅有 AllowNewUser 开关+email/phone自动绑定,无基于claims的角色/部门映射

5.3 SSO 集成建议方案

客户IdP(SAML/LDAP/企微/钉钉/飞书)
        │
        ▼
┌─────────────────┐   标准OIDC   ┌──────────────┐
│   桥接IdP        │─────────────▶│    Octo      │
│ (Keycloak/Auth0/ │              │ modules/oidc │
│  Aegis/自研)     │◀─────────────│              │
└─────────────────┘   JWKS/      └──────────────┘
        ▲           userinfo
        │
        └── 可在桥接层做:SAML/LDAP→OIDC、SCIM provisioning、
            企微/钉钉/飞书OAuth映射、部门/角色claims转换

售前口径:Octo 原生支持标准 OIDC/OAuth2 SSO;对接 SAML/LDAP/企微/钉钉/飞书等需通过 Keycloak 等标准IdP做协议桥接,Octo 团队可提供桥接方案但不承诺原生开箱即用。


§六、待确认项(to-follow-up)

以下问题在本次源码确认中未得到完整答案,列入后续复核清单,不得在客户面前做确定性表述。

编号 待确认项 优先级 复核方式
AUTH-01 三套角色(Space/Group/系统)对每个REST端点的完整权限矩阵(当前散在各handler,无集中定义) P1 逐handler梳理+生成权限矩阵文档
AUTH-02 群主/管理员 UI 是否暴露"Bot只发不读历史"开关(后端 allow_view_history_msg 字段已存在但UI入口未确认) P2 Web前端代码走查+产品管家确认
AUTH-03 OpenSearch mapping 中 payload.space_id 字段的索引定义、analyzer、routing策略(由space_scope.go反证存在但mapping文件未逐行读) P2 查 octo-search-indexer mapping JSON + 索引模板
AUTH-04 group/thread 搜索路径通过 channel_id 编码 Space 的具体机制(前缀位宽/碰撞风险/跨Space拼接攻击面) P2 查 messages_search 模块 channel_id 解析逻辑
AUTH-05 App Bot DM 对象范围边界:能否对平台任意用户发DM?是否限同Space? P2 逐endpoint验证+集成测试
AUTH-06 权限变更缓存60s延迟是否有"强制失效"管理API(踢人/降权时即时收敛) P2 查 Redis 缓存失效逻辑
AUTH-07 User Bot 主动退群/被禁言/被永久封禁的token生命周期 P3 bot_api 模块 state machine

§七、版本记录

版本 日期 变更说明 作者
v0.1 2026-09-22 初版。基于Q10源码确认级回答(22条确认结论),建立跨模块权限/鉴权/隔离/SSO全景文档。含三条售前安全硬口径、Bot安全边界、数据隔离机制与PR#713历史修复、SSO支持矩阵、7项待确认清单。 twb-knowledge-octo

附录A:源码证据索引

结论域 核心文件
Space模型/成员 modules/space/model.go
Space中间件/缓存 pkg/space/middleware.go
群模型/角色/外部群 modules/group/const.go、modules/group/sql/*_group_legacy01.sql、docs/external-group-design.md
系统角色 pkg/auth/manager_roles.go、modules/user/role_service.go
Bot认证/Principal modules/bot_api/auth.go、modules/bot_api/space_principal.go
App Bot modules/app_bot/app_bot.go
路由级Authtree守卫 pkg/authtree/authtree.go
OpenSearch space_id强制过滤 modules/messages_search/space_scope.go、modules/messages_search/authz.go
反枚举 各模块 authz.go
OIDC/OAuth2 SSO modules/oidc/{provider,provider_factory,config,sync_worker}.go
PR#713 跨租户漏洞修复 pkg/authtree/authtree.go 注释 + git history

附录B:术语对照

术语 含义 易混点
Space 租户隔离唯一单元 = organization = workspace 不是"组织下的子空间"
Group 群/频道,默认归属一个Space 不是"用户组(权限组)"
User Bot (bf_) 群聊Bot,需被拉入群才能访问群 不是App Bot
App Bot (app_) 应用级Bot,DM-only,有platform/space两档scope 不能进群
OBO (obo_*) on-behalf-of,代表用户调用的token 不是独立Bot类型
uk_* API Key,冻结绑定单一Space 不是user token
is_external_group 外部群标志(跨Space协作) 不是"外部分享链接群"
allow_view_history_msg 新成员是否看入群前历史 群级别开关
ScopeUnscoped 文件下载URL签名模式(对caller-named key签名) 不是上传用
ScopeRouteGuard 上传presigned签名守卫(服务端铸造key) 防路径穿越