Why: Account disable could be followed by late Backend business writes, API key creation could implicitly create users, account bootstrap locks could invert, and directory cursors were process-local or leaked out-of-scope changes. Evidence: Local go test ./... and isolated PostgreSQL package suites passed on the staged candidate; exact committed-head SELF replay and stage 6 human review remain pending. Impact: Adds final account transactions and external-effect checks across HTTP, Host, Bus and run manager; tightens API key issuance and persists versioned cursor signing. Delegated RDM rename still needs the separately proposed real-operator contract, so A1 and stage 6 are not complete. Refs: Documents U3 r1, approved A1-A4 amendment fb3a821, pending delegated-projection amendment 5783940, Dev feature/iam-u3-account-directory-recovery.
69 lines
2.1 KiB
Go
69 lines
2.1 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
|
||
agentcommand "agent-command"
|
||
)
|
||
|
||
// 命令审计按租户作用域(回归:GET /api/commands/audit 曾返回全租户审计行——
|
||
// ListAudits 无 owner 条件、command_audit 无 owner 列;多用户档下任何登录
|
||
// 用户可读他人命令 ID、入口、会话 ID 与参数摘要)。
|
||
// 经真实认证中间件(API key → owner)请求,断言每个租户只见本租户的行。
|
||
func TestCommandAuditOwnerScoping(t *testing.T) {
|
||
srv, st := newAuthTestServer(t)
|
||
h := srv.Handler()
|
||
|
||
aliceKey, err := createLegacyAPIKeyFixture(t, st, "alice", "e2e")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
bobKey, err := createLegacyAPIKeyFixture(t, st, "bob", "e2e")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// 两租户各产生一条命令审计(owner 列=v18 起的归属依据;Actor 仅为信封内容)。
|
||
if err := st.AppendAudit("alice", agentcommand.AuditEnvelope{
|
||
CommandID: "cmd.alice", Actor: "alice", State: agentcommand.StateCommitted,
|
||
}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := st.AppendAudit("bob", agentcommand.AuditEnvelope{
|
||
CommandID: "cmd.bob", Actor: "bob", State: agentcommand.StateCommitted,
|
||
}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
get := func(key string) []map[string]any {
|
||
t.Helper()
|
||
req := httptest.NewRequest(http.MethodGet, "/api/commands/audit", nil)
|
||
req.Header.Set("Authorization", "Bearer "+key)
|
||
rec := httptest.NewRecorder()
|
||
h.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusOK {
|
||
t.Fatalf("audit query: %d %s", rec.Code, rec.Body.String())
|
||
}
|
||
var out struct {
|
||
Audits []map[string]any `json:"audits"`
|
||
}
|
||
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return out.Audits
|
||
}
|
||
|
||
for key, owner := range map[string]string{aliceKey: "alice", bobKey: "bob"} {
|
||
rows := get(key)
|
||
if len(rows) != 1 {
|
||
t.Fatalf("owner %s: audit rows = %d, want 1(跨租户行不得返回;got=%v)", owner, len(rows), rows)
|
||
}
|
||
if rows[0]["actor"] != owner || rows[0]["commandId"] != "cmd."+owner {
|
||
t.Fatalf("owner %s: wrong row %v", owner, rows[0])
|
||
}
|
||
}
|
||
}
|