RAGFlow 0.25.6 独立权限代理服务设计

五级角色 · 四种知识库共享模式 · 配额管控 · 版本审批 —— 零侵入扩展方案
不改 RAGFlow 原始逻辑 不改原始表结构 独立 Sidecar 代理 面向金融合规场景

01设计目标与约束

在不触碰 RAGFlow 任何一张表、任何一行业务逻辑的前提下,外挂一个独立的权限代理服务,把企业级的细粒度权限模型「翻译」成 RAGFlow 能理解的原生原语。

✅ 必须满足(功能目标)

  • 五级角色体系:系统管理员 → 团队管理员 → 编辑者 → 检索用户 → 系统应用
  • 知识库四种共享模式:创建者私有 / 机构自动共享 / 指定用户共享 / 团队共享
  • 配额管控:KB 数量、文档上传量、API 调用频率、模型白名单
  • 版本审批:草稿 → 待审批 → 已发布 → 历史

🔒 硬约束(非功能目标)

  • RAGFlow 源码 零修改,可随官方版本平滑升级
  • RAGFlow 数据库表 不增字段、不改约束
  • 新增权限元数据全部落在 独立数据库
  • 代理对前端/SDK 透明,沿用 RAGFlow 原 Token 体系
💡 核心设计哲学
RAGFlow 的权限原语极度精简——知识库只有 me(私有)和 team(团队可见)两种状态,租户内只有 owner/admin/normal/invite 四种角色。我们不去增强它,而是在它外面包一层:所有细粒度判断在代理层完成,最终只向 RAGFlow 投影出它原生支持的状态。这样 RAGFlow 永远只看到「合法的简单世界」。

02RAGFlow 原生权限模型源码分析

先把 RAGFlow 现状摸清——这是「不改原逻辑」的前提。以下均为 0.25.6 实际源码。

2.1 角色枚举:仅租户级四角色

api/db/__init__.py:21
class UserTenantRole(StrEnum):
    OWNER  = 'owner'    # 租户所有者(创建者)
    ADMIN  = 'admin'    # 租户管理员
    NORMAL = 'normal'   # 普通成员
    INVITE = 'invite'   # 已邀请未接受

class TenantPermission(StrEnum):
    ME   = 'me'         # 仅自己可见
    TEAM = 'team'       # 团队可见
⚠️ 关键观察
没有「机构」概念,没有「编辑者/检索用户」之分,没有「指定用户共享」。一个 RAGFlow「租户(Tenant)」本质等同于一个「个人工作区 + 其邀请的成员」。租户 ID = 创建用户 ID(注册时 1:1 生成)。

2.2 数据表:权限相关字段

关键字段含义源码位置
useraccess_token, is_superuser登录令牌;是否 rootdb_models.py:711,725
tenantid, credit租户=工作区;credit 是唯一的额度字段db_models.py:750,767
user_tenantuser_id,tenant_id,role用户-租户多对多 + 角色db_models.py:774-778
knowledgebasetenant_id,created_by,permission归属租户/创建者/可见性(me|team)db_models.py:869-876
api_tokentenant_id,token,dialog_idAPI Key,按租户发放db_models.py:1024-1033
api/db/db_models.py:875
permission = CharField(max_length=16, null=False, help_text="me|team",
                       default="me", index=True)   # ← 知识库可见性,仅二态

2.3 知识库可见性的实际判定逻辑

这是 RAGFlow 决定「谁能看到哪个 KB」的全部逻辑——只有两条规则:① 是我建的;② 团队共享且我在该租户里。

api/db/services/knowledgebase_service.py:52 / 485
# 列表过滤(构造 Peewee 查询条件)
def _visibility_and_status_filter(cls, joined_tenant_ids, user_id):
    return (
        ( (cls.model.tenant_id.in_(joined_tenant_ids)
              & (cls.model.permission == TenantPermission.TEAM.value))   # 团队共享
          | (cls.model.tenant_id == user_id) )                          # 自己的工作区
        & (cls.model.status == StatusEnum.VALID.value)
    )

# 单个 KB 访问判定
def accessible(cls, kb_id, user_id):
    e, kb = cls.get_by_id(kb_id)
    if kb.tenant_id == user_id:                  return True   # 创建者
    if kb.permission != TenantPermission.TEAM.value: return False
    joined = TenantService.get_joined_tenants_by_user_id(user_id)
    return any(t["tenant_id"] == kb.tenant_id for t in joined)  # 同团队 + team 可见
