391 lines
9.2 KiB
Go
391 lines
9.2 KiB
Go
|
|
package iam
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"testing"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestP102_ParseScope 解析scope字符串
|
|||
|
|
func TestP102_ParseScope(t *testing.T) {
|
|||
|
|
testCases := []struct {
|
|||
|
|
name string
|
|||
|
|
scopeStr string
|
|||
|
|
wantErr bool
|
|||
|
|
domain string
|
|||
|
|
resource string
|
|||
|
|
action string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid supply scope",
|
|||
|
|
scopeStr: "supply:accounts:read",
|
|||
|
|
wantErr: false,
|
|||
|
|
domain: "supply",
|
|||
|
|
resource: "accounts",
|
|||
|
|
action: "read",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "valid billing scope",
|
|||
|
|
scopeStr: "billing:settlements:write",
|
|||
|
|
wantErr: false,
|
|||
|
|
domain: "billing",
|
|||
|
|
resource: "settlements",
|
|||
|
|
action: "write",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "invalid format",
|
|||
|
|
scopeStr: "supply:accounts",
|
|||
|
|
wantErr: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "invalid domain",
|
|||
|
|
scopeStr: "unknown:accounts:read",
|
|||
|
|
wantErr: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "invalid action",
|
|||
|
|
scopeStr: "supply:accounts:unknown",
|
|||
|
|
wantErr: true,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tc := range testCases {
|
|||
|
|
t.Run(tc.name, func(t *testing.T) {
|
|||
|
|
scope, err := ParseScope(tc.scopeStr)
|
|||
|
|
|
|||
|
|
if tc.wantErr {
|
|||
|
|
if err == nil {
|
|||
|
|
t.Error("expected error but got nil")
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if err != nil {
|
|||
|
|
t.Errorf("unexpected error: %v", err)
|
|||
|
|
}
|
|||
|
|
if scope.Domain != tc.domain {
|
|||
|
|
t.Errorf("domain: expected %s, got %s", tc.domain, scope.Domain)
|
|||
|
|
}
|
|||
|
|
if scope.Resource != tc.resource {
|
|||
|
|
t.Errorf("resource: expected %s, got %s", tc.resource, scope.Resource)
|
|||
|
|
}
|
|||
|
|
if scope.Action != tc.action {
|
|||
|
|
t.Errorf("action: expected %s, got %s", tc.action, scope.Action)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: scope解析验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_HasScope 检查权限
|
|||
|
|
func TestP102_HasScope(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:read",
|
|||
|
|
"supply:accounts:write",
|
|||
|
|
"supply:packages:read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
testCases := []struct {
|
|||
|
|
name string
|
|||
|
|
checkScope *Scope
|
|||
|
|
expected bool
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "has exact scope",
|
|||
|
|
checkScope: &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "read",
|
|||
|
|
},
|
|||
|
|
expected: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "has exact scope write",
|
|||
|
|
checkScope: &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "write",
|
|||
|
|
},
|
|||
|
|
expected: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "has manage scope",
|
|||
|
|
checkScope: &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "manage",
|
|||
|
|
},
|
|||
|
|
expected: false, // 用户没有manage但有read/write
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "missing scope",
|
|||
|
|
checkScope: &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "orders",
|
|||
|
|
Action: "read",
|
|||
|
|
},
|
|||
|
|
expected: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "admin has all",
|
|||
|
|
checkScope: &Scope{
|
|||
|
|
Domain: "billing",
|
|||
|
|
Resource: "ledgers",
|
|||
|
|
Action: "read",
|
|||
|
|
},
|
|||
|
|
expected: false, // 没有admin scope
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tc := range testCases {
|
|||
|
|
t.Run(tc.name, func(t *testing.T) {
|
|||
|
|
result := HasScope(userScopes, tc.checkScope)
|
|||
|
|
if result != tc.expected {
|
|||
|
|
t.Errorf("expected %v, got %v", tc.expected, result)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: HasScope验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_HasScopeWithManage 验证manage权限
|
|||
|
|
func TestP102_HasScopeWithManage(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:manage", // manage包含所有account操作
|
|||
|
|
"supply:packages:read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查manage是否包含read
|
|||
|
|
readScope := &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !HasScope(userScopes, readScope) {
|
|||
|
|
t.Error("manage should include read")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查manage是否包含write
|
|||
|
|
writeScope := &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "write",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !HasScope(userScopes, writeScope) {
|
|||
|
|
t.Error("manage should include write")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查manage是否包含delete
|
|||
|
|
deleteScope := &Scope{
|
|||
|
|
Domain: "supply",
|
|||
|
|
Resource: "accounts",
|
|||
|
|
Action: "delete",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !HasScope(userScopes, deleteScope) {
|
|||
|
|
t.Error("manage should include delete")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: manage权限验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_HasAnyScope 任一权限检查
|
|||
|
|
func TestP102_HasAnyScope(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "read"},
|
|||
|
|
{Domain: "supply", Resource: "packages", Action: "read"},
|
|||
|
|
{Domain: "billing", Resource: "ledgers", Action: "read"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !HasAnyScope(userScopes, requiredScopes) {
|
|||
|
|
t.Error("should return true when user has at least one scope")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: HasAnyScope验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_CommonScopes 常用scope定义
|
|||
|
|
func TestP102_CommonScopes(t *testing.T) {
|
|||
|
|
// 验证常用scope格式正确
|
|||
|
|
scopes := []string{
|
|||
|
|
CommonScopes.SupplyAccountsRead,
|
|||
|
|
CommonScopes.SupplyAccountsWrite,
|
|||
|
|
CommonScopes.SupplyAccountsDelete,
|
|||
|
|
CommonScopes.SupplyAccountsManage,
|
|||
|
|
CommonScopes.SupplyPackagesRead,
|
|||
|
|
CommonScopes.BillingAccountsRead,
|
|||
|
|
CommonScopes.BillingSettlementsWrite,
|
|||
|
|
CommonScopes.IAMUsersRead,
|
|||
|
|
CommonScopes.AuditEventsRead,
|
|||
|
|
CommonScopes.AdminAll,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, scopeStr := range scopes {
|
|||
|
|
scope, err := ParseScope(scopeStr)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Errorf("invalid common scope %s: %v", scopeStr, err)
|
|||
|
|
}
|
|||
|
|
if scope == nil {
|
|||
|
|
t.Errorf("failed to parse common scope %s", scopeStr)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: 常用scope定义验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_RoleScopes 角色默认scope
|
|||
|
|
func TestP102_RoleScopes(t *testing.T) {
|
|||
|
|
roles := []string{"viewer", "operator", "admin", "owner"}
|
|||
|
|
|
|||
|
|
for _, role := range roles {
|
|||
|
|
scopes := GetScopesForRole(role)
|
|||
|
|
if len(scopes) == 0 {
|
|||
|
|
t.Errorf("role %s should have scopes", role)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证每个scope都能正确解析
|
|||
|
|
for _, scopeStr := range scopes {
|
|||
|
|
_, err := ParseScope(scopeStr)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Errorf("role %s has invalid scope %s: %v", role, scopeStr, err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Log("P1-02: 角色默认scope验证通过")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestP102_Summary 测试总结
|
|||
|
|
func TestP102_Summary(t *testing.T) {
|
|||
|
|
t.Log("=== P1-02 Token Scope授权模型测试总结 ===")
|
|||
|
|
t.Log("问题: Token runtime定义scope为string[],但未定义scope命名空间和授权规则")
|
|||
|
|
t.Log("")
|
|||
|
|
t.Log("修复方案:")
|
|||
|
|
t.Log(" - scope格式: {domain}:{resource}:{action}")
|
|||
|
|
t.Log(" - 域: supply, billing, iam, audit, admin")
|
|||
|
|
t.Log(" - 动作: read, write, delete, manage, execute")
|
|||
|
|
t.Log(" - manage权限包含所有其他权限")
|
|||
|
|
t.Log(" - admin:admin:manage 包含所有权限")
|
|||
|
|
t.Log("")
|
|||
|
|
t.Log("示例scope:")
|
|||
|
|
t.Log(" supply:accounts:read")
|
|||
|
|
t.Log(" billing:settlements:write")
|
|||
|
|
t.Log(" iam:users:manage")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAllScopes_True tests HasAllScopes when user has all required scopes
|
|||
|
|
func TestHasAllScopes_True(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:read",
|
|||
|
|
"supply:accounts:write",
|
|||
|
|
"supply:accounts:delete",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "read"},
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "write"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := HasAllScopes(userScopes, requiredScopes)
|
|||
|
|
if !result {
|
|||
|
|
t.Error("expected true, user has all required scopes")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAllScopes_False tests HasAllScopes when user is missing some scopes
|
|||
|
|
func TestHasAllScopes_False(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "read"},
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "write"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := HasAllScopes(userScopes, requiredScopes)
|
|||
|
|
if result {
|
|||
|
|
t.Error("expected false, user is missing write scope")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAllScopes_EmptyRequired tests HasAllScopes with empty required scopes
|
|||
|
|
func TestHasAllScopes_EmptyRequired(t *testing.T) {
|
|||
|
|
userScopes := []string{"supply:accounts:read"}
|
|||
|
|
|
|||
|
|
result := HasAllScopes(userScopes, []*Scope{})
|
|||
|
|
if !result {
|
|||
|
|
t.Error("expected true, empty required scopes should return true")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAllScopes_EmptyUser tests HasAllScopes with empty user scopes
|
|||
|
|
func TestHasAllScopes_EmptyUser(t *testing.T) {
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "read"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := HasAllScopes([]string{}, requiredScopes)
|
|||
|
|
if result {
|
|||
|
|
t.Error("expected false, user has no scopes")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestInvalidScopeError tests the InvalidScopeError type
|
|||
|
|
func TestInvalidScopeError(t *testing.T) {
|
|||
|
|
err := &InvalidScopeError{
|
|||
|
|
Scope: "invalid:scope:format",
|
|||
|
|
Reason: "invalid format",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := err.Error()
|
|||
|
|
expected := "invalid scope 'invalid:scope:format': invalid format"
|
|||
|
|
if result != expected {
|
|||
|
|
t.Errorf("expected '%s', got '%s'", expected, result)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAnyScope_False tests HasAnyScope when user has no matching scopes
|
|||
|
|
func TestHasAnyScope_False(t *testing.T) {
|
|||
|
|
userScopes := []string{
|
|||
|
|
"supply:accounts:read",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "billing", Resource: "ledgers", Action: "read"},
|
|||
|
|
{Domain: "iam", Resource: "users", Action: "write"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := HasAnyScope(userScopes, requiredScopes)
|
|||
|
|
if result {
|
|||
|
|
t.Error("expected false, user has no matching scopes")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAnyScope_EmptyRequired tests HasAnyScope with empty required scopes
|
|||
|
|
func TestHasAnyScope_EmptyRequired(t *testing.T) {
|
|||
|
|
userScopes := []string{"supply:accounts:read"}
|
|||
|
|
|
|||
|
|
result := HasAnyScope(userScopes, []*Scope{})
|
|||
|
|
if result {
|
|||
|
|
t.Error("expected false, empty required scopes should return false")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestHasAnyScope_EmptyUser tests HasAnyScope with empty user scopes
|
|||
|
|
func TestHasAnyScope_EmptyUser(t *testing.T) {
|
|||
|
|
requiredScopes := []*Scope{
|
|||
|
|
{Domain: "supply", Resource: "accounts", Action: "read"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := HasAnyScope([]string{}, requiredScopes)
|
|||
|
|
if result {
|
|||
|
|
t.Error("expected false, user has no scopes")
|
|||
|
|
}
|
|||
|
|
}
|