yavsc/src/Yavsc.Tests.Shared/TestAuthPolicyProvider.cs
Paul Schneider 349ddc03f5
Some checks failed
Dotnet build and test / log-the-inputs (pull_request) Has been cancelled
Dotnet build and test / build (pull_request) Has been cancelled
refactor: extract WebHostFixture + TestAuthPolicyProvider to shared lib
Yavsc.Blogs.Tests will need a fixture too. Lifting the cross-cutting
plumbing (Kestrel + self-signed cert + address discovery + lazy init)
into a new Yavsc.Tests.Shared project lets the next fixture inherit
from it without copying 200+ lines of setup boilerplate, and keeps
the Org.Tests fixture focused on its IdentityServer + SMTP seed.

* New project src/Yavsc.Tests.Shared with WebHostFixture (abstract)
  and TestAuthPolicyProvider (test auth bypass via X-Test-Role).
* WebServerFixture in Org.Tests now inherits from WebHostFixture;
  BuildApp + ConfigurePipelineAsync hold only Org-specific work.
* Two shared package versions promoted to the root Directory.Packages.props.
* Tests still 30/30 green.
2026-07-06 21:33:57 +01:00

71 lines
3 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Security.Claims;
namespace Yavsc.Tests.Shared;
/// <summary>
/// Authorization policy provider used by integration tests. Replaces the
/// production provider in the test host so that any policy-protected
/// controller can be exercised by sending a <c>X-Test-Role: …</c>
/// header — no login roundtrip, no cookie, no database user.
///
/// Any policy that requires a role short-circuits to success when the
/// matching header is present; otherwise the production policy is
/// preserved. The test does not perform a real login, so we attach an
/// in-memory <see cref="ClaimsPrincipal"/> carrying the role claim to
/// the request <see cref="HttpContext.User"/> before the assertion
/// fires, so claim-based requirements (e.g. <c>RequireRole("Admin")</c>)
/// also pass.
/// </summary>
public sealed class TestAuthPolicyProvider : IAuthorizationPolicyProvider
{
/// <summary>HTTP header read by the test bypass to learn the role.</summary>
public const string HeaderName = "X-Test-Role";
/// <summary>Conventional admin role name; the production default.</summary>
public const string AdminRole = "Administrator";
private readonly DefaultAuthorizationPolicyProvider _fallback;
public TestAuthPolicyProvider(IOptions<AuthorizationOptions> options)
{
_fallback = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => _fallback.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => _fallback.GetFallbackPolicyAsync();
public async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
var policy = await _fallback.GetPolicyAsync(policyName);
if (policy is null) return null;
return new AuthorizationPolicyBuilder()
.RequireAssertion(ctx =>
{
// ASP.NET Core sets ctx.Resource to the HttpContext when
// the authorization middleware invokes the policy. Use
// the request headers directly to honour X-Test-Role.
var http = ctx.Resource as HttpContext;
if (http is null) return false;
var role = http.Request.Headers[HeaderName].ToString();
if (string.IsNullOrEmpty(role)) return false;
if (http.User.Identity is null || !http.User.Identity.IsAuthenticated)
{
var identity = new ClaimsIdentity(
new[]
{
new Claim(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
role),
},
authenticationType: "TestAuth");
http.User = new ClaimsPrincipal(identity);
}
return true;
})
.Build();
}
}