🚫 原生能力天花板
无法表达「这个 KB 只共享给张三、李四」,也无法表达「北京分行所有人自动可见,但上海分行不可见」。permission=team全租户广播,粒度太粗——这正是代理层要补的核心缺口。

2.4 鉴权入口:双令牌加载

RAGFlow 所有请求经 _load_user 解析身份,它同时支持 JWT(access_token) 和 APIToken 两条路径——这是代理服务最理想的「挂载缝隙」。

api/apps/__init__.py:129
def _load_user():
    authorization = request.headers.get("Authorization")
    # 路径①:JWT 解出 access_token → 查 user 表
    access_token = str(jwt.loads(auth_token))
    user = UserService.query(access_token=access_token, status=VALID)
    ...
    # 路径②:当作 APIToken → 用 tenant_id 反查 user(系统应用走这里)
    objs = APIToken.query(token=auth_token)
    user = UserService.query(id=objs[0].tenant_id, status=VALID)
    g.auth_via_api_token = True

2.5 唯一的原生配额:环境变量级文件数上限

api/db/services/document_service.py:117
MAX_FILE_NUM_PER_USER = int(os.environ.get("MAX_FILE_NUM_PER_USER", 0))
if 0 < MAX_FILE_NUM_PER_USER <= DocumentService.get_doc_count(tenant_id):
    raise RuntimeError("Exceed the maximum file number of a free user!")

全局一刀切,无法按角色/机构/KB 分级——配额管控需要在代理层重做。

03差距分析:原生能力 vs 需求

需求RAGFlow 原生差距代理层补法
五级角色租户内 4 角色,无业务分级代理维护角色表,做请求级 RBAC
机构/团队管理无机构概念代理建机构树(总行→分行→支行) + 成员关系
创建者私有访问permission=me直接复用
团队共享permission=team直接复用(全租户广播)
机构自动共享代理 KB-机构绑定,访问时动态判定
指定用户共享代理 KB-用户 ACL 表
KB 数量上限代理在「创建 KB」请求前置校验
文档上传限额仅全局环境变量代理按主体维度计数拦截
API 频率限制代理层令牌桶/滑动窗口
模型白名单代理拦截 chat/completion 校验 model
版本审批流无(仅 Agent 有 canvas version)代理建审批状态机 + 发布闸门

04总体架构:代理服务的位置

代理服务作为反向代理 + 策略决策点(PDP/PEP)置于客户端与 RAGFlow 之间。所有流量先过代理,鉴权/配额/审批判定后再转发给原生 RAGFlow。

