Files
Agent-System-Backend/internal/app/resource_authz_v2_test.go
T
weichen 06ef17a70a Harden delegated role authority boundaries
Why: Independent review found delegated account actions, stale resource policy, and role migration cutover gaps in the initial U4 implementation.

Evidence: Backend full SQLite suite passed; isolated PostgreSQL role and migration suite passed; independent final review found no remaining findings.

Impact: Enforces transactional system grants, current role snapshots, staged role import with v36 migration, and fail-closed activation.

Refs: feature/iam-u4-role-authority
2026-10-15 02:27:50 +00:00

90 lines
4.1 KiB
Go
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.
package app
// EXT-8b 资源级授权槽位测试(U9 首切片):
// 1. V2 未注册 → AuthorizeResource 回落 V1(owner×project 兼容现行);
// 2. V2 注册 → 按查询六要素咨询;受信字段缺失(actor/action/type/id 空)=拒绝;
// 3. V2 返回 error = 拒绝(fail-closed);
// 4. 首注册者生效(后来者忽略);
// 5. compiledRegistry 的 V2 注册被拒(声明式无宿主回调)。
import (
"errors"
"testing"
"agent-system-backend/pkg/extension"
)
func TestAuthorizeResourceV2FallbackAndQuery(t *testing.T) {
var a projectAuthz
// 未注册任何适配器 → 拒绝。
if ok, err := a.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
Owner: "u2", ProjectID: "p1"}); ok || err != nil {
t.Fatalf("未注册应拒绝, got %v %v", ok, err)
}
// 仅 V1:回落 owner×project 布尔。
a.set("p1", func(owner string, projectID string) (bool, error) { return true, nil })
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
Owner: "u2", ProjectID: "p1"}); !ok {
t.Fatal("V1 允许时回落应放行")
}
// V1 路径要求 owner/project 非空。
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1"}); ok {
t.Fatal("V1 回落在无项目私域应拒绝")
}
// 注册 V2 → 按六要素咨询。
a.setV2("p1", func(q extension.ResourceAuthzQuery) (bool, error) {
return q.Actor == "u1" && q.Action == "download" && q.ResourceType == "artifact" &&
q.ResourceID == "a1" && q.Owner == "u2" && q.ProjectID == "p1", nil
})
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
Owner: "u2", ProjectID: "p1"}); !ok {
t.Fatal("V2 六要素齐备应放行")
}
// 六要素缺一(action 空)=拒绝(不可判定)。
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u1", ResourceType: "artifact", ResourceID: "a1", Owner: "u2", ProjectID: "p1"}); ok {
t.Fatal("受信字段缺失应拒绝")
}
// V2 出错=拒绝(fail-closed)。
a2 := projectAuthz{}
a2.setV2("p1", func(extension.ResourceAuthzQuery) (bool, error) { return false, errors.New("boom") })
if ok, err := a2.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u", Action: "read", ResourceType: "x", ResourceID: "1"}); ok || err == nil {
t.Fatal("V2 error 应拒绝")
}
// 首注册者生效。
a3 := projectAuthz{}
a3.setV2("first", func(extension.ResourceAuthzQuery) (bool, error) { return true, nil })
a3.setV2("second", func(extension.ResourceAuthzQuery) (bool, error) { return false, nil })
if ok, _ := a3.AuthorizeResource(extension.ResourceAuthzQuery{
Actor: "u", Action: "read", ResourceType: "x", ResourceID: "1"}); !ok {
t.Fatal("首个 V2 注册者应生效")
}
}
func TestCompiledRegistryRejectsV2(t *testing.T) {
r := compiledRegistry{}
if err := r.RegisterResourceAuthorizerV2(func(extension.ResourceAuthzQuery) (bool, error) { return true, nil }); err == nil {
t.Fatal("声明式扩展不得注册资源级授权回调(fail-closed)")
}
}
func TestAuthorizeResourceEnrichesEveryInProcessQueryWithCurrentRolePolicy(t *testing.T) {
snapshot := extension.RolePolicySnapshot{SchemaVersion: extension.RolePolicySchemaVersion,
Owner: "alice", AccountEnabled: true, RoleID: "role_1", RoleEnabled: true, AuthorityEpoch: 7}
a := projectAuthz{enrich: func(q extension.ResourceAuthzQuery) (extension.ResourceAuthzQuery, error) {
q.AuthorityEpoch = snapshot.AuthorityEpoch
q.RolePolicy = &snapshot
return q, nil
}}
a.setV2("rdm", func(q extension.ResourceAuthzQuery) (bool, error) {
return q.AuthorityEpoch == 7 && q.RolePolicy != nil && q.RolePolicy.Owner == q.Actor, nil
})
if ok, err := a.AuthorizeResource(extension.ResourceAuthzQuery{Actor: "alice", Action: "search", ResourceType: "knowledge", ResourceID: "k1"}); err != nil || !ok {
t.Fatalf("enriched query ok=%v err=%v", ok, err)
}
}