Files
tiku-backend.net/Tiku.IntegrationTests/Api/AuthorizationManifestTests.cs

79 lines
3.7 KiB
C#

using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Security;
namespace Tiku.IntegrationTests.Api;
public sealed class AuthorizationManifestTests
{
private const int ExpectedActionCount = 330;
private const string ExpectedSha256 = "ad09167662cb9dc25111f40902c5f16a6633465f0ca7e7da0e50cdc10cfb8bb5";
[Fact]
public void Controller_authorization_surface_matches_reviewed_manifest()
{
var descriptors = typeof(Tiku.Api.ApiProgramMarker).Assembly.GetTypes()
.Where(type => !type.IsAbstract && typeof(ControllerBase).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(method => method.GetCustomAttributes<HttpMethodAttribute>().Any())
.Select(method => Describe(type, method)))
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join('\n', descriptors))))
.ToLowerInvariant();
Assert.True(
descriptors.Length == ExpectedActionCount && hash == ExpectedSha256,
$"Authorization manifest changed. count={descriptors.Length}, sha256={hash}");
}
[Fact]
public async Task Runtime_controller_endpoints_have_authorization_and_audit_metadata()
{
await using var factory = new ApiTestFactory();
using var client = factory.CreateClient();
_ = await client.GetAsync("/api/health");
var endpoints = factory.Services.GetRequiredService<EndpointDataSource>().Endpoints
.Where(endpoint => endpoint.Metadata.GetMetadata<ControllerActionDescriptor>() is not null)
.ToArray();
Assert.NotEmpty(endpoints);
foreach (var endpoint in endpoints)
{
var anonymous = endpoint.Metadata.GetMetadata<IAllowAnonymous>() is not null;
var metadata = endpoint.Metadata.GetMetadata<EndpointAuthorizationMetadata>();
if (anonymous)
{
Assert.Null(metadata);
continue;
}
Assert.NotNull(metadata);
Assert.False(string.IsNullOrWhiteSpace(metadata.AuditAction));
Assert.Contains(metadata.Realm, new[] { "authenticated", "tenant", "platform" });
}
}
private static string Describe(Type controller, MethodInfo action)
{
var controllerRoute = controller.GetCustomAttribute<RouteAttribute>()?.Template ?? string.Empty;
var http = action.GetCustomAttributes<HttpMethodAttribute>().ToArray();
var methods = string.Join(',', http.SelectMany(attribute => attribute.HttpMethods).Distinct().Order(StringComparer.Ordinal));
var templates = string.Join(',', http.Select(attribute => attribute.Template ?? string.Empty).Distinct().Order(StringComparer.Ordinal));
var policies = controller.GetCustomAttributes<AuthorizeAttribute>()
.Concat(action.GetCustomAttributes<AuthorizeAttribute>())
.Select(attribute => attribute.Policy ?? "authenticated")
.Order(StringComparer.Ordinal);
var anonymous = controller.IsDefined(typeof(AllowAnonymousAttribute)) ||
action.IsDefined(typeof(AllowAnonymousAttribute));
return $"{methods}|{controllerRoute}/{templates}|{controller.Name}.{action.Name}|anonymous={anonymous}|policies={string.Join(',', policies)}";
}
}