yao/openapi/tests/oauth/acl/role/role_test.go
Max 7aff66e3b4 Enhance ACL enforcement and logging for improved error handling
- Added detailed logging for ACL enforcement failures in the OAuth guard, providing clearer insights into permission issues.
- Updated error messages in the ACL enforcement logic to include contextual information such as client and user IDs, enhancing debugging capabilities.
- Introduced new constraints (CreatorOnly, EditorOnly) in the data access model, allowing for more granular control over data visibility based on user roles.
- Enhanced scope definitions to support additional constraints, improving flexibility in access control configurations.
- Updated documentation to reflect changes in data constraints and their usage in API handlers.
2025-10-22 09:51:06 +08:00

539 lines
15 KiB
Go

package role_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yaoapp/kun/maps"
"github.com/yaoapp/yao/openapi/oauth"
"github.com/yaoapp/yao/openapi/oauth/acl/role"
"github.com/yaoapp/yao/openapi/tests/testutils"
)
// Role Manager Test Suite
//
// PREREQUISITES:
// Before running tests, source the environment file:
// source $YAO_DEV/env.local.sh
//
// Then run tests:
// go test -v ./openapi/tests/oauth/acl/role/... -count=1
func TestNewManager(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
t.Run("CreateManagerWithProvider", func(t *testing.T) {
// Get cache and provider from OAuth service
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
// Create manager
manager := role.NewManager(cache, provider)
assert.NotNil(t, manager)
t.Log("Successfully created role manager with cache and provider")
})
t.Run("CreateManagerWithNilProvider", func(t *testing.T) {
// Get cache from OAuth service
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
// Create manager with nil provider
manager := role.NewManager(cache, nil)
assert.NotNil(t, manager)
// Should work but will error when trying to get roles
ctx := context.Background()
_, err := manager.GetUserRole(ctx, "test-user")
assert.Error(t, err)
assert.Contains(t, err.Error(), "user provider is not configured")
t.Log("Manager with nil provider correctly returns error")
})
}
func TestManagerWithNilCache(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Get provider
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
// Create manager with nil cache
manager := role.NewManager(nil, provider)
require.NotNil(t, manager)
ctx := context.Background()
t.Run("GetUserRoleWithNilCache", func(t *testing.T) {
// Create a test role and user
testRoleID := "test_role_nil_cache"
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Nil Cache Role",
"description": "Role for nil cache testing",
"is_active": true,
}
_, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
defer provider.DeleteRole(ctx, testRoleID)
userID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
userData := maps.MapStrAny{
"user_id": userID,
"email": "nilcache@example.com",
"role_id": testRoleID,
"status": "active",
}
_, err = provider.CreateUser(ctx, userData)
require.NoError(t, err)
defer provider.DeleteUser(ctx, userID)
// Get user role - should work even without cache
roleID, err := manager.GetUserRole(ctx, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID)
t.Log("Successfully retrieved role without cache")
})
t.Run("GetScopesWithNilCache", func(t *testing.T) {
// Create test role with permissions
testRoleID := "test_role_scopes_nil_cache"
permissions := maps.MapStrAny{
"read": true,
"write": true,
}
restrictedPermissions := []string{"admin"}
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Nil Cache Scopes",
"description": "Role for scopes nil cache testing",
"is_active": true,
"permissions": permissions,
"restricted_permissions": restrictedPermissions,
}
_, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
defer provider.DeleteRole(ctx, testRoleID)
// Get scopes - should work even without cache
allowed, restricted, err := manager.GetScopes(ctx, testRoleID)
assert.NoError(t, err)
assert.NotNil(t, allowed)
assert.NotNil(t, restricted)
t.Logf("Successfully retrieved scopes without cache: allowed=%v, restricted=%v", allowed, restricted)
})
t.Run("ClearCacheWithNilCache", func(t *testing.T) {
// Should not panic with nil cache
err := manager.ClearCache()
assert.NoError(t, err)
t.Log("ClearCache works safely with nil cache")
})
}
func TestGetUserRole(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("GetRoleForExistingUser", func(t *testing.T) {
// First, create a test role using provider
testRoleID := "test_role_get_user"
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Role",
"description": "Role for testing",
"is_active": true,
}
// Create role
createdRoleID, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
require.NotEmpty(t, createdRoleID)
defer provider.DeleteRole(ctx, testRoleID)
// Generate unique user ID
userID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
// Create test user with role
userData := maps.MapStrAny{
"user_id": userID,
"email": "testuser@example.com",
"role_id": testRoleID,
"status": "active",
}
_, err = provider.CreateUser(ctx, userData)
require.NoError(t, err)
defer provider.DeleteUser(ctx, userID)
// Get user role
roleID, err := manager.GetUserRole(ctx, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID)
t.Logf("Successfully retrieved role %s for user %s", roleID, userID)
// Get again (should come from cache)
roleID2, err := manager.GetUserRole(ctx, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID2)
t.Log("Successfully retrieved role from cache")
})
t.Run("GetRoleForNonExistentUser", func(t *testing.T) {
_, err := manager.GetUserRole(ctx, "non-existent-user-12345")
assert.Error(t, err)
t.Log("Correctly returns error for non-existent user")
})
t.Run("GetRoleForUserWithoutRole", func(t *testing.T) {
// Create user without role
userID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
userData := maps.MapStrAny{
"user_id": userID,
"email": "norole@example.com",
"status": "active",
}
_, err = provider.CreateUser(ctx, userData)
require.NoError(t, err)
defer provider.DeleteUser(ctx, userID)
_, err = manager.GetUserRole(ctx, userID)
assert.Error(t, err)
t.Log("Correctly returns error for user without role")
})
}
func TestGetMemberRole(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("GetRoleForExistingMember", func(t *testing.T) {
// Create test role
testRoleID := "test_role_member"
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Member Role",
"description": "Role for member testing",
"is_active": true,
}
_, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
defer provider.DeleteRole(ctx, testRoleID)
// Create test team
teamID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
teamData := maps.MapStrAny{
"team_id": teamID,
"name": "Test Team",
"owner_id": "test_owner",
"status": "active",
}
_, err = provider.CreateTeam(ctx, teamData)
require.NoError(t, err)
defer provider.DeleteTeam(ctx, teamID)
// Create test member
userID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": userID,
"role_id": testRoleID,
"member_type": "user",
"status": "active",
}
_, err = provider.CreateMember(ctx, memberData)
require.NoError(t, err)
defer provider.RemoveMember(ctx, teamID, userID)
// Get member role
roleID, err := manager.GetMemberRole(ctx, teamID, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID)
t.Logf("Successfully retrieved role %s for member %s in team %s", roleID, userID, teamID)
// Get again (should come from cache)
roleID2, err := manager.GetMemberRole(ctx, teamID, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID2)
t.Log("Successfully retrieved member role from cache")
})
t.Run("GetRoleForNonExistentMember", func(t *testing.T) {
_, err := manager.GetMemberRole(ctx, "non-existent-team", "non-existent-user")
assert.Error(t, err)
t.Log("Correctly returns error for non-existent member")
})
}
func TestGetScopes(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("GetScopesForRoleWithPermissions", func(t *testing.T) {
// Create test role with permissions
testRoleID := "test_role_scopes"
// Create role with permissions as map
permissions := maps.MapStrAny{
"read": true,
"write": true,
"delete": false, // Should not be included
}
restrictedPermissions := []string{"admin", "superuser"}
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Role with Scopes",
"description": "Role for scopes testing",
"is_active": true,
"permissions": permissions,
"restricted_permissions": restrictedPermissions,
}
_, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
defer provider.DeleteRole(ctx, testRoleID)
// Get scopes
allowed, restricted, err := manager.GetScopes(ctx, testRoleID)
assert.NoError(t, err)
assert.NotNil(t, allowed)
assert.NotNil(t, restricted)
// Verify allowed scopes contain enabled permissions
t.Logf("Allowed scopes: %v", allowed)
t.Logf("Restricted scopes: %v", restricted)
// Note: The exact format depends on how the database stores JSON
// We just verify we can retrieve them without error
assert.True(t, len(allowed) >= 0, "Should return allowed scopes (empty or with values)")
assert.True(t, len(restricted) >= 0, "Should return restricted scopes (empty or with values)")
// Get again (should come from cache)
allowed2, restricted2, err := manager.GetScopes(ctx, testRoleID)
assert.NoError(t, err)
assert.Equal(t, allowed, allowed2)
assert.Equal(t, restricted, restricted2)
t.Log("Successfully retrieved scopes from cache")
})
t.Run("GetScopesForNonExistentRole", func(t *testing.T) {
_, _, err := manager.GetScopes(ctx, "non-existent-role-12345")
assert.Error(t, err)
t.Log("Correctly returns error for non-existent role")
})
}
func TestGetTeamRole(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("GetRoleForTeamWithoutRole", func(t *testing.T) {
// Create team without role_id field
teamID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
teamData := maps.MapStrAny{
"team_id": teamID,
"name": "Test Team No Role",
"owner_id": "test_owner",
"status": "active",
}
_, err = provider.CreateTeam(ctx, teamData)
require.NoError(t, err)
defer provider.DeleteTeam(ctx, teamID)
// Get team role (should return error when no role_id assigned)
roleID, err := manager.GetTeamRole(ctx, teamID)
assert.Error(t, err, "Should return error when team has no role_id")
assert.Contains(t, err.Error(), "has no role_id assigned", "Error message should indicate missing role_id")
assert.Empty(t, roleID, "Role ID should be empty when error occurs")
t.Logf("Correctly returns error for team without role_id: %v", err)
})
t.Run("GetRoleForNonExistentTeam", func(t *testing.T) {
_, err := manager.GetTeamRole(ctx, "non-existent-team-12345")
assert.Error(t, err)
t.Log("Correctly returns error for non-existent team")
})
}
func TestGetClientRole(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("GetClientRoleReturnsDefault", func(t *testing.T) {
// Note: Client role retrieval is TODO in the code
// It currently returns a default "system:root" role
roleID, err := manager.GetClientRole(ctx, "test-client")
assert.NoError(t, err)
assert.Equal(t, "system:root", roleID)
t.Log("Client role returns default system:root (TODO: implement ClientProvider)")
})
}
func TestCacheIntegration(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean()
// Setup
cache := oauth.OAuth.GetCache()
require.NotNil(t, cache)
provider, err := oauth.OAuth.GetUserProvider()
require.NoError(t, err)
require.NotNil(t, provider)
manager := role.NewManager(cache, provider)
ctx := context.Background()
t.Run("RoleCachingWorks", func(t *testing.T) {
// Create test role and user
testRoleID := "test_role_cache"
roleData := maps.MapStrAny{
"role_id": testRoleID,
"name": "Test Cache Role",
"description": "Role for cache testing",
"is_active": true,
}
_, err := provider.CreateRole(ctx, roleData)
if err != nil {
t.Skipf("Skipping test - cannot create role: %v", err)
return
}
defer provider.DeleteRole(ctx, testRoleID)
userID, err := provider.GenerateUserID(ctx, true)
require.NoError(t, err)
userData := maps.MapStrAny{
"user_id": userID,
"email": "cache@example.com",
"role_id": testRoleID,
"status": "active",
}
_, err = provider.CreateUser(ctx, userData)
require.NoError(t, err)
defer provider.DeleteUser(ctx, userID)
// First call - should hit database
roleID1, err := manager.GetUserRole(ctx, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID1)
// Second call - should hit cache
roleID2, err := manager.GetUserRole(ctx, userID)
assert.NoError(t, err)
assert.Equal(t, testRoleID, roleID2)
// Results should be identical
assert.Equal(t, roleID1, roleID2)
t.Log("Cache integration verified: same role retrieved from cache")
})
}