Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

126 lines
6.1 KiB

/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using OpenIddict.Extensions;
namespace OpenIddict.Client.SystemIntegration;
/// <summary>
/// Contains the APIs needed to coordinate authentication operations that happen in a different context.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictClientSystemIntegrationMarshal
{
private readonly ConcurrentDictionary<string, Lazy<(
string RequestForgeryProtection,
SemaphoreSlim Semaphore,
TaskCompletionSource<ProcessAuthenticationContext> TaskCompletionSource)>> _operations = new();
/// <summary>
/// Determines whether the authentication demand corresponding to the specified nonce is tracked.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <returns><see langword="true"/> if the operation is tracked, <see langword="false"/> otherwise.</returns>
internal bool IsTracked(string nonce) => _operations.ContainsKey(nonce);
/// <summary>
/// Tries to add the specified authentication demand to the list of tracked operations.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <param name="protection">The request forgery protection associated with the specified authentication demand.</param>
/// <returns><see langword="true"/> if the operation could be added, <see langword="false"/> otherwise.</returns>
internal bool TryAdd(string nonce, string protection) => _operations.TryAdd(nonce, new(() => (
RequestForgeryProtection: protection,
Semaphore: new SemaphoreSlim(initialCount: 1, maxCount: 1),
TaskCompletionSource: new(TaskCreationOptions.RunContinuationsAsynchronously))));
/// <summary>
/// Tries to acquire a lock on the authentication demand corresponding to the specified nonce.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <returns><see langword="true"/> if the lock could be taken, <see langword="false"/> otherwise.</returns>
internal bool TryAcquireLock(string nonce)
=> _operations.TryGetValue(nonce, out var operation) && operation.Value.Semaphore.Wait(TimeSpan.Zero);
/// <summary>
/// Tries to resolve the authentication context associated with the specified nonce.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <param name="context">The authentication context associated with the tracked operation.</param>
/// <returns><see langword="true"/> if the context could be resolved, <see langword="false"/> otherwise.</returns>
internal bool TryGetResult(string nonce, [NotNullWhen(true)] out ProcessAuthenticationContext? context)
{
if (!_operations.TryGetValue(nonce, out var operation))
{
context = null;
return false;
}
if (!operation.IsValueCreated || !operation.Value.TaskCompletionSource.Task.IsCompleted)
{
context = null;
return false;
}
context = operation.Value.TaskCompletionSource.Task.Result;
return true;
}
/// <summary>
/// Tries to wait for the authentication demand associated with the specified nonce to complete.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><see langword="true"/> if the authentication demand is tracked, <see langword="false"/> otherwise.</returns>
/// <exception cref="OperationCanceledException">The operation was canceled by the user.</exception>
internal async Task<bool> TryWaitForCompletionAsync(string nonce, CancellationToken cancellationToken)
{
if (!_operations.TryGetValue(nonce, out var operation))
{
return false;
}
await operation.Value.TaskCompletionSource.Task.WaitAsync(cancellationToken);
return true;
}
/// <summary>
/// Tries to resolve the request forgery protection associated with the specified authentication demand.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <param name="protection">The request forgery protection associated with the specified authentication demand.</param>
/// <returns><see langword="true"/> if the operation could be validated, <see langword="false"/> otherwise.</returns>
internal bool TryGetRequestForgeryProtection(string nonce, [NotNullWhen(true)] out string? protection)
{
if (_operations.TryGetValue(nonce, out var operation))
{
protection = operation.Value.RequestForgeryProtection;
return true;
}
protection = null;
return false;
}
/// <summary>
/// Tries to complete the specified authentication demand.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <param name="context">The authentication context that will be returned to the caller.</param>
/// <returns><see langword="true"/> if the operation could be completed, <see langword="false"/> otherwise.</returns>
internal bool TryComplete(string nonce, ProcessAuthenticationContext context)
=> _operations.TryGetValue(nonce, out var operation) && operation.Value.TaskCompletionSource.TrySetResult(context);
/// <summary>
/// Tries to remove the specified authentication operation from the list of tracked operations.
/// </summary>
/// <param name="nonce">The nonce, used as a unique identifier.</param>
/// <returns><see langword="true"/> if the operation could be removed, <see langword="false"/> otherwise.</returns>
internal bool TryRemove(string nonce) => _operations.TryRemove(nonce, out _);
}