┌────────────┐   ①携带代理签发的Token    ┌──────────────────────────────┐
│  前端 / SDK │ ─────────────────────────► │   权限代理服务 (Auth Proxy)    │
│  系统应用   │                            │  ┌────────────────────────┐  │
└────────────┘                            │  │ PEP 拦截器 (Gateway)    │  │
                                          │  │  · 身份认证             │  │
       ┌──────────────────────────────┐  │  │  · 五级RBAC判定         │  │
       │   独立权限数据库 (proxy_db)    │◄─┤  │  · KB四模式ACL判定      │  │
       │  role / org / kb_acl          │  │  │  · 配额/频率校验        │  │
       │  quota / approval / audit_log │  │  │  · 模型白名单           │  │
       └──────────────────────────────┘  │  │  · 版本审批闸门         │  │
                                          │  └───────────┬────────────┘  │
                                          └──────────────┼───────────────┘
                                          ②注入RAGFlow原生Token, 透传
                                                         ▼
                                          ┌──────────────────────────────┐
                                          │   RAGFlow 0.25.6 (原封不动)    │
                                          │   api/apps/*  +  原数据库      │
                                          └──────────────────────────────┘

PEP(策略执行点)

网关拦截器,对每个请求做「放行 / 拒绝 / 改写」三态决策。

PDP(策略决策点)

查 proxy_db 计算有效权限,输出决策,不接触 RAGFlow 库。

Token Broker

用代理身份换取/缓存 RAGFlow 原生 access_token / APIToken。

✅ 为什么这样不破坏原逻辑
代理向 RAGFlow 发的每个请求,都带合法的原生 Token、访问的都是 permission 已被代理正确投影过的 KB。RAGFlow 侧的 accessible() 判定照常运行且永远通过——代理只是提前在外层做了更严格的过滤,从不放宽 RAGFlow 的原生限制。

05五级角色体系设计

在代理层定义五级角色,每级是上一级权限的子集。角色存于代理库,与 RAGFlow 的 owner/normal 解耦。

1

系统管理员 SYSTEM_ADMIN

全局配置:管理所有机构、所有角色、全局配额策略、模型白名单、审批策略。对应可(但不强制)映射到 RAGFlow is_superuser=True 的账号。
2

团队管理员 TEAM_ADMIN

机构管理:管理本机构成员、分配编辑者/检索用户角色、设置机构内配额、审批本机构 KB 版本发布。RAGFlow 侧映射为对应租户的 admin/owner
3

编辑者 EDITOR

知识库创建/编辑:建 KB、传文档、改解析配置、发起版本审批。受 KB 数量与上传配额约束。RAGFlow 侧为 normal 成员且具创建权。
4

普通检索用户 RETRIEVER

仅检索/对话:只读访问被授权的 KB,发起 chat/retrieval。代理拦截所有写操作。RAGFlow 侧为 normal 成员但代理屏蔽写接口。
5

系统应用 SERVICE_APP

API Key 独立访问:机器身份,无人机交互。绑定一组允许的 KB + 调用频率 + 模型白名单。RAGFlow 侧走 api_token 路径(g.auth_via_api_token=True)。

5.1 角色 → RAGFlow 原生身份映射表

代理角色能力范围RAGFlow 原生载体写操作
系统管理员全局is_superuser 账号 / 专用运维租户全部
团队管理员本机构租户 role=owner/admin本机构全部
编辑者授权 KB租户 role=normal + JWT建/编 KB、传文档
检索用户授权 KB 只读租户 role=normal + JWT❌ 代理拦截
系统应用绑定 KBapi_token按配置(通常只读检索)
💡 关键:写权限不靠 RAGFlow 区分
RAGFlow 不区分「编辑者」和「检索用户」——两者在原生侧都是 normal。区分发生在代理的 PEP 层:检索用户的请求若命中写接口(如 POST /v1/kb/create/v1/document/upload),代理直接 403,根本不转发给 RAGFlow。

06知识库四种访问控制模式

代理在 RAGFlow 的 me|team 二态之上,叠加四种语义化模式。每次 KB 访问由代理先算「有效可见集」,再决定是否转发。

模式语义在代理层 RAGFlow 侧最终投影
模式语义代理实现RAGFlow 投影
① 创建者直接访问 仅创建者可见可改 kb_acl 默认仅 owner 记录 permission=me(原生天然支持)
② 机构自动共享 本机构及上级机构成员自动可见
支行 KB → 二级分行 → 一级分行 → 总行 逐级可见
kb 绑定 org_id;访问者机构匹配则放行 me + 代理动态授权
③ 指定用户共享 仅授权名单可见 kb_acl 显式 (kb_id,user_id) 记录 me + 代理动态授权
④ 团队共享 全租户成员可见 无需额外 ACL permission=team(原生天然支持)

★ 模式②③的关键技巧见下方 6.2「动态授权」。

6.1 有效可见集计算(代理 PDP 伪代码)

def visible_kb_ids(subject):
    ids = set()
    # ① 创建者
    ids |= proxy_db.kb.where(created_by == subject.user_id)
    # ② 机构自动共享:主体所属机构(含上级分行/总行继承)绑定的 KB
    org_ids = org_tree.ancestors_and_self(subject.org_id)   # 支行→分行→总行
    ids |= proxy_db.kb_share.where(mode=='ORG', org_id in org_ids)
    # ③ 指定用户共享
    ids |= proxy_db.kb_acl.where(user_id == subject.user_id)
    # ④ 团队共享(直接信任 RAGFlow 原生 team)
    ids |= proxy_db.kb.where(mode=='TEAM', tenant_id in subject.joined_tenants)
    return ids - revoked(subject)        # 减去已撤销/审批未通过

6.2 模式②③如何在「不改 team 广播」下实现精准共享

⚠️ 难点
RAGFlow 的 team 是全租户广播,无法表达「只给部分人」。若把模式②③也设成 team,会泄露给整个租户。

方案:KB 保持 permission=me(归属"机构服务账号"租户),代理用 Token Broker 动态代理访问。

模式②③的 KB 创建在一个机构级"服务租户"名下,permission=me,对其他普通用户原生不可见——杜绝越权。
用户请求该 KB 时,代理先查 proxy_db 确认其在 dept 共享范围 / ACL 名单内。
通过后,代理用 服务租户的 access_token(Token Broker 持有)转发请求——RAGFlow 视角下是「服务租户访问自己的 me KB」,accessible() 自然通过。
代理把响应里属于该 KB 的内容回传给真实用户。真实用户从未直接持有该 KB 的访问权——授权完全由代理掌控、可随时撤销。
✅ 效果
精准到「人」的共享,却完全没改 RAGFlow 的 me|team 语义。撤销共享 = 删 proxy_db 一条 ACL,即时生效,无需触碰 RAGFlow。

07配额管控设计

四类配额均在代理 PEP 层「请求前置」校验,超限直接拒绝,不转发。计数维度可按用户/机构/系统应用配置。

① 知识库数量上限

拦截 POST /v1/kb/create。校验 count(proxy_db.kb where created_by=subject) < quota.max_kb。超限 403。

② 文档上传限额

拦截 /v1/document/upload。代理自维护 doc 计数(避免依赖 RAGFlow 全局 MAX_FILE_NUM_PER_USER)。支持按 KB / 按主体 / 按文件大小三种限额。

③ API 调用频率

滑动窗口 / 令牌桶(Redis)。Key = service_app_id 或 user_id + 接口组。超频返回 429。专门约束系统应用。

④ 模型白名单

拦截 chat/completion/retrieval 类请求,解析 body 中的 llm_id / model,比对主体白名单。非白名单模型 403。

7.1 配额校验流程

def enforce_quota(subject, action, payload):
    q = proxy_db.quota_policy.resolve(subject)     # 角色/机构/个体逐级覆盖
    match action:
      case "kb.create":
          assert proxy_db.count_kb(subject) < q.max_kb,            DENY(403)
      case "doc.upload":
          assert proxy_db.count_doc(subject) + payload.n <= q.max_doc, DENY(403)
          assert payload.size <= q.max_file_size,                  DENY(413)
      case "api.call":
          assert rate_limiter.allow(subject.id, q.qps),            DENY(429)
      case "chat.completion":
          assert payload.model in q.model_whitelist,               DENY(403)
    return ALLOW
💡 配额策略解析顺序
个体配额 > 机构配额 > 角色默认配额 > 全局默认。逐级覆盖(override),未配置则向上回退,便于金融场景「先粗后细」的分级授信管理。

08版本审批流程

面向金融合规——KB 内容发布须经审批留痕。代理维护版本状态机;只有「已发布」版本对检索用户/系统应用可见。

草稿 DRAFT 待审批 PENDING 已发布 PUBLISHED 历史 ARCHIVED

8.1 状态机与角色权限

转换触发者动作代理行为
— → 草稿编辑者创建/修改 KB 内容所有上传/解析落在「草稿区」标记,检索用户不可见
草稿 → 待审批编辑者提交审批冻结草稿快照,生成审批单,通知团队管理员
待审批 → 已发布团队/系统管理员批准打 PUBLISHED 标记;该版本进入检索可见集
待审批 → 草稿团队/系统管理员驳回退回编辑者,记录驳回意见
已发布 → 历史系统新版本发布时旧版本归档,保留审计回溯

8.2 「发布闸门」如何在不改 RAGFlow 下生效

⚠️ 难点
RAGFlow 文档一旦解析入库(向量化),检索就能命中——没有「未发布则不可检索」的原生开关。

方案:双 KB 影子机制(草稿 KB / 发布 KB)。

每个逻辑知识库在 RAGFlow 实际对应两个物理 KB{name}__draft{name}__published,均 permission=me 归属服务租户。
编辑者的写操作全部路由到 draft KB。检索用户/系统应用的检索请求由代理强制路由到 published KB。
审批通过时,代理执行「发布」:将 draft 已审定的文档同步进 published KB(调 RAGFlow 原生文档接口),并在 proxy_db 记录版本号 + 审批人 + 时间戳。
未发布内容只存在于 draft KB,而代理从不向检索用户暴露 draft——合规闸门成立,且 RAGFlow 全程只做它本来就做的「KB + 文档 + 检索」。
✅ 合规收益
全程审计链(谁改、谁审、何时发布、版本差异)落在 proxy_db.audit_log;RAGFlow 原始表零侵入;满足金融场景「四眼原则 + 可追溯 + 内容冻结」要求。

09代理服务表结构(独立库)

全部新增表落在 proxy_db,与 RAGFlow 库物理隔离。仅通过 ragflow_user_id / ragflow_kb_id 等外部主键引用原生实体,不加任何外键约束到 RAGFlow。

核心字段用途
p_roleid, code(5级), name, permissions(JSON)五级角色定义
p_orgid, name, parent_id, ragflow_tenant_id机构树;绑定服务租户
p_user_profileragflow_user_id, role_code, org_id用户-角色-机构绑定
p_kb_metaragflow_kb_id, share_mode(4模式), org_id, draft_kb_id, published_kb_idKB 共享元数据 + 影子映射
p_kb_aclragflow_kb_id, user_id, grant_type指定用户共享名单
p_quota_policyscope(global/role/dept/user), max_kb, max_doc, max_file_size, qps分级配额
p_model_whitelistscope_id, llm_ids(JSON)模型白名单
p_kb_versionragflow_kb_id, version_no, status(草稿/待审批/已发布/历史), snapshot版本状态机
p_approvalversion_id, submitter, approver, decision, comment, ts审批单
p_service_appid, api_key, ragflow_api_token, bound_kb_ids, qps, model_whitelist系统应用注册
p_audit_logactor, action, target, before/after, ts全量审计
p_token_brokertenant_id, access_token, expire_at缓存原生 Token
🚫 严格隔离原则
proxy_db 任何表不得与 RAGFlow 表建立数据库外键。仅以「值引用」方式持有 RAGFlow 的 id。RAGFlow 升级、迁移、甚至重建库,代理只需保证 id 映射一致即可,互不耦合。

10请求拦截与鉴权流程

单个请求在代理内的完整生命周期——这是整套设计的执行主干。

身份认证:解析代理签发的 Token(或系统应用的 API Key),定位 p_user_profile / p_service_app,确定主体 + 角色 + 机构。
路由识别:匹配请求方法+路径到「动作」(kb.create / doc.upload / chat / retrieval / kb.read…)。
RBAC 判定:动作是否在该角色允许集?检索用户命中写动作 → 直接 403。
资源 ACL 判定:涉及具体 KB 时,校验是否在「有效可见集」(§6.1)。版本闸门:检索请求强制改写到 published 影子 KB。
配额/频率/白名单:执行 §7 的 enforce_quota。超限 403/413/429。
Token 注入与转发:Token Broker 取对应租户的 RAGFlow 原生 access_token,替换 Authorization 头,转发给 RAGFlow。
响应过滤与审计:必要时裁剪响应(如列表接口只回有效可见集),写 p_audit_log,回传客户端。
💡 列表接口的二次过滤
像「列出我的 KB」这类接口,即使 Token Broker 用服务租户身份调用会返回较多结果,代理会在第 7 步用 §6.1 的有效可见集做响应裁剪,确保用户只看到自己被授权的条目。

11映射策略:如何落到原生 me|team

把细粒度模型「投影」到 RAGFlow 二态权限的完整对照——这是「零侵入」成立的技术保证。

代理概念RAGFlow 原生载体是否新增 RAGFlow 数据
5 级角色租户角色(owner/normal) + api_token,差异化由代理 PEP 补否(沿用原表)
机构「机构服务租户」(一个普通 tenant)否(用原 tenant 注册流程)
创建者私有(模式①)permission=me
机构共享(模式②)me + 服务租户 Token 代理
指定用户(模式③)me + ACL + Token 代理
团队共享(模式④)permission=team
版本审批draft/published 双影子 KB否(用原 KB+文档接口)
系统应用api_token否(用原 API Key 发放)
配额/频率/白名单—(纯代理层,RAGFlow 无感)
✅ 验证「不改原逻辑」的判据
在 RAGFlow 视角,它收到的永远是:合法 Token + 访问自己 me KB 或 team KB 的标准请求。它的 accessible()_visibility_and_status_filter()_load_user() 全部按原样运行且永远得到一致结果。所有「拒绝」都发生在请求到达 RAGFlow 之前。

12实施路线图

阶段交付关键风险点
P0 地基代理网关骨架 + Token Broker + proxy_db 建表 + 透传打通原生 Token 生命周期/刷新
P1 角色与 RBAC五级角色、路由→动作映射、写操作拦截接口清单覆盖完整性
P2 KB 四模式机构树、kb_acl、有效可见集、列表二次过滤服务租户代理访问的响应裁剪
P3 配额管控分级配额、Redis 频率、模型白名单计数一致性(并发上传)
P4 版本审批双影子 KB、状态机、审批单、审计draft→published 文档同步原子性
P5 合规加固全量审计、四眼原则、回溯报表金融审计字段完整性
💡 升级兼容性
因 RAGFlow 完全未改,官方版本升级时只需回归验证:① _load_user 双令牌逻辑、② KB accessible 语义、③ 文档/检索接口契约 三处是否变动。这三点是代理唯一的耦合面,验证成本极低。