fix(security): M2.19 review nits — idle/refresh config guard + adapter tests + dead-var/doc cleanup (#15)
- Add SecurityOptionsValidator (IValidateOptions<SecurityOptions>) enforcing RoleRefreshThresholdMinutes < IdleTimeoutMinutes; registered with ValidateOnStart in AddSecurity — startup FAILS if threshold >= idle, so the invariant cannot be silently misconfigured away. - Update SecurityOptions XML-docs: class-level summary distinguishes JWT Bearer path (JwtSigningKey/JwtExpiryMinutes) from Blazor cookie session path (IdleTimeoutMinutes/ RoleRefreshThresholdMinutes); both time fields document the ~45-min effective idle window and the new cross-field constraint. - Remove dead jwtService variable from /auth/login lambda in AuthEndpoints.cs (resolved but never used since login moved to SessionClaimBuilder). - Extract ApplyValidationResultAsync helper from OnValidatePrincipalAsync (pure decision-application step); add 3 adapter tests covering Reject → RejectPrincipal + SignOutAsync; Replace → ReplacePrincipal + ShouldRenew; Keep → no-op. - Fix inaccurate TryRefreshAsync comment (dropped "OR last-activity needs advancing" — the code only returns non-null when roleRefreshDue). - Add InternalsVisibleTo for Security.Tests in Security.csproj. - Add IsRoleRefreshDue tests: missing claim → due; unparsable claim → due; plus integration test covering the full ValidateAsync path for a principal missing zb:lastrolerefresh (triggers refresh + re-stamps anchor rather than keeping stale principal forever). - Add SecurityOptionsValidatorConfigGuardTests: default succeeds; equal fails; greater fails; boundary (idle-1) succeeds; wiring confirmed via AddSecurity container.
This commit is contained in:
@@ -82,6 +82,16 @@ public static class ServiceCollectionExtensions
|
||||
// to consume this seam in a later task.
|
||||
services.AddScoped<IGroupRoleMapper<string>, ScadaBridgeGroupRoleMapper>();
|
||||
|
||||
// M2.19 (#15): fail-fast config guard — RoleRefreshThresholdMinutes must be strictly
|
||||
// less than IdleTimeoutMinutes. If they are equal or inverted, a single un-refreshed
|
||||
// cycle can exhaust the entire idle window and idle enforcement is silently defeated.
|
||||
// SecurityOptionsValidator is registered with ValidateOnStart so a misconfigured
|
||||
// appsettings section fails at boot with a clear message rather than behaving subtly
|
||||
// incorrectly at runtime. Config-binding stays with the Host (component library must
|
||||
// not take IConfiguration), so we only register the validator + ValidateOnStart here.
|
||||
services.AddOptions<SecurityOptions>().ValidateOnStart();
|
||||
services.AddSingleton<IValidateOptions<SecurityOptions>, SecurityOptionsValidator>();
|
||||
|
||||
// Note: the old SecurityOptionsValidator (which fail-fast-validated LdapServer +
|
||||
// LdapSearchBase) is gone — those keys moved into the shared LdapOptions, whose
|
||||
// LdapOptionsValidator (registered with ValidateOnStart by AddZbLdapAuth above)
|
||||
@@ -195,6 +205,23 @@ public static class ServiceCollectionExtensions
|
||||
.ValidateAsync(context.Principal, context.HttpContext.RequestAborted)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await ApplyValidationResultAsync(context, result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a <see cref="SessionValidationResult"/> to a
|
||||
/// <see cref="CookieValidatePrincipalContext"/>: the pure decision-application
|
||||
/// step extracted from <see cref="OnValidatePrincipalAsync"/> so it can be
|
||||
/// exercised in unit tests without a live DI container resolving
|
||||
/// <see cref="CookieSessionValidator"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The cookie validation context to mutate.</param>
|
||||
/// <param name="result">The decision produced by <see cref="CookieSessionValidator.ValidateAsync"/>.</param>
|
||||
/// <returns>A task that completes when the result has been applied.</returns>
|
||||
internal static async Task ApplyValidationResultAsync(
|
||||
CookieValidatePrincipalContext context,
|
||||
SessionValidationResult result)
|
||||
{
|
||||
switch (result.Action)
|
||||
{
|
||||
case SessionValidationAction.Reject:
|
||||
|
||||
Reference in New Issue
Block a user