595 lines
16 KiB
Go
595 lines
16 KiB
Go
package test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/kms/api-key-service/internal/auth"
|
|
)
|
|
|
|
func TestPermissionHierarchy_InitializeDefaultPermissions(t *testing.T) {
|
|
hierarchy := auth.NewPermissionHierarchy()
|
|
|
|
// Test that default permissions are created
|
|
permissions := hierarchy.ListPermissions()
|
|
assert.NotEmpty(t, permissions)
|
|
|
|
// Test specific permissions exist
|
|
permissionNames := make(map[string]bool)
|
|
for _, perm := range permissions {
|
|
permissionNames[perm.Name] = true
|
|
}
|
|
|
|
expectedPermissions := []string{
|
|
"admin", "read", "write",
|
|
"app.admin", "app.read", "app.write", "app.create", "app.update", "app.delete",
|
|
"token.admin", "token.read", "token.write", "token.create", "token.revoke", "token.verify",
|
|
"permission.admin", "permission.read", "permission.write", "permission.grant", "permission.revoke",
|
|
"user.admin", "user.read", "user.write",
|
|
}
|
|
|
|
for _, expected := range expectedPermissions {
|
|
assert.True(t, permissionNames[expected], "Permission %s should exist", expected)
|
|
}
|
|
}
|
|
|
|
func TestPermissionHierarchy_InitializeDefaultRoles(t *testing.T) {
|
|
hierarchy := auth.NewPermissionHierarchy()
|
|
|
|
// Test that default roles are created
|
|
roles := hierarchy.ListRoles()
|
|
assert.NotEmpty(t, roles)
|
|
|
|
// Test specific roles exist
|
|
roleNames := make(map[string]bool)
|
|
for _, role := range roles {
|
|
roleNames[role.Name] = true
|
|
}
|
|
|
|
expectedRoles := []string{
|
|
"super_admin", "app_admin", "developer", "viewer", "token_manager",
|
|
}
|
|
|
|
for _, expected := range expectedRoles {
|
|
assert.True(t, roleNames[expected], "Role %s should exist", expected)
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_HasPermission(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
configMock.values["CACHE_ENABLED"] = "false" // Disable cache for testing
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
userID string
|
|
appID string
|
|
permission string
|
|
expectedResult bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "admin user has admin permission",
|
|
userID: "admin@example.com",
|
|
appID: "test-app",
|
|
permission: "admin",
|
|
expectedResult: true,
|
|
description: "Admin users should have admin permissions",
|
|
},
|
|
{
|
|
name: "developer user has token.create permission",
|
|
userID: "dev@example.com",
|
|
appID: "test-app",
|
|
permission: "token.create",
|
|
expectedResult: true,
|
|
description: "Developer users should have token creation permissions",
|
|
},
|
|
{
|
|
name: "viewer user has read permission",
|
|
userID: "viewer@example.com",
|
|
appID: "test-app",
|
|
permission: "app.read",
|
|
expectedResult: true,
|
|
description: "Viewer users should have read permissions",
|
|
},
|
|
{
|
|
name: "viewer user denied write permission",
|
|
userID: "viewer@example.com",
|
|
appID: "test-app",
|
|
permission: "app.write",
|
|
expectedResult: false,
|
|
description: "Viewer users should not have write permissions",
|
|
},
|
|
{
|
|
name: "non-existent permission",
|
|
userID: "admin@example.com",
|
|
appID: "test-app",
|
|
permission: "non.existent",
|
|
expectedResult: false,
|
|
description: "Non-existent permissions should be denied",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
evaluation, err := pm.HasPermission(ctx, tt.userID, tt.appID, tt.permission)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, evaluation)
|
|
assert.Equal(t, tt.expectedResult, evaluation.Granted, tt.description)
|
|
assert.Equal(t, tt.permission, evaluation.Permission)
|
|
assert.NotZero(t, evaluation.EvaluatedAt)
|
|
|
|
if evaluation.Granted {
|
|
assert.NotEmpty(t, evaluation.GrantedBy, "Granted permissions should have GrantedBy information")
|
|
} else {
|
|
assert.NotEmpty(t, evaluation.DeniedReason, "Denied permissions should have a reason")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_EvaluateBulkPermissions(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
configMock.values["CACHE_ENABLED"] = "false"
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
ctx := context.Background()
|
|
req := &auth.BulkPermissionRequest{
|
|
UserID: "dev@example.com",
|
|
AppID: "test-app",
|
|
Permissions: []string{
|
|
"app.read",
|
|
"token.create",
|
|
"token.read",
|
|
"app.delete", // Should be denied for developer
|
|
"admin", // Should be denied for developer
|
|
},
|
|
}
|
|
|
|
response, err := pm.EvaluateBulkPermissions(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, response)
|
|
assert.Equal(t, req.UserID, response.UserID)
|
|
assert.Equal(t, req.AppID, response.AppID)
|
|
assert.Len(t, response.Results, len(req.Permissions))
|
|
|
|
// Check specific results
|
|
assert.True(t, response.Results["app.read"].Granted, "Developer should have app.read permission")
|
|
assert.True(t, response.Results["token.create"].Granted, "Developer should have token.create permission")
|
|
assert.True(t, response.Results["token.read"].Granted, "Developer should have token.read permission")
|
|
assert.False(t, response.Results["app.delete"].Granted, "Developer should not have app.delete permission")
|
|
assert.False(t, response.Results["admin"].Granted, "Developer should not have admin permission")
|
|
}
|
|
|
|
func TestPermissionManager_AddPermission(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
permission *auth.Permission
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "add valid permission",
|
|
permission: &auth.Permission{
|
|
Name: "custom.permission",
|
|
Description: "Custom permission for testing",
|
|
Parent: "read",
|
|
Level: 2,
|
|
Resource: "custom",
|
|
Action: "test",
|
|
},
|
|
expectError: false,
|
|
description: "Valid permissions should be added successfully",
|
|
},
|
|
{
|
|
name: "add permission without name",
|
|
permission: &auth.Permission{
|
|
Description: "Permission without name",
|
|
Parent: "read",
|
|
Level: 2,
|
|
},
|
|
expectError: true,
|
|
description: "Permissions without names should be rejected",
|
|
},
|
|
{
|
|
name: "add permission with non-existent parent",
|
|
permission: &auth.Permission{
|
|
Name: "invalid.permission",
|
|
Description: "Permission with invalid parent",
|
|
Parent: "non.existent",
|
|
Level: 2,
|
|
},
|
|
expectError: true,
|
|
description: "Permissions with non-existent parents should be rejected",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := pm.AddPermission(tt.permission)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, tt.description)
|
|
} else {
|
|
assert.NoError(t, err, tt.description)
|
|
|
|
// Verify permission was added
|
|
permissions := pm.ListPermissions()
|
|
found := false
|
|
for _, perm := range permissions {
|
|
if perm.Name == tt.permission.Name {
|
|
found = true
|
|
assert.Equal(t, tt.permission.Description, perm.Description)
|
|
assert.Equal(t, tt.permission.Parent, perm.Parent)
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Added permission should be found in the list")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_AddRole(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
role *auth.Role
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "add valid role",
|
|
role: &auth.Role{
|
|
Name: "custom_role",
|
|
Description: "Custom role for testing",
|
|
Permissions: []string{"read", "app.read"},
|
|
Metadata: map[string]string{"level": "custom"},
|
|
},
|
|
expectError: false,
|
|
description: "Valid roles should be added successfully",
|
|
},
|
|
{
|
|
name: "add role without name",
|
|
role: &auth.Role{
|
|
Description: "Role without name",
|
|
Permissions: []string{"read"},
|
|
},
|
|
expectError: true,
|
|
description: "Roles without names should be rejected",
|
|
},
|
|
{
|
|
name: "add role with non-existent permission",
|
|
role: &auth.Role{
|
|
Name: "invalid_role",
|
|
Description: "Role with invalid permission",
|
|
Permissions: []string{"non.existent.permission"},
|
|
},
|
|
expectError: true,
|
|
description: "Roles with non-existent permissions should be rejected",
|
|
},
|
|
{
|
|
name: "add role with non-existent inherited role",
|
|
role: &auth.Role{
|
|
Name: "invalid_inherited_role",
|
|
Description: "Role with invalid inheritance",
|
|
Permissions: []string{"read"},
|
|
Inherits: []string{"non_existent_role"},
|
|
},
|
|
expectError: true,
|
|
description: "Roles with non-existent inherited roles should be rejected",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := pm.AddRole(tt.role)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, tt.description)
|
|
} else {
|
|
assert.NoError(t, err, tt.description)
|
|
|
|
// Verify role was added
|
|
roles := pm.ListRoles()
|
|
found := false
|
|
for _, role := range roles {
|
|
if role.Name == tt.role.Name {
|
|
found = true
|
|
assert.Equal(t, tt.role.Description, role.Description)
|
|
assert.Equal(t, tt.role.Permissions, role.Permissions)
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Added role should be found in the list")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_ListPermissions(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
permissions := pm.ListPermissions()
|
|
|
|
// Should have default permissions
|
|
assert.NotEmpty(t, permissions)
|
|
|
|
// Should be sorted by level and name
|
|
for i := 1; i < len(permissions); i++ {
|
|
prev := permissions[i-1]
|
|
curr := permissions[i]
|
|
|
|
if prev.Level == curr.Level {
|
|
assert.True(t, prev.Name <= curr.Name, "Permissions at same level should be sorted by name")
|
|
} else {
|
|
assert.True(t, prev.Level <= curr.Level, "Permissions should be sorted by level")
|
|
}
|
|
}
|
|
|
|
// Verify hierarchy structure
|
|
for _, perm := range permissions {
|
|
if perm.Parent != "" {
|
|
// Find parent permission
|
|
parentFound := false
|
|
for _, parent := range permissions {
|
|
if parent.Name == perm.Parent {
|
|
parentFound = true
|
|
assert.True(t, parent.Level < perm.Level, "Parent should have lower level than child")
|
|
assert.Contains(t, parent.Children, perm.Name, "Parent should contain child in children list")
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, parentFound, "Parent permission should exist for %s", perm.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_ListRoles(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
roles := pm.ListRoles()
|
|
|
|
// Should have default roles
|
|
assert.NotEmpty(t, roles)
|
|
|
|
// Should be sorted by name
|
|
for i := 1; i < len(roles); i++ {
|
|
assert.True(t, roles[i-1].Name <= roles[i].Name, "Roles should be sorted by name")
|
|
}
|
|
|
|
// Verify all permissions in roles exist
|
|
allPermissions := pm.ListPermissions()
|
|
permissionNames := make(map[string]bool)
|
|
for _, perm := range allPermissions {
|
|
permissionNames[perm.Name] = true
|
|
}
|
|
|
|
for _, role := range roles {
|
|
for _, perm := range role.Permissions {
|
|
assert.True(t, permissionNames[perm], "Role %s references non-existent permission %s", role.Name, perm)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPermissionManager_InvalidatePermissionCache(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
ctx := context.Background()
|
|
err := pm.InvalidatePermissionCache(ctx, "user123", "app123")
|
|
|
|
// Should not error (currently just logs)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPermissionHierarchy_BuildHierarchy(t *testing.T) {
|
|
hierarchy := auth.NewPermissionHierarchy()
|
|
|
|
// Test that parent-child relationships are built correctly
|
|
permissions := hierarchy.ListPermissions()
|
|
|
|
// Find admin permission
|
|
var adminPerm *auth.Permission
|
|
for _, perm := range permissions {
|
|
if perm.Name == "admin" {
|
|
adminPerm = perm
|
|
break
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, adminPerm, "Admin permission should exist")
|
|
|
|
// Admin should have children
|
|
assert.NotEmpty(t, adminPerm.Children, "Admin permission should have children")
|
|
|
|
// Check that app.admin is a child of admin
|
|
assert.Contains(t, adminPerm.Children, "app.admin", "app.admin should be a child of admin")
|
|
|
|
// Find app.write permission
|
|
var appWritePerm *auth.Permission
|
|
for _, perm := range permissions {
|
|
if perm.Name == "app.write" {
|
|
appWritePerm = perm
|
|
break
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, appWritePerm, "app.write permission should exist")
|
|
|
|
// app.write should have children
|
|
assert.NotEmpty(t, appWritePerm.Children, "app.write permission should have children")
|
|
assert.Contains(t, appWritePerm.Children, "app.create", "app.create should be a child of app.write")
|
|
assert.Contains(t, appWritePerm.Children, "app.update", "app.update should be a child of app.write")
|
|
assert.Contains(t, appWritePerm.Children, "app.delete", "app.delete should be a child of app.write")
|
|
}
|
|
|
|
// Benchmark tests for permission operations
|
|
func BenchmarkPermissionManager_HasPermission(b *testing.B) {
|
|
configMock := NewTestConfig()
|
|
configMock.values["CACHE_ENABLED"] = "false"
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := pm.HasPermission(ctx, "dev@example.com", "test-app", "token.create")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkPermissionManager_EvaluateBulkPermissions(b *testing.B) {
|
|
configMock := NewTestConfig()
|
|
configMock.values["CACHE_ENABLED"] = "false"
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
ctx := context.Background()
|
|
|
|
req := &auth.BulkPermissionRequest{
|
|
UserID: "dev@example.com",
|
|
AppID: "test-app",
|
|
Permissions: []string{
|
|
"app.read", "token.create", "token.read", "app.delete", "admin",
|
|
},
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := pm.EvaluateBulkPermissions(ctx, req)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkPermissionManager_ListPermissions(b *testing.B) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
permissions := pm.ListPermissions()
|
|
if len(permissions) == 0 {
|
|
b.Fatal("No permissions returned")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkPermissionManager_ListRoles(b *testing.B) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
roles := pm.ListRoles()
|
|
if len(roles) == 0 {
|
|
b.Fatal("No roles returned")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test permission hierarchy traversal
|
|
func TestPermissionHierarchy_PermissionInheritance(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
configMock.values["CACHE_ENABLED"] = "false"
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
// Test that admin users get hierarchical permissions
|
|
ctx := context.Background()
|
|
|
|
// Admin should have all permissions through hierarchy
|
|
adminPermissions := []string{
|
|
"admin",
|
|
"app.admin",
|
|
"token.admin",
|
|
"permission.admin",
|
|
"user.admin",
|
|
}
|
|
|
|
for _, perm := range adminPermissions {
|
|
evaluation, err := pm.HasPermission(ctx, "admin@example.com", "test-app", perm)
|
|
require.NoError(t, err)
|
|
assert.True(t, evaluation.Granted, "Admin should have %s permission", perm)
|
|
}
|
|
}
|
|
|
|
// Test role inheritance
|
|
func TestPermissionManager_RoleInheritance(t *testing.T) {
|
|
configMock := NewTestConfig()
|
|
|
|
logger := zap.NewNop()
|
|
pm := auth.NewPermissionManager(configMock, logger)
|
|
|
|
// Add a role that inherits from another role
|
|
parentRole := &auth.Role{
|
|
Name: "base_role",
|
|
Description: "Base role with basic permissions",
|
|
Permissions: []string{"read", "app.read"},
|
|
Metadata: map[string]string{"level": "base"},
|
|
}
|
|
|
|
childRole := &auth.Role{
|
|
Name: "extended_role",
|
|
Description: "Extended role that inherits from base",
|
|
Permissions: []string{"write"},
|
|
Inherits: []string{"base_role"},
|
|
Metadata: map[string]string{"level": "extended"},
|
|
}
|
|
|
|
err := pm.AddRole(parentRole)
|
|
require.NoError(t, err)
|
|
|
|
err = pm.AddRole(childRole)
|
|
require.NoError(t, err)
|
|
|
|
// Verify roles were added
|
|
roles := pm.ListRoles()
|
|
roleNames := make(map[string]*auth.Role)
|
|
for _, role := range roles {
|
|
roleNames[role.Name] = role
|
|
}
|
|
|
|
assert.Contains(t, roleNames, "base_role")
|
|
assert.Contains(t, roleNames, "extended_role")
|
|
assert.Equal(t, []string{"base_role"}, roleNames["extended_role"].Inherits)
|
|
}
|