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.
71 lines
3 KiB
C#
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();
|
|
}
|
|
}
|