using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; using MediaBrowser.Model.QuickConnect; namespace MediaBrowser.Controller.QuickConnect; /// /// A process-local , and the single-instance default. Nothing it holds /// is visible to another instance, so a deployment running more than one has to configure a shared /// store instead. /// public sealed class InMemoryQuickConnectStore : IQuickConnectStore { private readonly ConcurrentDictionary> _requests = new(StringComparer.Ordinal); private readonly ConcurrentDictionary> _authorizations = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _authorizationClaims = new(StringComparer.Ordinal); /// public Task GetRequestBySecretAsync(string secret, CancellationToken cancellationToken = default) { Expire(); return Task.FromResult(_requests.TryGetValue(secret, out var entry) ? entry.Value : null); } /// public Task GetRequestByCodeAsync(string code, CancellationToken cancellationToken = default) { Expire(); return Task.FromResult(_requests.Values .Select(entry => entry.Value) .FirstOrDefault(request => string.Equals(request.Code, code, StringComparison.Ordinal))); } /// public Task SetRequestAsync(QuickConnectResult request, DateTime expiresUtc, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); Expire(); if (expiresUtc > DateTime.UtcNow) { _requests[request.Secret] = new Entry(expiresUtc, request); } return Task.CompletedTask; } /// public Task TryClaimAuthorizationAsync(string secret, DateTime expiresUtc, CancellationToken cancellationToken = default) { Expire(); if (!_requests.TryGetValue(secret, out var entry) || entry.Value.Authenticated) { return Task.FromResult(false); } return Task.FromResult(_authorizationClaims.TryAdd(secret, expiresUtc)); } /// public Task SetAuthorizationAsync(string secret, AuthenticationResult authenticationResult, DateTime expiresUtc, CancellationToken cancellationToken = default) { Expire(); if (expiresUtc > DateTime.UtcNow) { _authorizations[secret] = new Entry(expiresUtc, authenticationResult); } return Task.CompletedTask; } /// public Task GetAuthorizationAsync(string secret, CancellationToken cancellationToken = default) { Expire(); return Task.FromResult(_authorizations.TryGetValue(secret, out var entry) ? entry.Value : null); } private void Expire() { var now = DateTime.UtcNow; foreach (var (secret, entry) in _requests) { if (entry.ExpiresUtc <= now) { _requests.TryRemove(secret, out _); } } foreach (var (secret, entry) in _authorizations) { if (entry.ExpiresUtc <= now) { _authorizations.TryRemove(secret, out _); } } foreach (var (secret, expiresUtc) in _authorizationClaims) { if (expiresUtc <= now) { _authorizationClaims.TryRemove(secret, out _); } } } private sealed record Entry(DateTime ExpiresUtc, T Value); }