|
|
|
@ -5,12 +5,14 @@ |
|
|
|
*/ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Concurrent; |
|
|
|
using System.Collections.Immutable; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using JetBrains.Annotations; |
|
|
|
using Microsoft.Extensions.Caching.Memory; |
|
|
|
using Microsoft.Extensions.Options; |
|
|
|
using Microsoft.Extensions.Primitives; |
|
|
|
using OpenIddict.Abstractions; |
|
|
|
|
|
|
|
namespace OpenIddict.Core |
|
|
|
@ -21,7 +23,8 @@ namespace OpenIddict.Core |
|
|
|
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
|
|
|
|
public class OpenIddictTokenCache<TToken> : IOpenIddictTokenCache<TToken>, IDisposable where TToken : class |
|
|
|
{ |
|
|
|
private readonly IMemoryCache _cache; |
|
|
|
private readonly MemoryCache _cache; |
|
|
|
private readonly ConcurrentDictionary<string, Lazy<CancellationTokenSource>> _signals; |
|
|
|
private readonly IOpenIddictTokenStore<TToken> _store; |
|
|
|
|
|
|
|
public OpenIddictTokenCache( |
|
|
|
@ -33,6 +36,7 @@ namespace OpenIddict.Core |
|
|
|
SizeLimit = options.CurrentValue.EntityCacheLimit |
|
|
|
}); |
|
|
|
|
|
|
|
_signals = new ConcurrentDictionary<string, Lazy<CancellationTokenSource>>(StringComparer.Ordinal); |
|
|
|
_store = resolver.Get<TToken>(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -51,14 +55,75 @@ namespace OpenIddict.Core |
|
|
|
throw new ArgumentNullException(nameof(token)); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken), |
|
|
|
Status = await _store.GetStatusAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken), |
|
|
|
Status = await _store.GetStatusAsync(token, cancellationToken), |
|
|
|
Type = await _store.GetTypeAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByApplicationIdAsync), |
|
|
|
Identifier = await _store.GetApplicationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByAuthorizationIdAsync), |
|
|
|
Identifier = await _store.GetAuthorizationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByIdAsync), |
|
|
|
Identifier = await _store.GetIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByReferenceIdAsync), |
|
|
|
Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindBySubjectAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByIdAsync), |
|
|
|
Identifier = await _store.GetIdAsync(token, cancellationToken) |
|
|
|
})) |
|
|
|
{ |
|
|
|
entry.SetSize(1L); |
|
|
|
entry.SetValue(token); |
|
|
|
entry.AddExpirationToken(signal) |
|
|
|
.SetSize(1L) |
|
|
|
.SetValue(token); |
|
|
|
} |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(new |
|
|
|
@ -67,15 +132,24 @@ namespace OpenIddict.Core |
|
|
|
Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) |
|
|
|
})) |
|
|
|
{ |
|
|
|
entry.SetSize(1L); |
|
|
|
entry.SetValue(token); |
|
|
|
entry.AddExpirationToken(signal) |
|
|
|
.SetSize(1L) |
|
|
|
.SetValue(token); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Disposes the cache held by this instance.
|
|
|
|
/// Disposes the resources held by this instance.
|
|
|
|
/// </summary>
|
|
|
|
public void Dispose() => _cache.Dispose(); |
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
foreach (var signal in _signals) |
|
|
|
{ |
|
|
|
signal.Value.Value.Dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Retrieves the tokens corresponding to the specified
|
|
|
|
@ -122,6 +196,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -184,6 +269,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -253,6 +349,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -300,6 +407,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -347,6 +465,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -393,6 +522,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
if (token != null) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(1L); |
|
|
|
entry.SetValue(token); |
|
|
|
} |
|
|
|
@ -440,6 +580,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
if (token != null) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(1L); |
|
|
|
entry.SetValue(token); |
|
|
|
} |
|
|
|
@ -486,6 +637,17 @@ namespace OpenIddict.Core |
|
|
|
|
|
|
|
using (var entry = _cache.CreateEntry(parameters)) |
|
|
|
{ |
|
|
|
foreach (var token in tokens) |
|
|
|
{ |
|
|
|
var signal = await CreateExpirationSignalAsync(token, cancellationToken); |
|
|
|
if (signal == null) |
|
|
|
{ |
|
|
|
throw new InvalidOperationException("An error occurred while creating an expiration signal."); |
|
|
|
} |
|
|
|
|
|
|
|
entry.AddExpirationToken(signal); |
|
|
|
} |
|
|
|
|
|
|
|
entry.SetSize(tokens.Length); |
|
|
|
entry.SetValue(tokens); |
|
|
|
} |
|
|
|
@ -511,59 +673,52 @@ namespace OpenIddict.Core |
|
|
|
throw new ArgumentNullException(nameof(token)); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
var identifier = await _store.GetIdAsync(token, cancellationToken); |
|
|
|
if (string.IsNullOrEmpty(identifier)) |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken), |
|
|
|
Status = await _store.GetStatusAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
throw new InvalidOperationException("The application identifier cannot be extracted."); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
if (_signals.TryGetValue(identifier, out Lazy<CancellationTokenSource> signal)) |
|
|
|
{ |
|
|
|
Method = nameof(FindAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken), |
|
|
|
Client = await _store.GetApplicationIdAsync(token, cancellationToken), |
|
|
|
Status = await _store.GetStatusAsync(token, cancellationToken), |
|
|
|
Type = await _store.GetTypeAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
signal.Value.Cancel(); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindByApplicationIdAsync), |
|
|
|
Identifier = await _store.GetApplicationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
_signals.TryRemove(identifier, out signal); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
/// <summary>
|
|
|
|
/// Creates an expiration signal allowing to invalidate all the
|
|
|
|
/// cache entries associated with the specified token.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="token">The token associated with the expiration signal.</param>
|
|
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
|
|
/// whose result returns an expiration signal for the specified token.
|
|
|
|
/// </returns>
|
|
|
|
protected virtual async Task<IChangeToken> CreateExpirationSignalAsync([NotNull] TToken token, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
if (token == null) |
|
|
|
{ |
|
|
|
Method = nameof(FindByAuthorizationIdAsync), |
|
|
|
Identifier = await _store.GetAuthorizationIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
throw new ArgumentNullException(nameof(token)); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
var identifier = await _store.GetIdAsync(token, cancellationToken); |
|
|
|
if (string.IsNullOrEmpty(identifier)) |
|
|
|
{ |
|
|
|
Method = nameof(FindByIdAsync), |
|
|
|
Identifier = await _store.GetIdAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
throw new InvalidOperationException("The token identifier cannot be extracted."); |
|
|
|
} |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
var signal = _signals.GetOrAdd(identifier, delegate |
|
|
|
{ |
|
|
|
Method = nameof(FindByReferenceIdAsync), |
|
|
|
Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) |
|
|
|
// Note: a Lazy<CancellationTokenSource> is used here to ensure only one CancellationTokenSource
|
|
|
|
// can be created. Not doing so would result in expiration signals being potentially linked to
|
|
|
|
// multiple sources, with a single one of them being eventually tracked and thus, cancelable.
|
|
|
|
return new Lazy<CancellationTokenSource>(() => new CancellationTokenSource()); |
|
|
|
}); |
|
|
|
|
|
|
|
_cache.Remove(new |
|
|
|
{ |
|
|
|
Method = nameof(FindBySubjectAsync), |
|
|
|
Subject = await _store.GetSubjectAsync(token, cancellationToken) |
|
|
|
}); |
|
|
|
return new CancellationChangeToken(signal.Value.Token); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|