Files
skybridge/test/permissions_test.go
2025-08-22 17:32:57 -04:00

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