33using System . Threading ;
44using System . Threading . Tasks ;
55using Microsoft . Extensions . Caching . Memory ;
6+ using Microsoft . Extensions . DependencyInjection ;
67using Microsoft . Extensions . Logging ;
78using Microsoft . Extensions . Options ;
8- using Newtonsoft . Json . Linq ;
9+ using SenseNet . Communication . Messaging ;
910using SenseNet . Configuration ;
1011using SenseNet . ContentRepository . Storage ;
1112using SenseNet . ContentRepository . Storage . Security ;
@@ -21,13 +22,13 @@ internal class ApiKeyManager : IApiKeyManager
2122 {
2223 private const string FeatureName = "apikey" ;
2324 private readonly ILogger _logger ;
25+ // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
2426 private readonly ApiKeysOptions _apiKeys ;
25- private readonly MemoryCache _apiKeyCache = new ( new MemoryCacheOptions { SizeLimit = 1024 } ) ;
2627
2728 public ApiKeyManager ( ILogger < ApiKeyManager > logger , IOptions < ApiKeysOptions > apiKeys )
2829 {
2930 _logger = logger ;
30- _apiKeys = apiKeys . Value ?? new ApiKeysOptions ( ) ;
31+ _apiKeys = apiKeys ? . Value ?? new ApiKeysOptions ( ) ;
3132
3233 var now = DateTime . UtcNow ;
3334 var apiKey = _apiKeys . HealthCheckerUser ;
@@ -53,7 +54,11 @@ public async Task<IUser> GetUserByApiKeyAsync(string apiKey, CancellationToken c
5354 if ( string . IsNullOrEmpty ( apiKey ) )
5455 return null ;
5556
56- if ( ! _apiKeyCache . TryGetValue < AccessToken > ( apiKey , out var token ) )
57+ MemoryCache cache ;
58+ lock ( _cacheLock )
59+ cache = _apiKeyCache ;
60+
61+ if ( ! cache . TryGetValue < AccessToken > ( apiKey , out var token ) )
5762 {
5863 token = await AccessTokenVault . GetTokenAsync ( apiKey , 0 , FeatureName , cancel ) . ConfigureAwait ( false ) ;
5964
@@ -63,7 +68,7 @@ public async Task<IUser> GetUserByApiKeyAsync(string apiKey, CancellationToken c
6368
6469 // cache for 2 minutes
6570 if ( token != null )
66- _apiKeyCache . Set ( apiKey , token , new MemoryCacheEntryOptions
71+ cache . Set ( apiKey , token , new MemoryCacheEntryOptions
6772 {
6873 AbsoluteExpiration = new DateTimeOffset ( DateTime . UtcNow . AddMinutes ( 2 ) ) ,
6974 Size = 1
@@ -126,25 +131,28 @@ public async System.Threading.Tasks.Task DeleteApiKeyAsync(string apiKey, Cancel
126131 AssertPermissions ( token . UserId ) ;
127132
128133 await AccessTokenVault . DeleteTokenAsync ( apiKey , cancel ) . ConfigureAwait ( false ) ;
134+ ResetCache ( ) ;
129135 }
130136
131- public System . Threading . Tasks . Task DeleteApiKeysByUserAsync ( int userId , CancellationToken cancel )
137+ public async System . Threading . Tasks . Task DeleteApiKeysByUserAsync ( int userId , CancellationToken cancel )
132138 {
133139 if ( userId < 1 )
134- return System . Threading . Tasks . Task . CompletedTask ;
140+ return ;
135141
136142 AssertPermissions ( userId ) ;
137143
138- return AccessTokenVault . DeleteTokensAsync ( userId , 0 , FeatureName , cancel ) ;
144+ await AccessTokenVault . DeleteTokensAsync ( userId , 0 , FeatureName , cancel ) ;
145+ ResetCache ( ) ;
139146 }
140147
141- public System . Threading . Tasks . Task DeleteApiKeysAsync ( CancellationToken cancel )
148+ public async System . Threading . Tasks . Task DeleteApiKeysAsync ( CancellationToken cancel )
142149 {
143150 // user id: -1 or 1
144151 if ( Math . Abs ( User . Current . Id ) != 1 )
145152 throw new SenseNetSecurityException ( "Only administrators may delete all api keys." ) ;
146153
147- return AccessTokenVault . DeleteTokensByFeatureAsync ( FeatureName , cancel ) ;
154+ await AccessTokenVault . DeleteTokensByFeatureAsync ( FeatureName , cancel ) ;
155+ ResetCache ( ) ;
148156 }
149157
150158 private void AssertPermissions ( int userId )
@@ -164,5 +172,44 @@ private static bool IsUserAllowed(int userId)
164172 return currentUser . Id == userId ||
165173 Providers . Instance . SecurityHandler . HasPermission ( userId , PermissionType . Save ) ;
166174 }
175+
176+ /* ==================================================================================== CACHE */
177+
178+ private readonly object _cacheLock = new ( ) ;
179+ private MemoryCache _apiKeyCache = CreateCache ( ) ;
180+
181+ private static MemoryCache CreateCache ( ) => new ( new MemoryCacheOptions { SizeLimit = 1024 } ) ;
182+
183+ private void ResetCache ( )
184+ {
185+ new ApiKeyManagerCacheResetDistributedAction ( ) . ExecuteAsync ( CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
186+ }
187+ private void ResetCachePrivate ( )
188+ {
189+ MemoryCache oldCache ;
190+ lock ( _cacheLock )
191+ {
192+ oldCache = _apiKeyCache ;
193+ _apiKeyCache = CreateCache ( ) ;
194+ }
195+ oldCache . Dispose ( ) ;
196+ }
197+
198+ [ Serializable ]
199+ internal sealed class ApiKeyManagerCacheResetDistributedAction : DistributedAction
200+ {
201+ public override string TraceMessage => null ;
202+
203+ public override System . Threading . Tasks . Task DoActionAsync ( bool onRemote , bool isFromMe , CancellationToken cancellationToken )
204+ {
205+ // Local echo of my action: Return without doing anything
206+ if ( onRemote && isFromMe )
207+ return System . Threading . Tasks . Task . CompletedTask ;
208+ var instance = Providers . Instance . Services . GetService < IApiKeyManager > ( ) as ApiKeyManager ;
209+ instance ? . ResetCachePrivate ( ) ;
210+ return System . Threading . Tasks . Task . CompletedTask ;
211+ }
212+ }
213+
167214 }
168215}
0 commit comments