mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Merge pull request #2298 from DuendeSoftware/jmdc/aaj-test-cleanup
Remove NSubstitute from JwtBearer extensions
This commit is contained in:
commit
1a93ff3ed6
6 changed files with 94 additions and 13 deletions
|
|
@ -17,7 +17,6 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" />
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityModel;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Duende.AspNetCore.Authentication.JwtBearer.DPoP;
|
||||
|
||||
|
|
@ -16,5 +15,9 @@ public static class AssertionExtensions
|
|||
result.Error.ShouldBe(OidcConstants.TokenErrors.InvalidDPoPProof);
|
||||
}
|
||||
|
||||
public static void ReplayCacheShouldNotBeCalled(this TestDPoPProofValidator validator) => validator.TestReplayCache.DidNotReceive().Add(Arg.Any<string>(), Arg.Any<TimeSpan>());
|
||||
public static void ReplayCacheShouldNotBeCalled(this TestDPoPProofValidator validator)
|
||||
{
|
||||
var mockCache = (TestReplayCache)validator.TestReplayCache;
|
||||
mockCache.VerifyAddWasNotCalled();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ using System.Security.Claims;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Duende.AspNetCore.Authentication.JwtBearer.DPoP.TestFramework;
|
||||
using Duende.IdentityModel;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Duende.AspNetCore.Authentication.JwtBearer.DPoP;
|
||||
|
||||
|
|
@ -37,12 +36,11 @@ public abstract class DPoPProofValidatorTestBase
|
|||
|
||||
protected DPoPProofValidationContext Context;
|
||||
protected DPoPOptions Options = new();
|
||||
protected IReplayCache ReplayCache = Substitute.For<IReplayCache>();
|
||||
protected TestReplayCache ReplayCache = new();
|
||||
|
||||
public TestDPoPProofValidator CreateProofValidator()
|
||||
{
|
||||
var optionsMonitor = Substitute.For<IOptionsMonitor<DPoPOptions>>();
|
||||
optionsMonitor.Get(Arg.Any<string>()).Returns(Options);
|
||||
var optionsMonitor = new TestOptionsMonitor<DPoPOptions>(Options);
|
||||
|
||||
return new TestDPoPProofValidator(
|
||||
optionsMonitor,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using NSubstitute;
|
||||
|
||||
namespace Duende.AspNetCore.Authentication.JwtBearer.DPoP;
|
||||
|
||||
public class ReplayTests : DPoPProofValidatorTestBase
|
||||
|
|
@ -11,7 +9,7 @@ public class ReplayTests : DPoPProofValidatorTestBase
|
|||
[Trait("Category", "Unit")]
|
||||
public async Task replays_detected_in_ValidateReplay_fail()
|
||||
{
|
||||
ReplayCache.Exists(TokenIdHash).Returns(true);
|
||||
ReplayCache.ExistsFunc = jti => jti == TokenIdHash;
|
||||
Result.TokenIdHash = TokenIdHash;
|
||||
|
||||
await ProofValidator.ValidateReplay(Context, Result);
|
||||
|
|
@ -28,7 +26,7 @@ public class ReplayTests : DPoPProofValidatorTestBase
|
|||
[InlineData(true, true, ClockSkew * 2, ClockSkew * 2)]
|
||||
public async Task new_proof_tokens_are_added_to_replay_cache(bool validateIat, bool validateNonce, int clientClockSkew, int serverClockSkew)
|
||||
{
|
||||
ReplayCache.Exists(TokenIdHash).Returns(false);
|
||||
ReplayCache.ExistsFunc = _ => false;
|
||||
|
||||
Options.ValidationMode = (validateIat && validateNonce) ? ExpirationValidationMode.Both
|
||||
: validateIat ? ExpirationValidationMode.IssuedAt : ExpirationValidationMode.Nonce;
|
||||
|
|
@ -46,6 +44,6 @@ public class ReplayTests : DPoPProofValidatorTestBase
|
|||
: (validateIat ? clientClockSkew : serverClockSkew);
|
||||
var expectedExpiration = TimeSpan.FromSeconds(skew * 2)
|
||||
.Add(TimeSpan.FromSeconds(ValidFor));
|
||||
await ReplayCache.Received().Add(TokenIdHash, expectedExpiration);
|
||||
ReplayCache.VerifyAddWasCalled(TokenIdHash, expectedExpiration);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Duende.AspNetCore.Authentication.JwtBearer.DPoP.TestFramework;
|
||||
|
||||
public class TestOptionsMonitor<T>(T options) : IOptionsMonitor<T>
|
||||
{
|
||||
private readonly T _options = options;
|
||||
|
||||
public T CurrentValue => _options;
|
||||
|
||||
public T Get(string? name) => _options;
|
||||
|
||||
public IDisposable? OnChange(Action<T, string?> listener) => null;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.AspNetCore.Authentication.JwtBearer.DPoP;
|
||||
|
||||
public class TestReplayCache : IReplayCache
|
||||
{
|
||||
private readonly Dictionary<string, (TimeSpan expiration, DateTime addedAt)> _cache = new();
|
||||
private readonly List<(string jtiHash, TimeSpan expiration)> _addCalls = new();
|
||||
private readonly List<string> _existsCalls = new();
|
||||
|
||||
// Configuration for test behavior
|
||||
public Func<string, bool>? ExistsFunc { get; set; }
|
||||
|
||||
public Task Add(string jtiHash, TimeSpan expiration, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_addCalls.Add((jtiHash, expiration));
|
||||
_cache[jtiHash] = (expiration, DateTime.UtcNow);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<bool> Exists(string jtiHash, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_existsCalls.Add(jtiHash);
|
||||
|
||||
if (ExistsFunc != null)
|
||||
{
|
||||
return Task.FromResult(ExistsFunc(jtiHash));
|
||||
}
|
||||
|
||||
return Task.FromResult(_cache.ContainsKey(jtiHash));
|
||||
}
|
||||
|
||||
// Verification methods
|
||||
public void VerifyAddWasCalled(string jtiHash, TimeSpan expectedExpiration)
|
||||
{
|
||||
var call = _addCalls.FirstOrDefault(c => c.jtiHash == jtiHash);
|
||||
if (call == default)
|
||||
{
|
||||
throw new Exception($"Add was not called with jtiHash: {jtiHash}");
|
||||
}
|
||||
if (call.expiration != expectedExpiration)
|
||||
{
|
||||
throw new Exception($"Add was called with wrong expiration. Expected: {expectedExpiration}, Actual: {call.expiration}");
|
||||
}
|
||||
}
|
||||
|
||||
public void VerifyAddWasNotCalled()
|
||||
{
|
||||
if (_addCalls.Count > 0)
|
||||
{
|
||||
throw new Exception($"Add was called {_addCalls.Count} time(s) but should not have been called");
|
||||
}
|
||||
}
|
||||
|
||||
public bool WasAddCalled => _addCalls.Count > 0;
|
||||
|
||||
public IReadOnlyList<(string jtiHash, TimeSpan expiration)> AddCalls => _addCalls;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
_addCalls.Clear();
|
||||
_existsCalls.Clear();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue