using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Security { /// sealed class IdentityCache : IIdentityCache, IAsyncDisposable { /// /// The for the . /// readonly IAsyncDelayer asyncDelayer; /// /// The for the . /// readonly ILogger logger; /// /// The map of s to s. /// readonly Dictionary cachedIdentities; /// /// Initializes a new instance of the class. /// /// The value of . /// The value of . public IdentityCache(IAsyncDelayer asyncDelayer, ILogger logger) { this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); cachedIdentities = new Dictionary(); } /// public ValueTask DisposeAsync() { logger.LogTrace("Disposing..."); return ValueTaskExtensions.WhenAll(cachedIdentities.Select(x => x.Value.DisposeAsync())); } /// public async ValueTask CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(systemIdentity); var uid = user.Require(x => x.Id); var sysId = systemIdentity.Uid; ValueTask oldIdentityDisposal = ValueTask.CompletedTask; try { lock (cachedIdentities) { logger.LogDebug("Caching system identity {sysId} of user {uid}", sysId, uid); if (cachedIdentities.TryGetValue(uid, out var identCache)) { logger.LogTrace("Expiring previously cached identity..."); oldIdentityDisposal = identCache.DisposeAsync(); // also clears it out } identCache = new IdentityCacheObject( systemIdentity.Clone(), asyncDelayer, () => { logger.LogDebug("Expiring system identity cache for user {uid}", uid); lock (cachedIdentities) cachedIdentities.Remove(uid); }, expiry); cachedIdentities.Add(uid, identCache); } } finally { await oldIdentityDisposal; } } /// public ISystemIdentity LoadCachedIdentity(User user) { ArgumentNullException.ThrowIfNull(user); var uid = user.Require(x => x.Id); lock (cachedIdentities) if (cachedIdentities.TryGetValue(uid, out var identity)) return identity.SystemIdentity.Clone(); throw new InvalidOperationException("Cached system identity has expired!"); } } }