test(security): add browser-vs-AJAX challenge tests for root path
Adds protected MapGet("/") in the test host plus three [Fact] methods
exercising the cookie scheme's challenge heuristic for the root route:
browser (Accept: text/html), AJAX (X-Requested-With: XMLHttpRequest),
and JSON (Accept: application/json) callers. Also adds a no-redirect
HttpClient helper so the 302 + Location can be asserted directly.
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -63,7 +64,13 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(e => e.MapOtOpcUaAuth());
|
||||
app.UseEndpoints(e =>
|
||||
{
|
||||
e.MapOtOpcUaAuth();
|
||||
// Protected root used by AuthChallengeTests below — exercises the cookie
|
||||
// scheme's challenge heuristic without depending on the full Razor host.
|
||||
e.MapGet("/", () => Results.Ok("authenticated")).RequireAuthorization();
|
||||
});
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
@@ -81,6 +88,13 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
|
||||
private HttpClient NewClient() => _server.CreateClient();
|
||||
|
||||
/// <summary>Creates a TestServer-backed HttpClient that does NOT auto-follow redirects.
|
||||
/// Used by challenge tests so we can assert on the 302 + Location directly.</summary>
|
||||
private HttpClient NewClientNoRedirect() => new(_server.CreateHandler())
|
||||
{
|
||||
BaseAddress = _server.BaseAddress,
|
||||
};
|
||||
|
||||
/// <summary>Tests that login with valid credentials returns 204 and sets cookie.</summary>
|
||||
[Fact]
|
||||
public async Task Login_with_valid_credentials_returns_204_and_sets_cookie()
|
||||
@@ -178,6 +192,46 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
.ShouldContain(c => c.StartsWith("OtOpcUa.Auth=") && c.Contains("expires=", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Anonymous browser GET of a protected route redirects to /login with a ReturnUrl.</summary>
|
||||
[Fact]
|
||||
public async Task Root_anonymous_browser_GET_redirects_to_login()
|
||||
{
|
||||
var client = NewClientNoRedirect();
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, "/");
|
||||
req.Headers.Accept.ParseAdd("text/html");
|
||||
var resp = await client.SendAsync(req, Ct);
|
||||
|
||||
resp.StatusCode.ShouldBe(HttpStatusCode.Found);
|
||||
resp.Headers.Location.ShouldNotBeNull();
|
||||
resp.Headers.Location!.OriginalString.ShouldContain("/login");
|
||||
resp.Headers.Location.OriginalString.ShouldContain("ReturnUrl");
|
||||
}
|
||||
|
||||
/// <summary>Anonymous AJAX GET of a protected route returns 401 with no Location.</summary>
|
||||
[Fact]
|
||||
public async Task Root_anonymous_ajax_GET_returns_401()
|
||||
{
|
||||
var client = NewClientNoRedirect();
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, "/");
|
||||
req.Headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||
var resp = await client.SendAsync(req, Ct);
|
||||
|
||||
resp.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
|
||||
resp.Headers.Location.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Anonymous JSON GET of a protected route returns 401.</summary>
|
||||
[Fact]
|
||||
public async Task Root_anonymous_json_GET_returns_401()
|
||||
{
|
||||
var client = NewClientNoRedirect();
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, "/");
|
||||
req.Headers.Accept.ParseAdd("application/json");
|
||||
var resp = await client.SendAsync(req, Ct);
|
||||
|
||||
resp.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
private static void AttachCookies(HttpRequestMessage request, HttpResponseMessage prior)
|
||||
{
|
||||
if (!prior.Headers.TryGetValues("Set-Cookie", out var setCookies)) return;
|
||||
|
||||
Reference in New Issue
Block a user