-
This commit is contained in:
594
test/permissions_test.go
Normal file
594
test/permissions_test.go
Normal file
@ -0,0 +1,594 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user