|
- using System.IdentityModel.Tokens.Jwt;
- using System.Net;
- using System.Net.Http.Json;
- using System.Net.Http.Headers;
- using System.Security.Claims;
- using System.Text;
- using Campaign_Tracker.Server.Authentication;
- using Microsoft.AspNetCore.Mvc.Testing;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.IdentityModel.Tokens;
-
- namespace Campaign_Tracker.Server.Tests;
-
- public class AuthEndpointTests : IClassFixture<WebApplicationFactory<Program>>
- {
- private const string Issuer = "http://kci-app01.ntp.kentcommunications.com:8180/realms/KCI";
- private const string ClientId = "canopy-web";
- private const string SigningKey = "test-signing-key-with-at-least-32-characters";
-
- private readonly WebApplicationFactory<Program> _factory;
-
- public AuthEndpointTests(WebApplicationFactory<Program> factory)
- {
- Environment.SetEnvironmentVariable("Keycloak__Authority", Issuer);
- Environment.SetEnvironmentVariable("Keycloak__ValidIssuer", Issuer);
- Environment.SetEnvironmentVariable("Keycloak__PublicAuthority", Issuer);
- Environment.SetEnvironmentVariable("Keycloak__ClientId", ClientId);
- Environment.SetEnvironmentVariable("Keycloak__DisableHttpsMetadata", "true");
- Environment.SetEnvironmentVariable("Keycloak__TestSigningKey", SigningKey);
-
- _factory = factory;
- }
-
- [Fact]
- public async Task SessionEndpoint_WithoutToken_ReturnsUnauthorized()
- {
- using var client = _factory.CreateClient();
-
- var response = await client.GetAsync("/api/auth/session");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SessionEndpoint_WithValidToken_ReturnsRoleWorkspaceAndAuditsSuccess()
- {
- using var client = _factory.CreateClient();
- client.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", CreateToken("daniel@example.test", "client-services"));
-
- var httpResponse = await client.GetAsync("/api/auth/session");
- var auditStore = _factory.Services.GetRequiredService<IAuthenticationAuditStore>();
- Assert.True(httpResponse.IsSuccessStatusCode, string.Join("; ", auditStore.Events.Select(audit => audit.Reason)));
-
- var response = await httpResponse.Content.ReadFromJsonAsync<AuthSessionResponse>();
-
- Assert.NotNull(response);
- Assert.Equal("daniel@example.test", response.UserName);
- Assert.Contains("client-services", response.Roles);
- Assert.Equal("/workspace/client-services", response.WorkspacePath);
- Assert.Contains(auditStore.Events, audit =>
- audit.EventType == AuthenticationAuditEventType.Success &&
- audit.Subject == "daniel@example.test");
- }
-
- [Fact]
- public async Task SessionEndpoint_WithInvalidToken_ReturnsUnauthorizedAndAuditsFailure()
- {
- using var client = _factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid.jwt.value");
-
- var response = await client.GetAsync("/api/auth/session");
- var auditStore = _factory.Services.GetRequiredService<IAuthenticationAuditStore>();
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- Assert.Contains(auditStore.Events, audit =>
- audit.EventType == AuthenticationAuditEventType.Failure &&
- audit.Reason.Contains("invalid", StringComparison.OrdinalIgnoreCase));
- }
-
- private static string CreateToken(string subject, string role)
- {
- var credentials = new SigningCredentials(
- new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SigningKey)),
- SecurityAlgorithms.HmacSha256);
- var token = new JwtSecurityToken(
- issuer: Issuer,
- audience: ClientId,
- claims:
- [
- new Claim(JwtRegisteredClaimNames.Sub, subject),
- new Claim(ClaimTypes.Name, subject),
- new Claim(ClaimTypes.Role, role),
- ],
- expires: DateTime.UtcNow.AddMinutes(10),
- signingCredentials: credentials);
-
- return new JwtSecurityTokenHandler().WriteToken(token);
- }
-
- private sealed class AuthSessionResponse
- {
- public string UserName { get; init; } = string.Empty;
- public string[] Roles { get; init; } = [];
- public string WorkspacePath { get; init; } = string.Empty;
- }
- }
|