25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
4.3KB

  1. using System.IdentityModel.Tokens.Jwt;
  2. using System.Net;
  3. using System.Net.Http.Json;
  4. using System.Net.Http.Headers;
  5. using System.Security.Claims;
  6. using System.Text;
  7. using Campaign_Tracker.Server.Authentication;
  8. using Microsoft.AspNetCore.Mvc.Testing;
  9. using Microsoft.Extensions.DependencyInjection;
  10. using Microsoft.IdentityModel.Tokens;
  11. namespace Campaign_Tracker.Server.Tests;
  12. public class AuthEndpointTests : IClassFixture<WebApplicationFactory<Program>>
  13. {
  14. private const string Issuer = "http://kci-app01.ntp.kentcommunications.com:8180/realms/KCI";
  15. private const string ClientId = "canopy-web";
  16. private const string SigningKey = "test-signing-key-with-at-least-32-characters";
  17. private readonly WebApplicationFactory<Program> _factory;
  18. public AuthEndpointTests(WebApplicationFactory<Program> factory)
  19. {
  20. Environment.SetEnvironmentVariable("Keycloak__Authority", Issuer);
  21. Environment.SetEnvironmentVariable("Keycloak__ValidIssuer", Issuer);
  22. Environment.SetEnvironmentVariable("Keycloak__PublicAuthority", Issuer);
  23. Environment.SetEnvironmentVariable("Keycloak__ClientId", ClientId);
  24. Environment.SetEnvironmentVariable("Keycloak__DisableHttpsMetadata", "true");
  25. Environment.SetEnvironmentVariable("Keycloak__TestSigningKey", SigningKey);
  26. _factory = factory;
  27. }
  28. [Fact]
  29. public async Task SessionEndpoint_WithoutToken_ReturnsUnauthorized()
  30. {
  31. using var client = _factory.CreateClient();
  32. var response = await client.GetAsync("/api/auth/session");
  33. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  34. }
  35. [Fact]
  36. public async Task SessionEndpoint_WithValidToken_ReturnsRoleWorkspaceAndAuditsSuccess()
  37. {
  38. using var client = _factory.CreateClient();
  39. client.DefaultRequestHeaders.Authorization =
  40. new AuthenticationHeaderValue("Bearer", CreateToken("daniel@example.test", "client-services"));
  41. var httpResponse = await client.GetAsync("/api/auth/session");
  42. var auditStore = _factory.Services.GetRequiredService<IAuthenticationAuditStore>();
  43. Assert.True(httpResponse.IsSuccessStatusCode, string.Join("; ", auditStore.Events.Select(audit => audit.Reason)));
  44. var response = await httpResponse.Content.ReadFromJsonAsync<AuthSessionResponse>();
  45. Assert.NotNull(response);
  46. Assert.Equal("daniel@example.test", response.UserName);
  47. Assert.Contains("client-services", response.Roles);
  48. Assert.Equal("/workspace/client-services", response.WorkspacePath);
  49. Assert.Contains(auditStore.Events, audit =>
  50. audit.EventType == AuthenticationAuditEventType.Success &&
  51. audit.Subject == "daniel@example.test");
  52. }
  53. [Fact]
  54. public async Task SessionEndpoint_WithInvalidToken_ReturnsUnauthorizedAndAuditsFailure()
  55. {
  56. using var client = _factory.CreateClient();
  57. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid.jwt.value");
  58. var response = await client.GetAsync("/api/auth/session");
  59. var auditStore = _factory.Services.GetRequiredService<IAuthenticationAuditStore>();
  60. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  61. Assert.Contains(auditStore.Events, audit =>
  62. audit.EventType == AuthenticationAuditEventType.Failure &&
  63. audit.Reason.Contains("invalid", StringComparison.OrdinalIgnoreCase));
  64. }
  65. private static string CreateToken(string subject, string role)
  66. {
  67. var credentials = new SigningCredentials(
  68. new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SigningKey)),
  69. SecurityAlgorithms.HmacSha256);
  70. var token = new JwtSecurityToken(
  71. issuer: Issuer,
  72. audience: ClientId,
  73. claims:
  74. [
  75. new Claim(JwtRegisteredClaimNames.Sub, subject),
  76. new Claim(ClaimTypes.Name, subject),
  77. new Claim(ClaimTypes.Role, role),
  78. ],
  79. expires: DateTime.UtcNow.AddMinutes(10),
  80. signingCredentials: credentials);
  81. return new JwtSecurityTokenHandler().WriteToken(token);
  82. }
  83. private sealed class AuthSessionResponse
  84. {
  85. public string UserName { get; init; } = string.Empty;
  86. public string[] Roles { get; init; } = [];
  87. public string WorkspacePath { get; init; } = string.Empty;
  88. }
  89. }

Powered by TurnKey Linux.