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) } }