|
|
|
@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
package auth
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
|
|
"context"
|
|
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/veypi/vbase/cfg"
|
|
|
|
|
|
|
|
"github.com/veypi/vbase/models"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// setupTestDB 初始化测试数据库
|
|
|
|
|
|
|
|
func setupTestDB() {
|
|
|
|
|
|
|
|
// 使用 SQLite 内存模式
|
|
|
|
|
|
|
|
cfg.Config.DB = "sqlite"
|
|
|
|
|
|
|
|
cfg.Config.DSN = fmt.Sprintf("file::memory:?cache=shared&_time=%d", time.Now().UnixNano())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据库表
|
|
|
|
|
|
|
|
if err := models.AllModels.AutoMigrate(cfg.DB()); err != nil {
|
|
|
|
|
|
|
|
panic(err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestMain 负责测试环境的全局初始化
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
|
|
|
|
setupTestDB()
|
|
|
|
|
|
|
|
code := m.Run()
|
|
|
|
|
|
|
|
os.Exit(code)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func getTestAuth() Auth {
|
|
|
|
|
|
|
|
// 每次获取一个新的实例名,避免测试间冲突(虽然内存库是共享的,但数据清理可能不完全)
|
|
|
|
|
|
|
|
// 为了简单起见,我们在 TestMain 中只初始化一次 DB,但可以通过应用名区分
|
|
|
|
|
|
|
|
appKey := fmt.Sprintf("test_app_%d", time.Now().UnixNano())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a := Factory.New(appKey, models.AppConfig{
|
|
|
|
|
|
|
|
Name: "Test App",
|
|
|
|
|
|
|
|
DefaultRoles: []models.RoleDefinition{
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "admin",
|
|
|
|
|
|
|
|
Name: "Administrator",
|
|
|
|
|
|
|
|
Policies: []string{"*:*"},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "editor",
|
|
|
|
|
|
|
|
Name: "Editor",
|
|
|
|
|
|
|
|
Policies: []string{
|
|
|
|
|
|
|
|
"article:create",
|
|
|
|
|
|
|
|
"article:read",
|
|
|
|
|
|
|
|
"article:update",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "viewer",
|
|
|
|
|
|
|
|
Name: "Viewer",
|
|
|
|
|
|
|
|
Policies: []string{
|
|
|
|
|
|
|
|
"article:read",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "deleter",
|
|
|
|
|
|
|
|
Name: "Deleter",
|
|
|
|
|
|
|
|
Policies: []string{
|
|
|
|
|
|
|
|
"article:delete",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
|
|
if err := a.(*appAuth).init(); err != nil {
|
|
|
|
|
|
|
|
panic(err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return a
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestBasicPermission 测试基础权限授予与检查
|
|
|
|
|
|
|
|
func TestBasicPermission(t *testing.T) {
|
|
|
|
|
|
|
|
a := getTestAuth()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
userID := "user_basic_001"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始状态应该无权限
|
|
|
|
|
|
|
|
ok, err := a.CheckPermission(ctx, userID, "", "article:read", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no permission initially")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 授予 viewer 角色
|
|
|
|
|
|
|
|
if err := a.GrantRole(ctx, userID, "", "viewer"); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("GrantRole failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 应该有 article:read 权限
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "article:read", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
t.Error("Expected article:read permission after granting viewer role")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 不应该有 article:create 权限
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "article:create", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no article:create permission for viewer")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestWildcardPermission 测试通配符权限
|
|
|
|
|
|
|
|
func TestWildcardPermission(t *testing.T) {
|
|
|
|
|
|
|
|
a := getTestAuth()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
userID := "user_admin_001"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 授予 admin 角色 (*:*)
|
|
|
|
|
|
|
|
if err := a.GrantRole(ctx, userID, "", "admin"); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("GrantRole failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 应该拥有所有权限
|
|
|
|
|
|
|
|
tests := []string{
|
|
|
|
|
|
|
|
"article:read",
|
|
|
|
|
|
|
|
"article:delete",
|
|
|
|
|
|
|
|
"user:create",
|
|
|
|
|
|
|
|
"config:update", // system:config:update 会被解析为 system 应用的权限,而 admin 是 test_app 的 admin
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, perm := range tests {
|
|
|
|
|
|
|
|
ok, err := a.CheckPermission(ctx, userID, "", perm, "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Errorf("CheckPermission %s failed: %v", perm, err)
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
t.Errorf("Expected permission %s for admin", perm)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestStrictPermissionIsolation 验证权限严格隔离性 (用户请求场景)
|
|
|
|
|
|
|
|
// 验证:拥有 delete 权限的用户,是否能通过 read 权限检查
|
|
|
|
|
|
|
|
func TestStrictPermissionIsolation(t *testing.T) {
|
|
|
|
|
|
|
|
a := getTestAuth()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
userID := "user_deleter_001"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 授予 deleter 角色 (只包含 article:delete)
|
|
|
|
|
|
|
|
if err := a.GrantRole(ctx, userID, "", "deleter"); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("GrantRole failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查 article:delete (应该通过)
|
|
|
|
|
|
|
|
ok, err := a.CheckPermission(ctx, userID, "", "article:delete", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission error: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
t.Errorf("Expected user to have article:delete permission")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查 article:read (应该失败)
|
|
|
|
|
|
|
|
// 关键验证点:delete 不包含 read
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "article:read", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission error: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Errorf("Strict isolation failed: User with 'article:delete' passed 'article:read' check")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestOrgIsolation 测试多租户/组织隔离
|
|
|
|
|
|
|
|
func TestOrgIsolation(t *testing.T) {
|
|
|
|
|
|
|
|
a := getTestAuth()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
userID := "user_org_001"
|
|
|
|
|
|
|
|
orgA := "org_a"
|
|
|
|
|
|
|
|
orgB := "org_b"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 OrgA 中授予 editor 角色
|
|
|
|
|
|
|
|
if err := a.GrantRole(ctx, userID, orgA, "editor"); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("GrantRole failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 OrgA 上下文中检查 article:create (应该通过)
|
|
|
|
|
|
|
|
ok, err := a.CheckPermission(ctx, userID, orgA, "article:create", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
t.Error("Expected permission in OrgA")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 OrgB 上下文中检查 article:create (应该失败)
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, orgB, "article:create", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no permission in OrgB (role was granted in OrgA)")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在全局上下文中检查 (应该失败,因为角色绑定在 OrgA)
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "article:create", "")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no global permission")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TestResourcePermission 测试特定资源权限
|
|
|
|
|
|
|
|
func TestResourcePermission(t *testing.T) {
|
|
|
|
|
|
|
|
a := getTestAuth()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
userID := "user_res_001"
|
|
|
|
|
|
|
|
resID := "doc_123"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 需要先创建权限定义,因为 GrantResourcePerm 会检查权限是否存在
|
|
|
|
|
|
|
|
permID := fmt.Sprintf("%s:doc:read", a.(*appAuth).appKey)
|
|
|
|
|
|
|
|
perm := models.Permission{
|
|
|
|
|
|
|
|
ID: permID,
|
|
|
|
|
|
|
|
AppKey: a.(*appAuth).appKey,
|
|
|
|
|
|
|
|
Resource: "doc",
|
|
|
|
|
|
|
|
Action: "read",
|
|
|
|
|
|
|
|
Description: "Read Doc",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cfg.DB().Create(&perm).Error; err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("Failed to create permission: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始无权限
|
|
|
|
|
|
|
|
ok, err := a.CheckPermission(ctx, userID, "", "doc:read", resID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no permission")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 授予对特定资源 doc_123 的 doc:read 权限
|
|
|
|
|
|
|
|
if err := a.GrantResourcePerm(ctx, userID, "", "doc:read", resID); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("GrantResourcePerm failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 doc:read on doc_123 (应该通过)
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "doc:read", resID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
t.Error("Expected permission on specific resource")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 doc:read on doc_456 (应该失败)
|
|
|
|
|
|
|
|
ok, err = a.CheckPermission(ctx, userID, "", "doc:read", "doc_456")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("CheckPermission failed: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
t.Error("Expected no permission on other resource")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|