Files
opsx-vpn/docs/specs/SDD-011-feishu-directory-and-admission.md
T

55 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDD-011:飞书姓名补全与 OpenVPN 成员准入
| 字段 | 内容 |
|---|---|
| 级别 | S3:认证准入、外部目录查询、数据库新增表 |
| 设计状态 | 已形成 |
| 实现状态 | 本地实现;待真实飞书环境验证与发布 |
| 日期 | 2026-10-23 |
| 授权 | 用户要求继续开发姓名反查及两类成员准入 |
## 1. 背景与范围
当前 `identityprovider/handlers.go` 使用飞书 `authen/v1/user_info` 的 `name` 保存到 `IdentityProfile`。测试环境已有会话稳定 subject,但保存姓名为飞书返回的“用户812604”。`FeishuCallback` 只检查 `tenant_key`,OIDC 专线尚无成员白名单。`sessionmgr` 仅用 subject 精确查姓名;不按 IP 或展示名猜测用户。
本轮在用户每次授权时尝试使用应用 tenant token 按同应用 `open_id` 查询基础姓名,再回退通讯录单用户接口;均失败时保留 `user_info` 结果,登录不中断。提供仅管理员可调用的姓名查询诊断,结果不含上游 token。增加专线级准入模式:现有与缺省专线保持同租户全员;指定成员模式只允许已明确授予的 subject,撤销后拒绝下一次登录。身份资料与授权名单分开,不自动让已登录用户获准。OpenVPN WebAuth 尚未完成认证时出现的 UNDEF 不创建匿名审计,待稳定 subject 出现后记录连接。
## 2. 需求
- FR-01:反查同一应用 `open_id` 时,仅在返回有效姓名时覆盖展示名;权限、范围、超时等错误降级而不跳过租户检查;无管理员授权的用户不能调用诊断。
- FR-02:管理员能查看/添加/撤销某条内置飞书 OpenVPN 专线的已验证用户授权;不能跨租户、跨 issuer 或凭可变姓名授权。
- FR-03:`all` / `allowlist` 两模式:`all` 仍要求相同 `tenant_key`;`allowlist` 需精确匹配当前 tunnel ID + issuer + app ID + subject 的授权记录,缺数据和数据库错误均拒绝发放授权码。切换模式需在专线编辑页明示影响。
- NFR-01:短期上游访问令牌不存数据库/日志;接口查询只由服务端执行;更换飞书应用后旧名单不能生效;历史会话不回填猜测姓名。
## 3. 架构
`FeishuCallback` 已有经过飞书签发并核实租户的 `open_id`。优先用本次授权的用户令牌调用飞书 `POST /contact/v3/users/basic_batch?user_id_type=open_id`;失败后用应用 tenant token 重试,最后尝试单用户通讯录接口。请求体为 `user_ids`;SDK 文档说明 basic_batch 不受通讯录可见范围限制,但接口仍可能返回权限错误。只接受 `code=0`、有姓名且响应用户 ID 与原 `open_id` 一致的结果;均失败即保留原姓名。认证判断在保存 profile 和生成内部授权码之前,根据回调 URL 定位唯一的内置专线并检查准入。管理员选择已验证 profile 加入准入表。UI 为专线编辑页模式选择与成员列表;身份设置页提供姓名权限检测和操作提示。
## 4. 接口和数据
在 `Tunnel.Config` 增加 `feishu_access_mode`,值为 `all` 或 `allowlist`;未配置按 `all` 处理以保持兼容。新增 `FeishuTunnelGrant`(tunnel_id、issuer、app_id、subject、created_at),唯一索引约束完整身份;撤销时删除此记录。管理员接口:查询已验证身份、列出授权、添加、撤销;诊断接口输入已验证 subject,服务端使用加密保存的 App Secret 请求飞书并返回有限的状态和展示名。其余认证模式拒绝使用此功能。
## 5. 风险和验收
| 风险 | 防护/恢复 | 观察 |
|---|---|---|
| 飞书权限不足或通讯录不可见 | 不阻断 `all` 登录;诊断说明需在飞书开放平台授权,平台不能代授 | 降级显示原姓名 |
| 白名单配置错误造成拒绝连接 | 切换前先加入用户;设置恢复 `all` 即回退 | 回调无新授权码 |
| 换 App 后旧授权误用 | 联合 issuer/app ID/subject 精确匹配 | 跨应用负向测试 |
| 在线已连用户撤销 | 下一次登录阻断;本轮不强制踢已有连接 | 会话状态与下一次授权 |
| AC | 需求 | 验证 | 状态 |
|---|---|---|---|
| AC-01 | FR-01/NFR-01 | 模拟飞书成功/403/不一致/请求取消,核验姓名与不泄露 token | 单元测试通过;真实姓名待验证 |
| AC-02 | FR-02 | 管理员添加/撤销、普通账号禁止及未知身份拒绝 | 授权接口已实现,管理角色与未知身份尚未单独测试 |
| AC-03 | FR-03 | 默认 all;同租户白名单准入,跨应用、未授权、DB 失败均拒绝 | 单元测试通过,真实回调待验证 |
| AC-04 | 全部 | Go 测试、前端构建、现有登录路由回归 | 完整 Go 测试及前端构建通过;真实飞书登录待验证 |
## 6. 发布与恢复
先备份数据库和加密密钥,运行迁移与测试,部署后检查当前 `all` 专线仍能登录。先导入可验证身份并授予,再手动切换 `allowlist`;遇故障切回 `all`,新增表保留供重试。不删旧身份资料或证书。未实际完成测试前不标通过。
## 7. 未决
2026-10-23 测试环境以已保存的应用凭据查询已验证 `open_id`:tenant token 成功,`basic_batch` 返回飞书错误码 `99991672`,说明明确要求应用身份权限 `contact:user.basic_profile:readonly`;单用户接口成功且确认 open_id 匹配,但 `name` 仍为“用户812604”,其他姓名字段为空。浏览器用户令牌的 basic_batch 路径尚待下一次真人授权验证。基础姓名接口的应用权限和发布状态需在飞书开放平台核对;不能由平台代授权或仅靠用户数字反查。全量通讯录同步、部门实时同步和强制踢离在线用户留到后续迭代,本轮仅支持按已验证身份授权。