114 lines
3.8 KiB
C#
114 lines
3.8 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// A process-local <see cref="IQuickConnectStore"/>, 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.
|
|
/// </summary>
|
|
public sealed class InMemoryQuickConnectStore : IQuickConnectStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, Entry<QuickConnectResult>> _requests = new(StringComparer.Ordinal);
|
|
private readonly ConcurrentDictionary<string, Entry<AuthenticationResult>> _authorizations = new(StringComparer.Ordinal);
|
|
private readonly ConcurrentDictionary<string, DateTime> _authorizationClaims = new(StringComparer.Ordinal);
|
|
|
|
/// <inheritdoc />
|
|
public Task<QuickConnectResult?> GetRequestBySecretAsync(string secret, CancellationToken cancellationToken = default)
|
|
{
|
|
Expire();
|
|
return Task.FromResult(_requests.TryGetValue(secret, out var entry) ? entry.Value : null);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<QuickConnectResult?> 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)));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task SetRequestAsync(QuickConnectResult request, DateTime expiresUtc, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
Expire();
|
|
if (expiresUtc > DateTime.UtcNow)
|
|
{
|
|
_requests[request.Secret] = new Entry<QuickConnectResult>(expiresUtc, request);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<bool> 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));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task SetAuthorizationAsync(string secret, AuthenticationResult authenticationResult, DateTime expiresUtc, CancellationToken cancellationToken = default)
|
|
{
|
|
Expire();
|
|
if (expiresUtc > DateTime.UtcNow)
|
|
{
|
|
_authorizations[secret] = new Entry<AuthenticationResult>(expiresUtc, authenticationResult);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<AuthenticationResult?> 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<T>(DateTime ExpiresUtc, T Value);
|
|
}
|