f72af76e99
- ApiKeyOptionsValidator (ValidateOnStart on any driver/admin node): a blank or placeholder Security:ApiKeys:Pepper fails host start instead of rejecting every key after a green boot (R03:2.3). - JWT is finished and opt-in by key (R03:2.4): with Security:Jwt:SigningKey set, a Bearer scheme (JwtBearerAuthenticationHandler over JwtTokenService.TryValidate / BuildValidationParameters, fail closed) accepts the tokens /auth/token mints; the default scheme is a cookie-or-bearer policy scheme and the fallback policy names it, so /api/* and every RequireAuthorization endpoint take a browser session OR a machine token. JwtOptions are validated at start (JwtOptionsValidator). Without a key, /auth/token is not mapped (404, not the former constructor 500) and no bearer scheme exists. A direct AddJwtBearer needs Microsoft.AspNetCore.Authentication.JwtBearer in Directory.Packages.props (not this lane's file) — the handler is a drop-in to swap. - AuthenticationStateAuditActorAccessor is the registered IAuditActorAccessor: the Blazor circuit's principal first, HttpContext as the fallback (R03:2.5). HttpAuditActorAccessor shares its resolver. - CookieAuthenticationStateProvider (unregistered, fire-and-forget ping loop) is deleted (R03:1.5/2.5). - /auth/login logs the LDAP exception at Warning before answering 503 (R03:2.7). - Admin operations reply with a fixed message + correlation id instead of ex.Message (R03:4.1). Refs #733 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
55 lines
1.8 KiB
C#
55 lines
1.8 KiB
C#
using Microsoft.Extensions.Options;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Configuration;
|
|
|
|
/// <summary>
|
|
/// R03:2.3 — an unset or placeholder pepper must fail host start, not surface as "Invalid API key"
|
|
/// on the first activation.
|
|
/// </summary>
|
|
public sealed class ApiKeyOptionsValidatorTests
|
|
{
|
|
private static ValidateOptionsResult Validate(ApiKeyOptions options) =>
|
|
new ApiKeyOptionsValidator().Validate(Options.DefaultName, options);
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void Blank_pepper_fails(string pepper)
|
|
{
|
|
var result = Validate(new ApiKeyOptions { Pepper = pepper });
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldNotBeNull();
|
|
result.FailureMessage.ShouldContain("Pepper is required");
|
|
}
|
|
|
|
[Fact]
|
|
public void Short_pepper_fails()
|
|
{
|
|
var result = Validate(new ApiKeyOptions { Pepper = "x" });
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldNotBeNull();
|
|
result.FailureMessage.ShouldContain($"at least {ApiKeyOptionsValidator.MinPepperLength}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Blank_username_fails()
|
|
{
|
|
var result = Validate(new ApiKeyOptions { Pepper = new string('p', 32), Username = " " });
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldNotBeNull();
|
|
result.FailureMessage.ShouldContain("Username");
|
|
}
|
|
|
|
/// <summary>Positive control: the docker-dev rig's pepper and the defaults pass unchanged.</summary>
|
|
[Fact]
|
|
public void Configured_pepper_passes()
|
|
{
|
|
Validate(new ApiKeyOptions { Pepper = "docker-dev-apikey-pepper-not-for-production" })
|
|
.Succeeded.ShouldBeTrue();
|
|
}
|
|
}
|