Files
Agent-System-Backend/internal/app/account_guard_test.go
T
weichen adcd8fefd2 Fence U3 account writes and stabilize directory cursors
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.
2026-10-14 07:33:54 +00:00

118 lines
4.0 KiB
Go

package app
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"agent-system-backend/internal/settings"
"agent-system-backend/internal/store"
)
func TestInprocHostRechecksEffectiveAccountOnEveryCapability(t *testing.T) {
st, err := store.OpenSQLite(t.TempDir() + "/host.db")
if err != nil {
t.Fatal(err)
}
defer st.Close()
if err := st.CreateAccount("alice", "", "", false); err != nil {
t.Fatal(err)
}
check := accountEffectiveAuthorization(st, true)
h := inprocHost{caps: hostcaps{st: st, accountGuard: func(owner string) error {
valid, err := check(context.Background(), owner)
if err != nil {
return err
}
if !valid {
return store.ErrAccountForbidden
}
return nil
}}, owner: "alice", actor: "alice", pluginID: "rdm"}
if err := h.KVPut("before", "allowed"); err != nil {
t.Fatal(err)
}
if _, err := st.DB().Exec(`UPDATE users SET enabled=FALSE WHERE owner=?`, "alice"); err != nil {
t.Fatal(err)
}
if err := h.KVPut("after", "denied"); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host wrote KV: %v", err)
}
if _, _, err := h.KVGet("before"); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host read KV: %v", err)
}
if _, err := h.TaskCreate("test", "denied", ""); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host created task: %v", err)
}
if _, err := h.ProjectNameAvailable("denied", ""); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host used project capability: %v", err)
}
if err := h.Audit("action", "target", ""); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host wrote audit: %v", err)
}
if _, err := h.Complete(context.Background(), "denied"); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host reached model completion: %v", err)
}
if _, err := h.KnowledgeSearch(context.Background(), "", "denied", 1); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("disabled host read knowledge: %v", err)
}
}
func TestHostCompletionChecksAgainAfterModelSettingsRead(t *testing.T) {
st, err := store.OpenSQLite(t.TempDir() + "/host-complete.db")
if err != nil {
t.Fatal(err)
}
defer st.Close()
requests := 0
model := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
requests++
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"late"}}]}`))
}))
defer model.Close()
set := settings.NewService(st)
set.SetSystemOverride(map[string]any{"models": map[string]any{"chat": map[string]any{"model": "test", "baseUrl": model.URL}}})
checks := 0
h := inprocHost{caps: hostcaps{st: st, set: set, accountGuard: func(string) error {
checks++
if checks >= 3 {
return store.ErrAccountForbidden
}
return nil
}}, owner: "alice", actor: "alice", pluginID: "rdm"}
if _, err := h.Complete(context.Background(), "late"); !errors.Is(err, store.ErrAccountForbidden) {
t.Fatalf("completion after disable: %v", err)
}
if checks != 3 || requests != 0 {
t.Fatalf("late completion reached model: checks=%d requests=%d", checks, requests)
}
}
func TestImplicitLocalAccountExceptionIsAuthOffOnly(t *testing.T) {
st, err := store.OpenSQLite(t.TempDir() + "/local.db")
if err != nil {
t.Fatal(err)
}
defer st.Close()
allowed, err := accountEffectiveAuthorization(st, false)(context.Background(), store.OwnerDefault)
if err != nil || !allowed {
t.Fatalf("implicit local denied: %v %v", allowed, err)
}
allowed, err = accountEffectiveAuthorization(st, true)(context.Background(), store.OwnerDefault)
if !errors.Is(err, store.ErrNotFound) || allowed {
t.Fatalf("auth-on missing account allowed: %v %v", allowed, err)
}
if err := st.EnsureUser(store.OwnerDefault, ""); err != nil {
t.Fatal(err)
}
if _, err := st.DB().Exec(`UPDATE users SET enabled=FALSE WHERE owner=?`, store.OwnerDefault); err != nil {
t.Fatal(err)
}
allowed, err = accountEffectiveAuthorization(st, false)(context.Background(), store.OwnerDefault)
if err != nil || allowed {
t.Fatalf("disabled existing local account allowed: %v %v", allowed, err)
}
}