fix: allow agent JWT to use attached online_db_id (Z14c)

Agent tokens use agent_id as user_id while Binding is under the human member; authorize schema/push/ensure against the agent's mounted OnlineDBID.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-05 18:22:57 +08:00
parent 4fae7ae718
commit bb59bb5efd
3 changed files with 63 additions and 10 deletions

View File

@@ -0,0 +1,20 @@
package handler
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"aijianzhan/platform/internal/authx"
)
func TestAgentOwnsOnlineDBUsesContextAgent(t *testing.T) {
// 无 Agents store 时必须安全返回 false
r := httptest.NewRequest(http.MethodPost, "/", nil)
ctx := authx.WithFullClaims(context.Background(), 2, 99, authx.Role智能体, 99, 0, []string{authx.Perm数据同步})
r = r.WithContext(ctx)
if agentOwnsOnlineDB(nil, r, 2, "ch1", "ch1_u3") {
t.Fatal("expected false without svc")
}
}

View File

@@ -198,8 +198,8 @@ func authorizeUserSyncPush(svcCtx *svc.ServiceContext, w http.ResponseWriter, r
return authorizeUserOnlineDB(svcCtx, w, r, channelID, item.OnlineDBID, "用户自助 push 须带 online_db_id且须为本人 Binding")
}
// authorizeUserOnlineDB带 online_db_id 时一律按本人 Binding 校验(含有「数据同步」的人类管理员)。
// 仅「数据同步」且未带 online_db_id 时保持租户级管理路径(智能体/管理员兼容)
// authorizeUserOnlineDB带 online_db_id 时本人 Binding或智能体挂载落点匹配Z14c)。
// 仅「数据同步」且未带 online_db_id 时保持租户级管理路径。
func authorizeUserOnlineDB(svcCtx *svc.ServiceContext, w http.ResponseWriter, r *http.Request, channelID, onlineDBID, emptyMsg string) bool {
online := strings.TrimSpace(onlineDBID)
wide := authx.SyncTenantWide(r.Context())
@@ -215,8 +215,41 @@ func authorizeUserOnlineDB(svcCtx *svc.ServiceContext, w http.ResponseWriter, r
}
uid := authx.UserID(r.Context())
tid := syncTenantID(r)
if !svcCtx.DBSync.Store().UserOwnsOnlineDB(tid, uid, channelID, online) {
authx.WriteError(w, http.StatusForbidden, "无权访问该 online_db_id非本人 Binding / 非公司共享库)")
if svcCtx.DBSync.Store().UserOwnsOnlineDB(tid, uid, channelID, online) {
return true
}
// Z14c智能体 JWT 的 user_id=agent_idBinding 记在人类成员上;放行 agents/me / ticket 挂载的 online_db_id
if agentOwnsOnlineDB(svcCtx, r, tid, channelID, online) {
return true
}
authx.WriteError(w, http.StatusForbidden, "无权访问该 online_db_id非本人 Binding / 非智能体挂载落点 / 非公司共享库)")
return false
}
// agentOwnsOnlineDB 当前智能体账号是否已挂载该 online_db_id及可选 channel
func agentOwnsOnlineDB(svcCtx *svc.ServiceContext, r *http.Request, tenantID int64, channelID, onlineDBID string) bool {
if svcCtx == nil || svcCtx.Agents == nil {
return false
}
aid := authx.AgentID(r.Context())
if aid <= 0 {
// 兼容旧票Role=智能体 时 UserID 即 AgentID
if authx.Role(r.Context()) == authx.Role智能体 {
aid = authx.UserID(r.Context())
}
}
if aid <= 0 || tenantID <= 0 {
return false
}
acc, err := svcCtx.Agents.Get(r.Context(), tenantID, aid)
if err != nil || acc == nil {
return false
}
if strings.TrimSpace(acc.OnlineDBID) != strings.TrimSpace(onlineDBID) {
return false
}
ch := strings.TrimSpace(channelID)
if ch != "" && strings.TrimSpace(acc.ChannelID) != "" && strings.TrimSpace(acc.ChannelID) != ch {
return false
}
return true

View File

@@ -19,7 +19,7 @@
| **Z11** | **智建已落实** | 发布自动补列 |
| **Z12** | **双方已接** | 默认同步通道;**默认 Enabled=运行中**`cb76824` |
| **Z13** | **双方代码已接** | 绑定码/手机/凭票/policy生产配号+Secret 见 §0.2 B |
| **Z14** | **宇恒已接;智建 fingerprint 已合入待 pull** | 见 §5.11Z14c `_uN` ACL 仍待智建修 |
| **Z14** | **双方已接;生产 pull** | 指纹 API + Z10d**Z14c** 智能体挂载 online 已放行;见 §5.11 |
| **仍建议关注** | 性能 | SQLite remote 串行 drain高并发易锁 |
### 0.1 绑定产品冻结摘要Z13 · 试运行)
@@ -49,7 +49,7 @@
| 优先级 | 编号 | 内容 |
|--------|------|------|
| **P0 配合** | **数据怎么上云** | 见 **§0.3**:绑定 → 库选「同步」→ agent drain智建「查看线上表」只反映已 push/ensure |
| **P0 配合** | **online_db_id** | `_uN` **403** 时:暂用本机 `local_database_id` 作可写 id或省略 online有「数据同步」管理权时等 Z14c |
| **P0 配合** | **online_db_id** | 智能体 JWT 可带 `agents/me` / ticket 返回的 `{channel}_uN`Z14c 已修);人类自助 JWT 仍须本人 Binding |
| **P1** | **Z12e / Z7 / §2.6** | env 过渡Binding 可读名;按库串行 drain |
| **P1** | **指纹** | 优先调智建 `schema/fingerprint`;未部署则回退 `schema.row_count` |
@@ -70,7 +70,7 @@
| **P0** | 生产再 pull 本批 | Z10d ADD COLUMN + `schema/fingerprint` + 默认启动;`bash ./restart.sh --pull` |
| **P0** | 成员手机 | 「宇信达」绑 **`13531041944`**;勿超管 `13531041945` |
| **P0** | 通道表白名单 | **勿**「填入测试默认」;形态 B 可空 |
| **P0** | **Z14c** `online_db_id` ACL | ticket/`agents/me``{channel}_uN` 须与 Binding/JWT 一致,避免 schema/ensure/push **403** |
| **P0** | **Z14c** `online_db_id` ACL | ~~ticket/`agents/me` 的 `{channel}_uN` 403~~**已修**:智能体 JWT 放行其挂载的 `online_db_id``4fae7ae` 后续) |
| **P1** | 凭票 Secret | `YuhengTicket.Secret``YXD_YUHENG_TICKET_SECRET` 同值 |
| **P1** | `DefaultRemoteDSN` | 生产 Postgres空则 sqlite 文件(联调) |
| **P2** | 正式短信 | Provider≠`"off"``RequireForBind=true` |
@@ -639,7 +639,7 @@ POST .../schema
| **Z14a** | **P0** | 宇恒本机指纹 + 对比线上 row_count + 自动修复 | **宇恒已接**`yxd/app_fastapi/sync_fingerprint.py`agent 默认 TTL **600s**`POST /database/sync/fingerprint` |
| **Z14b** | **P1** | 智建返回 `content_hash` | **智建已合入待生产 pull**`POST /api/v1/agent/sync/channels/{id}/schema/fingerprint` |
| **Z10d** | **P0** | ensure 对已存在表补列 | **智建已合入待生产 pull**;否则 `填土高度` 等缺 `id` 会 503 |
| **Z14c** | **P1** | ticket/`agents/me``online_db_id` 与 gateway ACL 一致 | **智建**;宇恒暂用本机库 id 或省略 online见 §0.3 |
| **Z14c** | **P1** | ticket/`agents/me``online_db_id` 与 gateway ACL 一致 | **智建已落实**:智能体 JWT 校验挂载落点(`agentOwnsOnlineDB`Binding 仍按人类 user_id |
**契约摘要Z14b**
@@ -656,13 +656,13 @@ POST /api/v1/agent/sync/channels/{id}/schema/fingerprint
| 方 | 做什么 |
|----|--------|
| 宇恒 | 本机算指纹;优先调 fingerprint API404 则回退 `schema.row_count`;不一致自动 ensure+`enqueue_full_push`heartbeat 写入 `fingerprint_*` |
| 智建 | 部署 fingerprint + Z10d修正 `_uN` ACLZ14c |
| 智建 | 部署 fingerprint + Z10d**Z14c 已修**(智能体挂载 online |
**联调踩坑2026-08-05 · 宇信达生产)**
1. **数据量对不上**:历史行不会因开通同步自动上云 → 须 full-push指纹对账可检出差表。
2. **`填土高度(6标一工区)`**:本机列 `_row_id`,线上曾无 `id`push 默认 pk=`id` 并注入该列 → `has no column named id`;整队曾因 `mark_error` 未改 status 卡住(宇恒已修)。修复路径:删表重建 / 部署 Z10d 后 ensure 补 `id` 再推。
3. **`online_db_id=_u3` 403**ticket/`agents/me` 返回 `{channel}_u3`,但显式带该 id 调 schema/ensure 被拒;省略或用本机库 id 可写。JWT `user_id=1``_u3` 后缀不一致,属平台 Binding/ACL 问题
3. **`online_db_id=_uN` 403(已修)**:智能体 JWT 的 `user_id=agent_id`Binding 记在人类成员上;现 `authorizeUserOnlineDB` 额外放行智能体挂载的 `OnlineDBID`/`ChannelID`
**验收**