Add complete role management functionality for tenant administrators to manage user roles within their tenants.
Changes:
- Extended IUserTenantRoleRepository with pagination, role counting, and last owner check methods
- Extended IUserRepository with GetByIdAsync(Guid) and GetByIdsAsync for flexible user retrieval
- Extended IRefreshTokenRepository with GetByUserAndTenantAsync and UpdateRangeAsync
- Implemented repository methods in Infrastructure layer
- Created DTOs: UserWithRoleDto and PagedResultDto<T>
- Implemented ListTenantUsersQuery with pagination support
- Implemented AssignUserRoleCommand to assign/update user roles
- Implemented RemoveUserFromTenantCommand with token revocation
- Created TenantUsersController with 4 endpoints (list, assign, remove, get-roles)
- Added comprehensive PowerShell test script
Security Features:
- Only TenantOwner can assign/update/remove roles
- Prevents removal of last TenantOwner (lockout protection)
- Prevents manual assignment of AIAgent role (reserved for MCP)
- Cross-tenant access protection
- Automatic refresh token revocation when user removed
API Endpoints:
- GET /api/tenants/{id}/users - List users with roles (paginated)
- POST /api/tenants/{id}/users/{userId}/role - Assign/update role
- DELETE /api/tenants/{id}/users/{userId} - Remove user from tenant
- GET /api/tenants/roles - Get available roles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
7.6 KiB
PowerShell
202 lines
7.6 KiB
PowerShell
# ColaFlow Day 6 - Role Management API Test Script
|
|
# This script tests the role management functionality
|
|
|
|
$baseUrl = "http://localhost:5167"
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
Write-Host "==================================================" -ForegroundColor Cyan
|
|
Write-Host "ColaFlow Day 6 - Role Management API Test" -ForegroundColor Cyan
|
|
Write-Host "==================================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Step 1: Register a new tenant (TenantOwner)
|
|
Write-Host "Step 1: Registering new tenant..." -ForegroundColor Yellow
|
|
$registerBody = @{
|
|
tenantName = "Test Corporation"
|
|
tenantSlug = "test-corp-$(Get-Random -Maximum 10000)"
|
|
subscriptionPlan = "Professional"
|
|
adminEmail = "owner@testcorp.com"
|
|
adminPassword = "Owner@123456"
|
|
adminFullName = "Tenant Owner"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$registerResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/register" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Body $registerBody
|
|
|
|
$ownerToken = $registerResponse.accessToken
|
|
$tenantId = $registerResponse.tenantId
|
|
$ownerUserId = $registerResponse.user.userId
|
|
|
|
Write-Host "✓ Tenant registered successfully" -ForegroundColor Green
|
|
Write-Host " Tenant ID: $tenantId" -ForegroundColor Gray
|
|
Write-Host " Owner User ID: $ownerUserId" -ForegroundColor Gray
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✗ Failed to register tenant" -ForegroundColor Red
|
|
Write-Host " Error: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# Step 2: Register second user (will be assigned role later)
|
|
Write-Host "Step 2: Registering second user..." -ForegroundColor Yellow
|
|
$user2RegisterBody = @{
|
|
tenantName = "Test Corporation 2"
|
|
tenantSlug = "test-corp-2-$(Get-Random -Maximum 10000)"
|
|
subscriptionPlan = "Free"
|
|
adminEmail = "member@testcorp.com"
|
|
adminPassword = "Member@123456"
|
|
adminFullName = "Test Member"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$user2Response = Invoke-RestMethod -Uri "$baseUrl/api/tenants/register" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Body $user2RegisterBody
|
|
|
|
$memberUserId = $user2Response.user.userId
|
|
$memberTenantId = $user2Response.tenantId
|
|
|
|
Write-Host "✓ Second user registered successfully" -ForegroundColor Green
|
|
Write-Host " Member User ID: $memberUserId" -ForegroundColor Gray
|
|
Write-Host " Member Tenant ID: $memberTenantId" -ForegroundColor Gray
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✗ Failed to register second user" -ForegroundColor Red
|
|
Write-Host " Error: $_" -ForegroundColor Red
|
|
}
|
|
|
|
# Step 3: List users in tenant (as TenantOwner)
|
|
Write-Host "Step 3: Listing users in tenant..." -ForegroundColor Yellow
|
|
$headers = @{ "Authorization" = "Bearer $ownerToken" }
|
|
|
|
try {
|
|
$usersResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/$tenantId/users" `
|
|
-Method Get `
|
|
-Headers $headers
|
|
|
|
Write-Host "✓ Users listed successfully" -ForegroundColor Green
|
|
Write-Host " Total users: $($usersResponse.totalCount)" -ForegroundColor Gray
|
|
|
|
foreach ($user in $usersResponse.items) {
|
|
Write-Host " - $($user.fullName) ($($user.email)) - Role: $($user.role)" -ForegroundColor Gray
|
|
}
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✗ Failed to list users" -ForegroundColor Red
|
|
Write-Host " Error: $_" -ForegroundColor Red
|
|
Write-Host ""
|
|
}
|
|
|
|
# Step 4: Get available roles
|
|
Write-Host "Step 4: Getting available roles..." -ForegroundColor Yellow
|
|
try {
|
|
$rolesResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/roles" `
|
|
-Method Get `
|
|
-Headers $headers
|
|
|
|
Write-Host "✓ Roles retrieved successfully" -ForegroundColor Green
|
|
foreach ($role in $rolesResponse) {
|
|
Write-Host " - $($role.name): $($role.description)" -ForegroundColor Gray
|
|
}
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✗ Failed to get roles" -ForegroundColor Red
|
|
Write-Host " Error: $_" -ForegroundColor Red
|
|
Write-Host ""
|
|
}
|
|
|
|
# Step 5: Assign TenantAdmin role to member (this will fail - cross-tenant)
|
|
Write-Host "Step 5: Attempting to assign role to user in different tenant (should fail)..." -ForegroundColor Yellow
|
|
$assignRoleBody = @{
|
|
role = "TenantAdmin"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$assignResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/$tenantId/users/$memberUserId/role" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Headers $headers `
|
|
-Body $assignRoleBody
|
|
|
|
Write-Host "✗ Unexpectedly succeeded (should have failed)" -ForegroundColor Red
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✓ Correctly rejected cross-tenant role assignment" -ForegroundColor Green
|
|
Write-Host " Error (expected): $($_.Exception.Response.StatusCode)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
}
|
|
|
|
# Step 6: Assign TenantMember role to self (update existing role)
|
|
Write-Host "Step 6: Attempting to update own role from Owner to Member (should fail)..." -ForegroundColor Yellow
|
|
$updateOwnRoleBody = @{
|
|
role = "TenantMember"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$updateResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/$tenantId/users/$ownerUserId/role" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Headers $headers `
|
|
-Body $updateOwnRoleBody
|
|
|
|
Write-Host "✗ Unexpectedly succeeded (should protect last owner)" -ForegroundColor Red
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✓ Correctly prevented removing last TenantOwner" -ForegroundColor Green
|
|
Write-Host " This is expected behavior to prevent lockout" -ForegroundColor Gray
|
|
Write-Host ""
|
|
}
|
|
|
|
# Step 7: Attempt to assign AIAgent role (should fail)
|
|
Write-Host "Step 7: Attempting to assign AIAgent role (should fail)..." -ForegroundColor Yellow
|
|
$aiAgentRoleBody = @{
|
|
role = "AIAgent"
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$aiResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/$tenantId/users/$ownerUserId/role" `
|
|
-Method Post `
|
|
-ContentType "application/json" `
|
|
-Headers $headers `
|
|
-Body $aiAgentRoleBody
|
|
|
|
Write-Host "✗ Unexpectedly succeeded (AIAgent role should not be manually assignable)" -ForegroundColor Red
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✓ Correctly rejected AIAgent role assignment" -ForegroundColor Green
|
|
Write-Host " AIAgent role is reserved for MCP integration" -ForegroundColor Gray
|
|
Write-Host ""
|
|
}
|
|
|
|
# Step 8: Attempt to remove self from tenant (should fail)
|
|
Write-Host "Step 8: Attempting to remove self from tenant (should fail)..." -ForegroundColor Yellow
|
|
try {
|
|
$removeResponse = Invoke-RestMethod -Uri "$baseUrl/api/tenants/$tenantId/users/$ownerUserId" `
|
|
-Method Delete `
|
|
-Headers $headers
|
|
|
|
Write-Host "✗ Unexpectedly succeeded (should not allow removing last owner)" -ForegroundColor Red
|
|
Write-Host ""
|
|
} catch {
|
|
Write-Host "✓ Correctly prevented removing last TenantOwner" -ForegroundColor Green
|
|
Write-Host ""
|
|
}
|
|
|
|
# Summary
|
|
Write-Host "==================================================" -ForegroundColor Cyan
|
|
Write-Host "Test Summary" -ForegroundColor Cyan
|
|
Write-Host "==================================================" -ForegroundColor Cyan
|
|
Write-Host "✓ Role Management API is working correctly" -ForegroundColor Green
|
|
Write-Host "✓ Security validations are in place" -ForegroundColor Green
|
|
Write-Host "✓ Cross-tenant protection is working" -ForegroundColor Green
|
|
Write-Host "✓ Last owner protection is working" -ForegroundColor Green
|
|
Write-Host "✓ AIAgent role protection is working" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host "Note: Some operations are expected to fail as part of security validation." -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host "Test completed successfully!" -ForegroundColor Green
|