尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

部门知识空间成员授权范围收敛:Bisheng 基于 ReBAC 的部门子树授权与用户组禁用实战

部门知识空间成员授权范围收敛:Bisheng 基于 ReBAC 的部门子树授权与用户组禁用实战 部门知识空间成员授权范围收敛Bisheng 基于 ReBAC 的部门子树授权与用户组禁用实战【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng部门知识空间Department Knowledge Space是 Bisheng 中与组织部门树绑定的知识空间类型。本文将围绕 v2.6.0 的 F033 特性「部门知识空间成员授权范围收敛」完整讲解如何将部门空间的授权对象范围收敛为「绑定部门 其所有子部门 子树内成员」同时禁用「用户组」授权维度并给出后端单一判定源、前端 Tab 收敛、历史数据一次性清理脚本以及完整测试策略的源码级实现。读完本文你将掌握这套「只对部门空间生效、普通知识空间完全不变」的双层授权收敛方案可直接在 Bisheng 仓库spec.md、design.md、tasks.md中定位到每一处实现。1. 特性背景与用户故事部门知识空间在创建时会与某个部门绑定DepartmentKnowledgeSpace绑定关系其成员管理在 v2.6.0 之前与普通知识空间共用一套授权 UI用户 / 部门 / 用户组三个维度部门范围为全租户部门树。这带来一个组织隔离问题部门空间的访问授权可以跨越部门边界扩散可授权给其它部门、甚至通过用户组间接扩散到任意成员与本部门空间应有的「组织内共享」语义相悖。F033 的诉求非常明确——作为部门知识空间的管理员 / 创建者含 super_admin新增成员授权时只能选择本部门及子部门、以及其中的成员使部门知识空间的访问授权严格限定在本部门组织范围内不被跨部门或用户组维度扩散。特性优先级为 P1所属版本 v2.6.0。1.1 范围边界纳入 / 排除原文档spec.md明确划定了本次改动边界本次纳入部门知识空间「成员管理 → 新增用户授权」弹窗的可授权范围收敛为「绑定部门 其所有子部门」「这些部门下的成员」部门知识空间禁用「用户组」授权维度前端隐藏 后端拒绝一次性脚本清理部门知识空间历史遗留的用户组授权。本次明确排除防误扩范围不改普通知识空间的成员管理user / department / user_group 三维度、全租户范围——逐条回归保证不变不改 platform 端PermissionDialog其只服务knowledge_library不涉及knowledge_space不改部门知识空间创建时「默认授予绑定部门 viewer」的既有逻辑不改成员列表展示 / 角色升降 / 移除逻辑不为部门空间用户组授权保留运行期兼容历史态由脚本清理异常/历史态集中清理优于长期背兼容分支不动其它资源类型workflow / assistant / tool / channel / dashboard / knowledge_library的授权范围。2. 验收标准AC逐条解读验收标准只写可观测的对外行为错误码 / 实现归属见 design.md §2 / §3 / §6。编号验收点WHERE…THE SYSTEM SHALL…AC-01当前资源是部门知识空间时「新增用户授权」弹窗只提供「用户」「部门」两个维度不展示「用户组」维度对所有身份生效含 super_admin。AC-02部门知识空间的「部门」维度只返回「该空间绑定的部门及其所有子部门」不返回租户内其它部门。AC-03部门知识空间的「用户」维度只返回「绑定部门及其所有子部门下的成员」用户归属其中任一部门即可见。AC-04对部门知识空间提交「用户组」授权、或「用户/部门」对象不在绑定部门子树内系统拒绝该授权并返回权限不足错误对所有身份生效super_admin 同样被拒。AC-05普通知识空间的成员授权行为完全不变仍提供用户/部门/用户组三维度范围仍为全租户用户组授权仍可用。AC-06一次性清理脚本以--apply执行时清除所有部门知识空间上的用户组授权--dry-run默认只输出受影响的空间、用户组、波及用户数清单不改动任何授权。3. 边界情况与合理降级绑定部门已归档 / 子树为空部门与用户维度返回空列表提交非空授权被拒不报错合理降级——无可授对象用户归属多个部门只要任一部门落在绑定部门子树内即视为可见 / 可授权对应 B3 实现中的相关EXISTS语义天然不重复用户撤销授权不被拦截对部门空间执行撤销授权含撤销历史用户组授权时范围约束不拦截便于清理与移除前端标记不可信无论前端如何标记系统以「空间与部门的绑定关系」为唯一判定依据列表与授权准入不被绕过。4. 核心设计决策五条关键方案对比F033 最有价值的章节是 design.md §3 的五个决策这里完整展开决策 1部门空间判定 后端按绑定查询前端 flag 仅作 UI备选评估结论后端按DepartmentKnowledgeSpaceDao.aget_by_space_id判定前端额外传isDepartmentSpace仅用于隐藏 tab安全列表收敛 authorize 准入都在后端前端 flag 被篡改也无法越权前端 flag 缺失时后端仍然兜底选定仅前端按space.spaceKind department控制可被绕过直接调/authorize传 user_group 或子树外部门/用户否决后端在knowledge_space资源上加配置字段标识重复真相绑定关系已是单一真相再加字段会不一致否决原因DepartmentKnowledgeSpace绑定是「是否部门空间 绑定哪个部门」的唯一真相后端判定零额外状态、不可绕过。何时该推翻若未来一个空间可绑定多个部门 → 子树范围改为「多绑定部门 path 的并集」判定函数返回 list 即可准入逻辑不变。决策 2收敛点 收口到一个_resolve_department_space_scopehelper新增私有 helper_resolve_department_space_scope(resource_type, resource_id) - DeptScope | None仅当resource_type knowledge_space且存在绑定时返回{department_id, path, subtree_dept_ids}否则None。grant-subjects/users、grant-subjects/departments、grant-subjects/user-groups、authorize_resource四处统一调用它。备选否决四处各自查绑定 → 判定逻辑四份、易漂移原因单一判定来源None分支即「普通空间/非空间资源」——天然保证普通知识空间走原路径把「不影响普通空间」做成结构性保证而非散落 if。决策 3部门空间的非法授权 复用PermissionDeniedError不新增错误码authorize_resource对部门空间的grant校验①subject_type user_group→PermissionDeniedError.return_resp(部门知识空间不支持按用户组授权)②user/department的 subject 不在绑定部门子树内 →PermissionDeniedError.return_resp(只能授权给本部门及子部门的成员)。备选否决新增 109/180 段错误码——前端无差异化分支需求正常流程下越界请求不会发生仅作越权兜底无需独立码原因遵循 release-contract「不新增错误码」与既有_is_invalid_owner_subject的拒绝模式一致同样复用PermissionDeniedError何时推翻若前端需对「用户组被禁」与「越界」做不同文案 → 再拆 180 段两个码。决策 4历史用户组授权 一次性清理脚本运行期不兼容选定上线随附一次性脚本扫描所有DepartmentKnowledgeSpace绑定的space_id删除其上subject_typeuser_group的 ReBAC tupleFGA relation-model bindingDB。运行期authorize仅拒绝新增user_group不为历史保留读取/展示路径备选否决运行期列表里仍渲染历史用户组 允许撤销——用户明确选择「脚本清理即可业务逻辑无需兼容」风险点清理会立即收回这些用户组成员的访问脚本须先 dry-run 输出受影响 space/group/用户量供确认详见 §7。决策 5前端范围收敛 复用后端已收敛的列表不在前端做子树过滤前端不感知部门子树SubjectSearchUser/SubjectSearchDepartment仍调既有grant-subjects/users|departments因后端已按空间收敛列表天然只剩子树内数据。前端唯一改动是按isDepartmentSpace去掉user_grouptab。备选否决前端拿到departmentId后自行裁剪部门树/过滤用户——与后端两份范围逻辑、易不一致原因范围单一真相在后端前端零重复逻辑改动面最小。5. 系统现状与数据流主线5.1 复用的既有模式授权入口客户端KnowledgeSpaceShareDialog.tsx ——SUBJECT_TABSuser/department/user_group 授权弹窗PermissionGrantTab是部门空间成员授权的唯一 UI客户端 APIknowledge.ts ——KnowledgeSpace.spaceKindnormal|department/departmentId已由space_kind/department_id映射好后端授权与列表resource_permission.py ——POST /resources/{type}/{id}/authorize、GET .../grant-subjects/users|departments|user-groups绑定真相department_knowledge_space.py ——DepartmentKnowledgeSpaceDao.aget_by_space_id(space_id)返回绑定含department_id表结构包含tenant_id、department_id、space_id、created_by等字段且对space_id有唯一约束uk_dks_space_id保证「一个空间至多绑定一个部门」部门数据department.py ——Department.path物化路径子树前缀、UserDepartment用户-部门含is_primary。5.2 数据流输入 → 输出主线[部门空间成员管理 → 新增用户授权] 前端 ShareDialog: isDepartmentSpace space.spaceKind department ← 调用方传入 → SUBJECT_TABS 过滤掉 user_group仅 user/department → PermissionGrantTab 调 grant-subjects/users|departments 后端 grant-subjects/*: scope _resolve_department_space_scope(knowledge_space, space_id) scope is None → 普通空间/非空间 → 原全租户列表逻辑不变 scope 命中 → users : 仅 subtree_dept_ids 内成员 departments: 仅绑定部门 path 子树 user-groups: 返回 [] 后端 authorize: scope _resolve_department_space_scope(...) scope is None → 原逻辑 scope 命中 → 对每个 grant: user_group → PermissionDeniedError user 不在子树成员 → PermissionDeniedError department 不在子树 → PermissionDeniedError revoke 不限制便于清理/移除 通过 → 走既有 PermissionService.authorize6. 后端实现B1–B6 源码级拆解全部改动落在 resource_permission.py接口层 私有 helper无新增 ORM / 错误码 / 配置。B1_resolve_department_space_scope— 单一判定来源dataclass(frozenTrue) class _DepartmentSpaceScope: department_id: int department_path: str | None subtree_dept_ids: frozenset[int] async def _resolve_department_space_scope( resource_type: str, resource_id: str, *, load_subtree_ids: bool True, ) - _DepartmentSpaceScope | None: if resource_type ! knowledge_space: return None binding await DepartmentKnowledgeSpaceDao.aget_by_space_id(space_id) if binding is None: return None dept await DepartmentDao.aget_by_id(department_id) if dept is None or dept.status ! active: return _DepartmentSpaceScope(department_id, None, frozenset()) # 归档/缺失 → 空 department_path dept.path subtree_ids await DepartmentDao.aget_subtree_ids(department_path) if load_subtree_ids else [] return _DepartmentSpaceScope(department_id, department_path, frozenset(subtree_ids))对应源码位置resource_permission.py#L645-L708。要点只对knowledge_space判定其余资源类型直接返回Noneload_subtree_idsTrue仅用于授权写入校验复用DepartmentDao.aget_subtree_ids(path)得严格 ID 集只读候选列表路径传False直接使用department_path不展开整棵子树性能修订见 §9 坑 5绑定部门归档/缺失时返回空的scope列表返回空、authorize 拒绝非空 grant属合理降级。B2 / B3列表收敛部门 用户_list_knowledge_space_grant_departments增restrict_dept_ids入参非空时在既有 stmt 上追加Department.id.in_(restrict_dept_ids)仍复用既有建树/计数/排序_list_knowledge_space_grant_users增restrict_dept_path入参非空时以相关EXISTS联结UserDepartment → Department按Department.path LIKE bound_path%判断子树成员多部门用户命中任一即可见 resource_permission.py#L828-L843if restrict_dept_path is not None: in_department_subtree ( select(UserDepartment.id) .join(Department, Department.id UserDepartment.department_id) .where( UserDepartment.user_id User.user_id, Department.path.like(f{restrict_dept_path}%), Department.status active, ) .exists() ) stmt stmt.where(in_department_subtree)同时用户查询只投影接口需要的字段user_id / user_name / external_id不再 JOINTenant、不依赖distinct。restrict_dept_pathNone即普通资源路径——普通知识空间与其它资源零行为变化这是「不影响普通空间」的代码级保证。B4候选端点透传 scopeget_grant_subject_users/get_grant_subject_departments及其 F038 懒加载变体children/search/path-tree统一调 B1 得 scope把department_path作为restrict_root_path透传给懒加载部门树查询归档/缺失绑定部门返回空。B5user-groups 端点短路resource_permission.py#L1869-L1871部门空间直接resp_200([])不查库否则原逻辑。if await _resolve_department_space_scope(resource_type, resource_id, load_subtree_idsFalse) is not None: return resp_200([])B6authorize_resource准入校验关键resource_permission.py#L1476-L1483 中scope 判定与校验被放置在if not login_user.is_admin()管理权豁免分支之外——这正是「super_admin 不豁免」的代码级落点# F033: department knowledge space restricts grants to the bound department # subtree / its members and forbids user-group grants. Applies to ALL # identities (incl. super_admin), so it sits outside the is_admin() bypass. department_scope await _resolve_department_space_scope(resource_type, resource_id) if department_scope is not None: denial await _validate_department_space_grants(department_scope, request.grants or []) if denial is not None: return denial准入校验函数_validate_department_space_grantsresource_permission.py#L727-L752for grant in grants: if grant.subject_type user_group: return PermissionDeniedError.return_resp(部门知识空间不支持按用户组授权) if grant.subject_type department: if int(grant.subject_id) not in scope.subtree_dept_ids: return PermissionDeniedError.return_resp(只能授权给本部门及子部门) elif grant.subject_type user: candidate_user_ids.add(int(grant.subject_id)) if candidate_user_ids: allowed await _subtree_user_ids(scope.subtree_dept_ids, candidate_user_ids) if not candidate_user_ids.issubset(allowed): return PermissionDeniedError.return_resp(只能授权给本部门及子部门的成员)注意 revokes 不校验——若把 revoke 也拦截历史用户组授权将无法在 UI/脚本里撤销与决策 4 冲突。校验通过后走既有PermissionService.authorize链路含 FGA tuple 写入 relation-model binding 持久化。7. 前端实现仅隐藏用户组 Tab前端改动极小核心在 KnowledgeSpaceShareDialog.tsx// F1: 新增可选 prop默认 false → 行为完全不变向后兼容 isDepartmentSpace?: boolean; // SUBJECT_TABS 改为 useMemo部门空间剔除 user_group const SUBJECT_TABS useMemo(() { const tabs [...]; return isDepartmentSpace ? tabs.filter((tab) tab.value ! user_group) : tabs; }, [isDepartmentSpace]);因为授权弹窗的并列 tab 与现有授权列表PermissionListTab共用同一SUBJECT_TABS剔除后列表视图也不再显示用户组 tab恰好符合决策 4历史用户组授权不在 UI 展示。flag 与操作者身份无关故super_admin 在部门空间同样只有 user/department 两 tab。两个 call site 传入 flagpages/knowledge/SpaceDetail/index.tsxKnowledgeSpaceShareDialog ... isDepartmentSpace{space.spaceKind department} /pages/knowledge/index.tsxspacePermissionDialogSpace?.spaceKind department。部门树/人员列表无需前端改动后端已收敛SubjectSearchDepartment/SubjectSearchUser拿到的就是子树数据。如需要「部门空间不支持用户组授权」提示文案可加com_permission.*keyen/zh-Hans/ja但后端PermissionDeniedError文案已可兜底前端 toast 走既有captureAndAlertRequestErrorHoc本期实现无新增 i18n。8. 一次性清理脚本历史用户组授权的处置脚本位置clean_department_space_user_group_grants.py一次性数据清理归scripts/非 Alembic/migration/。用法在src/backend目录下# Dry-run默认只扫描并打印受影响的 (空间, 用户组, relation, 波及用户数)不写库 python scripts/clean_department_space_user_group_grants.py # 真正应用 python scripts/clean_department_space_user_group_grants.py --apply逻辑DepartmentKnowledgeSpaceDao.aget_all()→ 所有部门空间space_id→PermissionService.get_resource_permissions找subject_typeuser_group的授权 →--apply时经PermissionService.authorize的 revokeenforce_fga_successTrue删 ReBAC tuple _save_bindings去 relation-model binding。运行环境要求脚本入口先initialize_app_context(configsettings)DB OpenFGA否则 revoke 的 FGA 写会报FGAClient not availableDB 读写在bypass_tenant_filter()下跨租户扫描所有租户的部门空间。安全保证仅作用于部门知识空间且只删除subject_typeuser_group的授权——不碰普通知识空间、不碰 user / department 维度授权Dry-run 是默认行为不写任何库--apply才执行不可逆删除即收回该用户组成员的访问务必先看 dry-run 输出再--apply。9. 已知坑 / 反直觉事实实现要点清单判定必须在后端、不能信前端 flag—— 前端isDepartmentSpace只控制 tab 显隐列表收敛与 authorize 准入都必须独立用 B1 判定否则直接调 API 可越权绕过隐藏的 tab。这是双层落地的根因。revoke 不能被收敛拦截—— 准入校验只作用于grants。若把 revoke 也拦了历史用户组授权将无法在 UI/脚本里撤销。默认 viewer 授权的include_childrenFalse与新可选范围不一致是预期的—— 建空间时只给「绑定部门本级」viewer_grant_department_members_viewer但新需求允许管理员额外勾选子部门/子树成员授更高档位。两者不矛盾默认是底座手动授权是叠加。本期不改默认授权的 include_children。多部门用户—— 用户只要任一UserDepartment落在子树即可见/可授B3 用相关EXISTS天然不重复用户。不要误用is_primary过滤那只用于显示主部门路径。grant-subjects/users的子树过滤必须在 SQL 层完成不能拉全量再内存筛—— 大租户全量用户分页会错位先分页后过滤会漏。使用相关EXISTS(UserDepartment JOIN Department WHERE Department.path LIKE bound_path%)后再offset/limit不能把整棵子树展开成大.in_()——tasks.md 记录的真实教训是空间 27 的用户候选查询把约 3.4 万个subtree_dept_ids展开为单条大.in_()触发达梦CODE:-608SQL 超长超时因此恢复department_path并给 resolver 增加load_subtree_ids参数只读候选路径用 path 相关EXISTS、不加载整棵 ID 集授权写入校验继续加载 ID 集业务准入语义不变。空间被解绑/部门归档—— B1 命中绑定但绑定部门status ! active时subtree_dept_ids为空 → 列表返回空、authorize 拒绝非空 grant。属合理降级无可授对象不报错。platform 端不涉及—— platform 的PermissionDialog只用于knowledge_library不会传knowledge_space本期不碰它避免误改普通库授权。super_admin 不豁免范围约束最反直觉—— 一般authorize_resource对is_admin()跳过管理权校验惯性会以为超管可任意授权。但部门空间范围收敛与身份无关超管同样只能授本部门子树/子树成员、同样禁用用户组。处理方式是把 B6 校验置于该豁免分支之外见 §6 B6。10. 对外契约与依赖Outgoing本特性对外提供grant-subjects/users|departments对部门空间返回收敛后的子集grant-subjects/user-groups对部门空间返回[]。风险点调用方若假设这些接口总是全租户需知悉部门空间例外普通空间不变authorize对部门空间拒绝 user_group / 子树外 subjectPermissionDeniedError。风险点自动化脚本批量授权部门空间需遵守范围前端KnowledgeSpaceShareDialog新增可选 propisDepartmentSpace默认false→ 行为完全不变向后兼容其它调用方。Incoming本特性依赖他人DepartmentKnowledgeSpaceDao.aget_by_space_id/aget_all绑定真相来源若改为一空间多部门绑定B1 需同步返回并集Department.path子树语义、UserDepartment表结构子树与成员判定的数据契约既有_resolve_grant_subject_tenant_id/bypass_tenant_filter/PermissionService.authorize不改其行为仅在其前后加 scope 收敛KnowledgeSpace.spaceKindclient api前端 flag 来源。release-contract 登记无新增领域对象、不新增表/对外 API/错误码复用PermissionDeniedError、不新增不变量——仅在既有 ReBAC 授权/列表链路对knowledge_space资源加部门子树收敛分支。11. 测试与验证策略11.1 测试层级Wave 1 纯逻辑单测_resolve_department_space_scope的 SQLite 单测部门空间命中、普通空间返回None、非 knowledge_space 返回None、绑定部门归档 → 子树空Wave 2–3 真库 e2e测试策略修订因 ReBAC/多租户/部门子树行为正确层级是真库Wave 2-3 由 SQLite seed 单测改为统一真库 e2etest_e2e_department_space_scope.py10 用例test_e2e_resource_permission_ui.py13 passed覆盖前端KnowledgeSpaceShareDialog在isDepartmentSpace下不渲染 user_group tab普通空间三 tab 不变。11.2 e2e 用例矩阵覆盖 AC 追溯用例覆盖 AC断言test_ac02_*AC-02部门空间grant-subjects/departments仅绑定部门子部门test_ac03_*AC-03部门空间grant-subjects/users仅子树成员、严格子集、多部门任一命中、keyword分页正确test_ac04_*admin super_adminAC-04grant user_group → 拒子树外 user/department → 拒子树内 → 通过revoke 放行以 super_admin 身份重跑拒绝用例同样被拒test_ac05_*AC-05普通空间三维度全通过、部门全租户、user-groups 正常返回test_ac04_user_groups_disabled_*AC-01数据面部门空间grant-subjects/user-groups返回[]11.3 手动验证清单完整清单见 e2e-checklist.md核心步骤部门空间打开成员管理 → 新增授权只有「用户/部门」两 tab无「用户组」部门 tab 只列绑定部门及子部门用户 tab 只列子树成员 → 授权成功super_admin 不豁免以 super_admin 登录重复上述步骤仍只有两 tab、范围同样收敛普通知识空间回归同入口三 tab、全租户范围、可按用户组授权——确认无变化脚本验证清理脚本--dry-run输出受影响项--apply后部门空间不再有用户组授权真库验证dry-run 扫 31 空间 / 0 grantseed-and-apply spot check 通过。12. 后续改进方向一空间多部门绑定当前按单绑定设计B1 已预留并集化扩展点多绑定部门 path 的并集差异化错误文案如产品要区分「用户组被禁」与「越界授权」→ 再拆 180 段错误码默认授权含子部门开关若后续希望建空间即给整棵子树 viewer再评估改_grant_department_members_viewer的include_children本期不做。13. 相关文档特性规格spec.md需求口径与验收标准设计真相design.md接手第一入口含方案对比与系统现状执行与落档tasks.md任务拆解与偏差记录端到端验证清单e2e-checklist.md版本契约features/v2.6.0/release-contract.md后端实现resource_permission.py绑定模型department_knowledge_space.py前端弹窗KnowledgeSpaceShareDialog.tsx清理脚本clean_department_space_user_group_grants.pye2e 测试test_e2e_department_space_scope.py【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表