119 lines
4.7 KiB
C#
119 lines
4.7 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Xunit;
|
|
|
|
namespace D3ROVoice.Api.Tests;
|
|
|
|
[CollectionDefinition("Api server integration", DisableParallelization = true)]
|
|
public sealed class ApiServerIntegrationCollection;
|
|
|
|
[Collection("Api server integration")]
|
|
public sealed class AdminAuthorizationE2ETests : IClassFixture<AdminAuthorizationE2ETests.ApiFactory>
|
|
{
|
|
private const string Secret = "admin-e2e-jwt-secret-0123456789-abcdef";
|
|
private const string Issuer = "https://admin-e2e.test";
|
|
private const string Audience = "d3ro-admin-e2e";
|
|
private readonly ApiFactory _factory;
|
|
|
|
public AdminAuthorizationE2ETests(ApiFactory factory)
|
|
{
|
|
_factory = factory;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AdminReadEndpointsRejectAnonymousAndNonManager()
|
|
{
|
|
using var client = _factory.CreateClient();
|
|
Assert.Equal(HttpStatusCode.Unauthorized, (await client.GetAsync("/api/admin/stats")).StatusCode);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token("User"));
|
|
Assert.Equal(HttpStatusCode.Forbidden, (await client.GetAsync("/api/admin/stats")).StatusCode);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token("manager"));
|
|
Assert.Equal(HttpStatusCode.OK, (await client.GetAsync("/api/admin/stats")).StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AdminMutationRejectsManagerAndAcceptsNormalizedSuperAdmin()
|
|
{
|
|
using var client = _factory.CreateClient();
|
|
using var emptyPayload = new StringContent("{}", Encoding.UTF8, "application/json");
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token("Manager"));
|
|
Assert.Equal(HttpStatusCode.Forbidden,
|
|
(await client.PostAsync("/api/admin/endpoints", emptyPayload)).StatusCode);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token("super_admin"));
|
|
using var secondPayload = new StringContent("{}", Encoding.UTF8, "application/json");
|
|
Assert.Equal(HttpStatusCode.BadRequest,
|
|
(await client.PostAsync("/api/admin/endpoints", secondPayload)).StatusCode);
|
|
}
|
|
|
|
private static string Token(string role)
|
|
{
|
|
var credentials = new SigningCredentials(
|
|
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret)),
|
|
SecurityAlgorithms.HmacSha256);
|
|
var token = new JwtSecurityToken(
|
|
issuer: Issuer,
|
|
audience: Audience,
|
|
claims:
|
|
[
|
|
new Claim(ClaimTypes.NameIdentifier, "1"),
|
|
new Claim(ClaimTypes.Email, "admin@example.com"),
|
|
new Claim(ClaimTypes.Role, role)
|
|
],
|
|
expires: DateTime.UtcNow.AddMinutes(5),
|
|
signingCredentials: credentials);
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
|
|
public sealed class ApiFactory : WebApplicationFactory<Program>
|
|
{
|
|
private readonly string _databasePath = Path.Combine(
|
|
Path.GetTempPath(), $"d3ro-admin-auth-e2e-{Guid.NewGuid():N}.db");
|
|
private readonly Dictionary<string, string?> _previousEnvironment = new();
|
|
|
|
public ApiFactory()
|
|
{
|
|
SetEnvironment("JWT_SECRET", Secret);
|
|
SetEnvironment("JWT_ISSUER", Issuer);
|
|
SetEnvironment("JWT_AUDIENCE", Audience);
|
|
SetEnvironment("D3RO_API_TOKEN", "fixture-internal-gateway-token-32-bytes-minimum");
|
|
SetEnvironment("DB_PATH", _databasePath);
|
|
SetEnvironment("CORS_ALLOWED_ORIGINS", "http://localhost:3001");
|
|
SetEnvironment("ALLOWED_HOSTS", "localhost;127.0.0.1");
|
|
}
|
|
|
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
{
|
|
builder.UseEnvironment("Development");
|
|
builder.ConfigureLogging(logging => logging.ClearProviders());
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
SqliteConnection.ClearAllPools();
|
|
if (File.Exists(_databasePath)) File.Delete(_databasePath);
|
|
foreach (var (name, value) in _previousEnvironment)
|
|
{
|
|
Environment.SetEnvironmentVariable(name, value);
|
|
}
|
|
}
|
|
|
|
private void SetEnvironment(string name, string value)
|
|
{
|
|
_previousEnvironment[name] = Environment.GetEnvironmentVariable(name);
|
|
Environment.SetEnvironmentVariable(name, value);
|
|
}
|
|
}
|
|
}
|