docs: complete XML-doc coverage and strip internal tracking IDs from code comments
Resolve all CommentChecker findings across the gateway server, worker, tests, and .NET client (314 -> 0 real issues): add missing <returns>/<summary>/<param> on public and test members, convert Stream/interface overrides to <inheritdoc/>, and remove internal task/issue tracking IDs (SEC-*, IPC-*, WRK-*, GWC-*, TST-*, Client.Dotnet-*) from shipped code documentation while preserving the design rationale prose. Shipped comments should not carry internal bookkeeping, and complete XML docs keep the analyzer/TreatWarningsAsErrors gate and generated API docs clean. The 6 remaining flags are heuristic false positives (MD5, UTC-4, capacity-1, near-1601) left intact so real documentation is not corrupted. Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
This commit is contained in:
+1
-1
@@ -236,7 +236,7 @@ public static class ApiKeyAdminCommandLineParser
|
||||
ReadHistorizedOnly: HasFlag(options, "read-historized-only"));
|
||||
}
|
||||
|
||||
// Parses the optional --expires value into an absolute UTC expiry (SEC-10). Accepts a relative
|
||||
// Parses the optional --expires value into an absolute UTC expiry. Accepts a relative
|
||||
// "<N>d"/"<N>h" duration from now (operator-friendly) or an absolute ISO-8601 instant/date
|
||||
// (assumed UTC). Null/blank means no expiry — expiry stays opt-in, preserving prior behaviour.
|
||||
private static DateTimeOffset? ParseExpiry(string? value)
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ public static class AuthStoreServiceCollectionExtensions
|
||||
// migrator and the migration hosted service.
|
||||
services.AddZbApiKeyAuth(effectiveConfig, AuthenticationSectionPath);
|
||||
|
||||
// SEC-08 hot-path decorators. Every gRPC call previously did a SQLite read plus a
|
||||
// Hot-path decorators. Every gRPC call previously did a SQLite read plus a
|
||||
// last_used_utc WRITE via IApiKeyVerifier.VerifyAsync (the library verifier couples the
|
||||
// mark into verification). Two gateway-side decorators cut that cost without editing the
|
||||
// external library:
|
||||
@@ -138,7 +138,7 @@ public static class AuthStoreServiceCollectionExtensions
|
||||
/// <summary>
|
||||
/// Replaces the last registration of <typeparamref name="TService"/> with a singleton that wraps
|
||||
/// it. The wrapped (inner) service is created once, preserving singleton semantics. Used to layer
|
||||
/// the SEC-08 store decorator over the external library's registration without editing it.
|
||||
/// the store decorator over the external library's registration without editing it.
|
||||
/// </summary>
|
||||
private static void DecorateSingleton<TService>(
|
||||
IServiceCollection services,
|
||||
|
||||
@@ -30,7 +30,7 @@ public interface IApiKeyCacheInvalidator
|
||||
/// <para>
|
||||
/// Only successful verifications are cached. Failures and unparseable headers always fall through
|
||||
/// to the inner verifier — caching a failure risks pinning a transiently-wrong negative, and the
|
||||
/// SEC-11 per-peer failure counter (not this cache) is what bounds brute-force cost.
|
||||
/// per-peer failure counter (not this cache) is what bounds brute-force cost.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The cache key is the hex SHA-256 of the presented token (which embeds both the key id and the
|
||||
@@ -71,7 +71,11 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid
|
||||
{
|
||||
}
|
||||
|
||||
// Test/explicit-TTL seam.
|
||||
/// <summary>Initializes a new instance of the <see cref="CachingApiKeyVerifier"/> class with an explicit TTL.</summary>
|
||||
/// <param name="inner">The wrapped verifier (the library verifier) reached on a cache miss.</param>
|
||||
/// <param name="cache">The shared memory cache.</param>
|
||||
/// <param name="ttl">The verification-cache TTL; a zero or negative value disables caching.</param>
|
||||
/// <remarks>Test/explicit-TTL seam.</remarks>
|
||||
internal CachingApiKeyVerifier(IApiKeyVerifier inner, IMemoryCache cache, TimeSpan ttl)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(inner);
|
||||
@@ -81,7 +85,14 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid
|
||||
_ttl = ttl;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Verifies the given authorization header, returning a cached successful result when one is
|
||||
/// still within TTL, or falling through to the inner verifier on a cache miss or non-cacheable
|
||||
/// header.
|
||||
/// </summary>
|
||||
/// <param name="authorizationHeader">The raw <c>Authorization</c> header value to verify.</param>
|
||||
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>The verification result, cached or freshly computed.</returns>
|
||||
public async Task<ApiKeyVerification> VerifyAsync(string authorizationHeader, CancellationToken ct)
|
||||
{
|
||||
if (_ttl <= TimeSpan.Zero || !TryComputeCacheKey(authorizationHeader, out string cacheKey))
|
||||
|
||||
+17
-4
@@ -41,7 +41,10 @@ public sealed class CoalescingMarkApiKeyStore : IApiKeyStore
|
||||
{
|
||||
}
|
||||
|
||||
// Test/explicit-window seam.
|
||||
/// <summary>Initializes a new instance of the <see cref="CoalescingMarkApiKeyStore"/> class with an explicit coalescing window (test/explicit-window seam).</summary>
|
||||
/// <param name="inner">The wrapped store.</param>
|
||||
/// <param name="window">The coalescing window; marks within this window of the last forwarded mark for a key are dropped.</param>
|
||||
/// <param name="clock">The time provider.</param>
|
||||
internal CoalescingMarkApiKeyStore(IApiKeyStore inner, TimeSpan window, TimeProvider clock)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(inner);
|
||||
@@ -51,15 +54,25 @@ public sealed class CoalescingMarkApiKeyStore : IApiKeyStore
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Looks up an API key record by key id, delegating to the wrapped store unchanged.</summary>
|
||||
/// <param name="keyId">The API key id to look up.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The matching <see cref="ApiKeyRecord"/>, or <see langword="null"/> if none exists.</returns>
|
||||
public Task<ApiKeyRecord?> FindByKeyIdAsync(string keyId, CancellationToken ct)
|
||||
=> _inner.FindByKeyIdAsync(keyId, ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Looks up an active API key record by key id, delegating to the wrapped store unchanged.</summary>
|
||||
/// <param name="keyId">The API key id to look up.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The matching active <see cref="ApiKeyRecord"/>, or <see langword="null"/> if none exists or is inactive.</returns>
|
||||
public Task<ApiKeyRecord?> FindActiveByKeyIdAsync(string keyId, CancellationToken ct)
|
||||
=> _inner.FindActiveByKeyIdAsync(keyId, ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Marks the key as used, coalescing writes so at most one reaches the wrapped store per key per window.</summary>
|
||||
/// <param name="keyId">The API key id being marked as used.</param>
|
||||
/// <param name="whenUtc">The UTC timestamp of the use.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(keyId);
|
||||
|
||||
@@ -19,11 +19,6 @@ namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
||||
/// </remarks>
|
||||
public static class GatewayApiKeyIdentityMapper
|
||||
{
|
||||
// SEC-08: memoize the constraints deserialization keyed by the raw JSON blob so the per-call
|
||||
// JSON parse on the auth hot path collapses to a dictionary lookup. Distinct blobs are bounded
|
||||
// by the number of API keys (small); ApiKeyConstraints is an immutable record, so a parsed
|
||||
// instance is safe to share across callers. The cap is a defensive backstop against a pathological
|
||||
// spread of distinct blobs — past it, we simply parse without caching rather than grow unbounded.
|
||||
private const int MaxCachedConstraintBlobs = 1024;
|
||||
private static readonly ConcurrentDictionary<string, ApiKeyConstraints> ConstraintCache =
|
||||
new(StringComparer.Ordinal);
|
||||
|
||||
@@ -4,7 +4,7 @@ using ZB.MOM.WW.MxGateway.Server.Configuration;
|
||||
namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
||||
|
||||
/// <summary>
|
||||
/// Cheap, in-process per-peer sliding-window failure counter for the gRPC auth path (SEC-11). It is
|
||||
/// Cheap, in-process per-peer sliding-window failure counter for the gRPC auth path. It is
|
||||
/// checked BEFORE the API-key verification store read and short-circuits a peer that has exceeded
|
||||
/// <see cref="SecurityOptions.ApiKeyFailureLimit"/> failed attempts within
|
||||
/// <see cref="SecurityOptions.ApiKeyFailureWindowSeconds"/>; a successful verification resets the
|
||||
@@ -45,7 +45,11 @@ public sealed class ApiKeyFailureLimiter
|
||||
{
|
||||
}
|
||||
|
||||
// Test/explicit seam.
|
||||
/// <summary>Initializes a new instance of the <see cref="ApiKeyFailureLimiter"/> class. Test/explicit seam.</summary>
|
||||
/// <param name="limit">The maximum number of failures allowed within <paramref name="window"/>.</param>
|
||||
/// <param name="window">The sliding window over which failures are counted.</param>
|
||||
/// <param name="maxPeers">The maximum number of tracked peers before least-recently-active eviction kicks in.</param>
|
||||
/// <param name="clock">The time provider.</param>
|
||||
internal ApiKeyFailureLimiter(int limit, TimeSpan window, int maxPeers, TimeProvider clock)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(clock);
|
||||
@@ -145,6 +149,7 @@ public sealed class ApiKeyFailureLimiter
|
||||
|
||||
private sealed class PeerState
|
||||
{
|
||||
/// <summary>Timestamps (in ticks) of failures still within the sliding window.</summary>
|
||||
public Queue<long> FailureTicks { get; } = new();
|
||||
|
||||
public long LastActivityTicks;
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public sealed class GatewayGrpcAuthorizationInterceptor(
|
||||
|
||||
string? authorizationHeader = context.RequestHeaders.GetValue("authorization");
|
||||
|
||||
// SEC-11: short-circuit a peer that has already failed too many times inside the sliding
|
||||
// Short-circuit a peer that has already failed too many times inside the sliding
|
||||
// window BEFORE the verification store read, so online guessing cannot spend a SQLite read
|
||||
// per attempt. The peer key prefers the presented key id over the transport address (NAT
|
||||
// caveat). ResourceExhausted signals throttling without revealing whether any particular
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public static class GrpcAuthorizationServiceCollectionExtensions
|
||||
services.AddSingleton<GatewayGrpcScopeResolver>();
|
||||
services.AddSingleton<IGatewayRequestIdentityAccessor, GatewayRequestIdentityAccessor>();
|
||||
services.AddSingleton<IConstraintEnforcer, ConstraintEnforcer>();
|
||||
// SEC-11 per-peer failure counter, checked before the verification store read. Bind the knobs
|
||||
// Per-peer failure counter, checked before the verification store read. Bind the knobs
|
||||
// from IConfiguration directly (not IOptions<GatewayOptions>) to avoid coupling this
|
||||
// registration to the whole-options validation pipeline.
|
||||
services.AddSingleton(sp => new ApiKeyFailureLimiter(
|
||||
|
||||
Reference in New Issue
Block a user