230 lines
8.6 KiB
C#
230 lines
8.6 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
|
|
using FluentAssertions;
|
|
|
|
namespace ColaFlow.Modules.Identity.IntegrationTests.Identity;
|
|
|
|
/// <summary>
|
|
/// Integration tests for Role-Based Access Control (RBAC) functionality (Day 5 - Phase 2)
|
|
/// Tests role assignment, JWT claims, and role persistence across authentication flows
|
|
/// </summary>
|
|
public class RbacTests(DatabaseFixture fixture) : IClassFixture<DatabaseFixture>
|
|
{
|
|
private readonly HttpClient _client = fixture.Client;
|
|
|
|
[Fact]
|
|
public async Task RegisterTenant_ShouldAssignTenantOwnerRole()
|
|
{
|
|
// Arrange & Act
|
|
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Assert - Verify token contains TenantOwner role
|
|
TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisterTenant_ShouldIncludeRoleInJwtClaims()
|
|
{
|
|
// Arrange & Act
|
|
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Assert - Decode JWT and verify claims
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var token = handler.ReadJwtToken(accessToken);
|
|
var claims = token.Claims.ToList();
|
|
|
|
// Should have either 'role' or 'tenant_role' claim with value 'TenantOwner'
|
|
claims.Should().Contain(c =>
|
|
(c.Type == "role" || c.Type == "tenant_role") &&
|
|
c.Value == "TenantOwner");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_ShouldPreserveRole()
|
|
{
|
|
// Arrange - Register tenant
|
|
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
|
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
|
var password = "Admin@1234";
|
|
|
|
await RegisterTenantAsync(tenantSlug, email, password);
|
|
|
|
// Act - Login
|
|
var (accessToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
|
|
|
|
// Assert - Role should be preserved
|
|
TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RefreshToken_ShouldPreserveRole()
|
|
{
|
|
// Arrange - Register and get initial tokens
|
|
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Act - Refresh token
|
|
var refreshRequest = new { refreshToken };
|
|
var response = await _client.PostAsJsonAsync("/api/auth/refresh", refreshRequest);
|
|
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
|
|
|
|
// Assert - New token should preserve role
|
|
TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetMe_ShouldReturnUserRoleInformation()
|
|
{
|
|
// Arrange - Register and get tokens
|
|
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Act - Call /api/auth/me with token
|
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
|
var response = await _client.GetAsync("/api/auth/me");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var userInfo = await response.Content.ReadFromJsonAsync<UserInfoResponse>();
|
|
userInfo.Should().NotBeNull();
|
|
userInfo!.TenantRole.Should().Be("TenantOwner");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JwtToken_ShouldContainAllRequiredRoleClaims()
|
|
{
|
|
// Arrange & Act
|
|
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Assert - Verify all expected claims
|
|
var claims = TestAuthHelper.ParseJwtToken(accessToken).ToList();
|
|
|
|
// Must have user identity claims
|
|
claims.Should().Contain(c => c.Type == "user_id");
|
|
claims.Should().Contain(c => c.Type == "tenant_id");
|
|
claims.Should().Contain(c => c.Type == "email");
|
|
claims.Should().Contain(c => c.Type == "full_name");
|
|
|
|
// Must have role claim
|
|
claims.Should().Contain(c =>
|
|
(c.Type == "role" || c.Type == "tenant_role") &&
|
|
c.Value == "TenantOwner");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MultipleTokenRefresh_ShouldMaintainRole()
|
|
{
|
|
// Arrange - Register and get initial tokens
|
|
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Act & Assert - Refresh multiple times
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
|
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
|
|
|
|
// Verify role is maintained
|
|
TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();
|
|
|
|
// Update token for next iteration
|
|
refreshToken = result.RefreshToken;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AccessProtectedEndpoint_WithValidRole_ShouldSucceed()
|
|
{
|
|
// Arrange - Register and get token with TenantOwner role
|
|
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
|
|
|
// Act - Access protected endpoint
|
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
|
var response = await _client.GetAsync("/api/auth/me");
|
|
|
|
// Assert - Should succeed because user has valid role
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AccessProtectedEndpoint_WithoutToken_ShouldFail()
|
|
{
|
|
// Arrange - No authorization header
|
|
|
|
// Act - Try to access protected endpoint
|
|
var response = await _client.GetAsync("/api/auth/me");
|
|
|
|
// Assert - Should fail with 401 Unauthorized
|
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AccessProtectedEndpoint_WithInvalidToken_ShouldFail()
|
|
{
|
|
// Arrange - Invalid token
|
|
var invalidToken = "invalid.jwt.token";
|
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", invalidToken);
|
|
|
|
// Act - Try to access protected endpoint
|
|
var response = await _client.GetAsync("/api/auth/me");
|
|
|
|
// Assert - Should fail with 401 Unauthorized
|
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RoleInformation_ShouldBeConsistentAcrossAllFlows()
|
|
{
|
|
// This test verifies role consistency across:
|
|
// 1. Registration
|
|
// 2. Login
|
|
// 3. Token Refresh
|
|
// 4. User Info Endpoint
|
|
|
|
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
|
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
|
var password = "Admin@1234";
|
|
|
|
// Step 1: Register
|
|
await RegisterTenantAsync(tenantSlug, email, password);
|
|
var (registerToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
|
|
TestAuthHelper.HasRole(registerToken, "TenantOwner").Should().BeTrue("Registration should assign TenantOwner");
|
|
|
|
// Step 2: Login
|
|
var (loginToken, refreshToken) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
|
|
TestAuthHelper.HasRole(loginToken, "TenantOwner").Should().BeTrue("Login should preserve TenantOwner");
|
|
|
|
// Step 3: Token Refresh
|
|
var refreshResponse = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
|
var refreshResult = await refreshResponse.Content.ReadFromJsonAsync<RefreshResponse>();
|
|
TestAuthHelper.HasRole(refreshResult!.AccessToken, "TenantOwner").Should().BeTrue("Refresh should preserve TenantOwner");
|
|
|
|
// Step 4: User Info Endpoint
|
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshResult.AccessToken);
|
|
var meResponse = await _client.GetAsync("/api/auth/me");
|
|
var userInfo = await meResponse.Content.ReadFromJsonAsync<UserInfoResponse>();
|
|
userInfo!.TenantRole.Should().Be("TenantOwner", "User info should show TenantOwner");
|
|
}
|
|
|
|
#region Helper Methods
|
|
|
|
private async Task RegisterTenantAsync(string tenantSlug, string email, string password)
|
|
{
|
|
var request = new
|
|
{
|
|
tenantName = "Test Corp",
|
|
tenantSlug,
|
|
subscriptionPlan = "Professional",
|
|
adminEmail = email,
|
|
adminPassword = password,
|
|
adminFullName = "Test Admin"
|
|
};
|
|
|
|
var response = await _client.PostAsJsonAsync("/api/tenants/register", request);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
#endregion
|
|
}
|