diff --git a/docs/oidc-test-plan.md b/docs/oidc-test-plan.md new file mode 100644 index 0000000..5054154 --- /dev/null +++ b/docs/oidc-test-plan.md @@ -0,0 +1,99 @@ +# OIDC 接入 — 测试点描述 + +覆盖范围:OIDC 登录、hf 用户与 OIDC 身份绑定、`HARBORFORGE_OIDC_ONLY` +模式、管理员 OIDC 配置页面。涉及仓库分支 `feature/oidc-login` +(`HarborForge.Backend` + `HarborForge.Frontend`)。 + +“本地状态” 列:✅ 已用真实 Keycloak 在本地栈端到端验证;🟡 已用接口/构建 +验证但未走真实 IdP UI;⬜ 待测。 + +## 0. 测试环境 + +- 后端 `http://127.0.0.1:18000`、前端 `http://127.0.0.1:13000`(本地验证栈) +- IdP:Keycloak 容器,realm `hf`,confidential client `hf-client` +- IdP 测试用户:`tester` / `Test123!`(emailVerified=true) +- 关键约束:**issuer URL 必须浏览器与后端容器都能用同一地址访问** + (否则 token `iss` 校验失败)。本地用宿主机 IP 统一两端。 +- 配置项(运行时 env 或 DB,DB 覆盖 env): + `OIDC_ENABLED / OIDC_ISSUER / OIDC_CLIENT_ID / OIDC_CLIENT_SECRET / + OIDC_REDIRECT_URI / OIDC_SCOPES / OIDC_POST_LOGIN_REDIRECT`; + 部署级 `HARBORFORGE_OIDC_ONLY`(Docker ARG/ENV,前后端均有)。 + +## 1. 管理员 OIDC 配置(页面 + API) + +| # | 测试点 | 步骤 | 预期 | 本地 | +|---|--------|------|------|------| +|1.1|读取初始配置|`GET /auth/oidc/settings`(admin)|返回 `source=env`,`has_client_secret=false`,`effective_enabled` 反映 env|✅| +|1.2|保存配置|`PUT /auth/oidc/settings` 填 issuer/client/secret/redirect/scopes/post_login|200,`source=db`,`effective_enabled=true`|✅| +|1.3|client_secret 只写不回显|保存后再 `GET`|响应无明文 secret,仅 `has_client_secret=true`|✅| +|1.4|secret 留空保留原值|`PUT` 不带 `client_secret`|原 secret 不变,登录仍可用|🟡| +|1.5|配置即时生效|保存后 `GET /auth/config`|`oidc_enabled` 立即变化,无需重启|✅| +|1.6|页面仅 admin 可见|非 admin 访问 `/settings/oidc`|被重定向到 `/`;侧栏无入口;API 返回 401/403|🟡| +|1.7|页面展示 Callback URL|打开 OIDC 设置页|醒目显示需在 IdP 注册的 redirect/callback URL、当前状态与配置来源|🟡| + +## 2. OIDC 登录流程(授权码) + +| # | 测试点 | 步骤 | 预期 | 本地 | +|---|--------|------|------|------| +|2.1|发起登录|`GET /auth/oidc/login`|302 跳转到 IdP authorize 端点(state/nonce 入会话 cookie)|✅| +|2.2|IdP 登录成功回跳|在 IdP 输入 `tester/Test123!`|302 回 `…/auth/oidc/callback?code=…&state=…`|✅| +|2.3|回调换码并签发|后端处理 callback|校验 ID token(JWKS)→定位绑定用户→签发 hf JWT→302 到前端 `post_login#token=…`|✅| +|2.4|登录态可用|用返回 token 调 `GET /auth/me`|返回被绑定的 hf 用户|✅| +|2.5|前端按钮|登录页点 “Sign in with SSO”|全页跳转到后端 `/auth/oidc/login`|🟡| +|2.6|未配置 OIDC|`OIDC` 关闭时访问 `/auth/oidc/login`|503 “OIDC is not configured”|🟡| +|2.7|token 交换失败|code 失效/被篡改|回前端 `?oidc_error=exchange_failed`,登录页提示|⬜| + +## 3. hf 用户 ↔ OIDC 身份绑定 + +| # | 测试点 | 步骤 | 预期 | 本地 | +|---|--------|------|------|------| +|3.1|管理员绑定|`PUT /users/{id}/oidc-binding` {issuer,subject}(admin 或 acc-manager)|200,用户响应含 `oidc_issuer/oidc_subject`|✅| +|3.2|未绑定身份拒绝登录|解绑后用该 IdP 账号登录|回 `?oidc_error=not_linked`,**不签发 token**(不自动开号)|✅| +|3.3|身份唯一性|把同一 (issuer,subject) 绑到另一用户|409 冲突|✅| +|3.4|解绑|`DELETE /users/{id}/oidc-binding`|200,绑定清空|✅| +|3.5|绑定鉴权|无凭据 / 普通用户调用绑定 API|401 / 403|✅| +|3.6|API key 通道|admin 的 API key 调用绑定/配置 API|200(支持 JWT 或 API key)|✅| +|3.7|自助绑定(非 ONLY)|登录用户点侧栏 “Link OIDC account” 走一次 OIDC|回 `?oidc_linked=1`,当前账号绑定成功|🟡| +|3.8|自助绑定冲突|自助绑定到已被占用的身份|`?oidc_error=already_bound`|⬜| +|3.9|OIDC_ONLY 下禁自助|ONLY 模式访问 `/auth/oidc/link`|403(仅管理员 API 可绑)|🟡| + +## 4. HARBORFORGE_OIDC_ONLY 模式 + +| # | 测试点 | 步骤 | 预期 | 本地 | +|---|--------|------|------|------| +|4.1|配置反映|ONLY=true 时 `GET /auth/config`|`oidc_only=true`,`password_login=false`|✅| +|4.2|禁用密码登录|`POST /auth/token`|403 “Password login is disabled (OIDC only)”|✅| +|4.3|建用户忽略密码|`POST /users` 带 password|201,但 DB `hashed_password=NULL`(无密码用户)|✅| +|4.4|改用户忽略密码|`PATCH /users/{id}` 带 password|密码不被设置|🟡| +|4.5|无密码用户仍可用|对该用户绑定 OIDC、生成 API key|绑定 200、`reset-apikey` 200;可经 OIDC 登录|✅| +|4.6|前端隐藏密码 UI|ONLY 模式打开登录页 / 用户管理页|无用户名密码框;用户管理无密码/重置密码组件|🟡| +|4.7|防锁死恢复|ONLY 模式且 OIDC 配错|管理员 API key 仍可调 `GET/PUT /auth/oidc/settings` 修复|✅| + +## 5. 回归(不破坏既有功能) + +| # | 测试点 | 步骤 | 预期 | 本地 | +|---|--------|------|------|------| +|5.1|默认模式密码登录|未开 ONLY 时 `POST /auth/token`|正常 200 签发 token|✅| +|5.2|用户响应新增字段|`GET /users` `/users/{id}` `/auth/me`|含 `oidc_issuer/oidc_subject`(未绑定为 null)|✅| +|5.3|启动迁移幂等|新旧库重复启动后端|`users.oidc_*` 列与 `oidc_settings` 表存在,无报错|✅| +|5.4|前端构建|`npm run build`(Docker 镜像)|TS 编译通过|✅| +|5.5|后端导入|镜像内 `import app.main`|无导入错误,OIDC 路由注册|✅| +|5.6|镜像参数|前后端 Dockerfile|含 `ARG/ENV HARBORFORGE_OIDC_ONLY`|✅| + +## 6. 安全检查点 + +- ID token 经 IdP JWKS 校验(Authlib discovery + nonce);签发的是 hf 原有 + HS256 JWT,依赖强 `SECRET_KEY`(弱 key 后端拒绝启动)。 +- `client_secret` 持久化在 DB,接口永不回显。 +- 绑定/配置接口强制 admin(或 account-manager 绑定),支持 API key 作为 + OIDC_ONLY 下的恢复通道。 +- 未绑定 OIDC 身份一律拒绝,不自动开号(防越权开户)。 +- `redirect_uri` 必须在 IdP 精确注册;后端回跳前端地址来自服务端配置 + (`post_login_redirect`),不接受客户端传入,避免开放重定向。 + +## 7. 已知限制 / 待补 + +- 真实浏览器交互(点按钮、IdP 同意页、SameSite cookie 行为)需人工过一遍 + (本地已用 curl 无头跑通授权码全流程,逻辑等价)。 +- 多 IdP / 多 issuer、token 刷新、登出联动(SLO)未实现,超出本次范围。 +- 自助绑定相关 UI(3.7/3.8)建议人工在浏览器复核。