perf(auth): allocation-free token parsing; single partition-key build per RPC
This commit is contained in:
@@ -196,6 +196,55 @@ public sealed class CachingApiKeyVerifierTests
|
||||
Assert.Equal(2, inner.MarkUsedCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins the header parse that arms the revoke-vs-in-flight generation guard. It runs on every
|
||||
/// cache miss and is written to allocate only the returned key id; these cases hold it to the
|
||||
/// rules the original <c>Split('_')</c> form applied. Note this parse is deliberately laxer than
|
||||
/// the interceptor's partition parse — it applies no key-id length cap and does not require a
|
||||
/// non-empty third segment — because a wrong <em>id</em> here would disarm the guard, whereas
|
||||
/// an over-long one merely fails to match any generation.
|
||||
/// </summary>
|
||||
/// <param name="authorizationHeader">The presented header value.</param>
|
||||
/// <param name="expected">The key id the parse must yield, or <see langword="null"/>.</param>
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("", null)]
|
||||
[InlineData(" ", null)]
|
||||
[InlineData("Bearer", null)]
|
||||
[InlineData("Bearer ", null)]
|
||||
[InlineData("Bearer mxgw_operator01_super-secret", "operator01")]
|
||||
[InlineData("bearer mxgw_operator01_super-secret", "operator01")]
|
||||
[InlineData(" Bearer mxgw_operator01_super-secret ", "operator01")]
|
||||
[InlineData("mxgw_operator01_super-secret", "operator01")]
|
||||
[InlineData("Bearer mxgw_abc_sec_ret", "abc")]
|
||||
[InlineData("Bearer mxgw_a_b_c", "a")]
|
||||
|
||||
// Laxer than the interceptor: an empty or absent third segment still yields the key id.
|
||||
[InlineData("Bearer mxgw_abc_", "abc")]
|
||||
[InlineData("Bearer mxgw_abc__secret", "abc")]
|
||||
[InlineData("Bearer mxgw_abc", null)]
|
||||
[InlineData("Bearer mxgwabcsecret", null)]
|
||||
[InlineData("Bearer mxgw__secret", null)]
|
||||
[InlineData("Bearer _mxgw_abc_secret", null)]
|
||||
[InlineData("Bearer MXGW_abc_secret", null)]
|
||||
[InlineData("Bearer xmxgw_abc_secret", null)]
|
||||
[InlineData("Bearer mxgw", null)]
|
||||
[InlineData("Bearer mxgw_", null)]
|
||||
[InlineData("Bearer ___", null)]
|
||||
public void TryParseKeyId_MatchesTokenShapeRules(string? authorizationHeader, string? expected)
|
||||
{
|
||||
Assert.Equal(expected, CachingApiKeyVerifier.TryParseKeyId(authorizationHeader));
|
||||
}
|
||||
|
||||
/// <summary>The guard parse applies no key-id length cap: an over-long id is still returned whole.</summary>
|
||||
[Fact]
|
||||
public void TryParseKeyId_LongKeyId_ReturnedWhole()
|
||||
{
|
||||
string keyId = new('a', 65);
|
||||
|
||||
Assert.Equal(keyId, CachingApiKeyVerifier.TryParseKeyId($"Bearer mxgw_{keyId}_secret"));
|
||||
}
|
||||
|
||||
private static MemoryCache NewCache() => new(new MemoryCacheOptions());
|
||||
|
||||
private static ApiKeyVerification Success(string keyId) => new(
|
||||
|
||||
+58
@@ -659,6 +659,64 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins the token shape the limiter partition is minted from. The parser is on every
|
||||
/// authenticated request, so it is written to allocate only the returned key id; these cases
|
||||
/// hold it to the rules the original <c>Split('_')</c> form applied — literal <c>mxgw</c> first
|
||||
/// segment, non-empty second and third segments, and a bounded key id — including the ones a
|
||||
/// hand-rolled scanner is most likely to drift on (a key id read from the second segment even
|
||||
/// when the secret itself contains separators).
|
||||
/// </summary>
|
||||
/// <param name="authorizationHeader">The presented header value.</param>
|
||||
/// <param name="expected">The key id the parse must yield, or <see langword="null"/>.</param>
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("", null)]
|
||||
[InlineData(" ", null)]
|
||||
[InlineData("Bearer", null)]
|
||||
[InlineData("Bearer ", null)]
|
||||
[InlineData("Bearer mxgw_abc_secret", "abc")]
|
||||
[InlineData("bearer mxgw_abc_secret", "abc")]
|
||||
[InlineData(" Bearer mxgw_abc_secret ", "abc")]
|
||||
[InlineData("mxgw_abc_secret", "abc")]
|
||||
|
||||
// The secret may carry separators of its own; the key id is still the second segment.
|
||||
[InlineData("Bearer mxgw_abc_sec_ret", "abc")]
|
||||
[InlineData("Bearer mxgw_a_b_c", "a")]
|
||||
[InlineData("Bearer mxgw_abc_secret_", "abc")]
|
||||
|
||||
// Shape failures: no separators, too few segments, empty segments, wrong prefix.
|
||||
[InlineData("Bearer mxgwabcsecret", null)]
|
||||
[InlineData("Bearer mxgw_abc", null)]
|
||||
[InlineData("Bearer mxgw_abc_", null)]
|
||||
[InlineData("Bearer mxgw_abc__secret", null)]
|
||||
[InlineData("Bearer mxgw__secret", null)]
|
||||
[InlineData("Bearer _mxgw_abc_secret", null)]
|
||||
[InlineData("Bearer _abc_secret", null)]
|
||||
[InlineData("Bearer MXGW_abc_secret", null)]
|
||||
[InlineData("Bearer xmxgw_abc_secret", null)]
|
||||
[InlineData("Bearer mxgw", null)]
|
||||
[InlineData("Bearer mxgw_", null)]
|
||||
[InlineData("Bearer ___", null)]
|
||||
public void TryResolveKeyId_MatchesTokenShapeRules(string? authorizationHeader, string? expected)
|
||||
{
|
||||
Assert.Equal(expected, GatewayGrpcAuthorizationInterceptor.TryResolveKeyId(authorizationHeader));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key-id length cap is what stops an invented id of arbitrary length from becoming a
|
||||
/// limiter partition, so the boundary is pinned on both sides.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveKeyId_HonoursKeyIdLengthCap()
|
||||
{
|
||||
string atCap = new('a', 64);
|
||||
string overCap = new('a', 65);
|
||||
|
||||
Assert.Equal(atCap, GatewayGrpcAuthorizationInterceptor.TryResolveKeyId($"Bearer mxgw_{atCap}_secret"));
|
||||
Assert.Null(GatewayGrpcAuthorizationInterceptor.TryResolveKeyId($"Bearer mxgw_{overCap}_secret"));
|
||||
}
|
||||
|
||||
private static MxAccessGatewayService CreateService(
|
||||
ISessionManager sessionManager,
|
||||
IGatewayRequestIdentityAccessor identityAccessor)
|
||||
|
||||
Reference in New Issue
Block a user