- 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.
539 lines
15 KiB
Go
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")
|
|
})
|
|
}
|