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.
 
 
 
 
 
 

189 lines
8.5 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.ComponentModel;
using System.IO.Pipes;
using System.Net;
using System.Runtime.Versioning;
using System.Security.Principal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
#if SUPPORTS_WINDOWS_RUNTIME
using Windows.Security.Authentication.Web;
#endif
namespace OpenIddict.Client.SystemIntegration;
/// <summary>
/// Contains the logic necessary to handle URI protocol activations (that
/// are typically resolved when launching the application or redirected
/// by other instances using inter-process communication).
/// </summary>
public sealed class OpenIddictClientSystemIntegrationService
{
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientSystemIntegrationService"/> class.
/// </summary>
/// <param name="options">The OpenIddict client system integration options.</param>
/// <param name="provider">The service provider.</param>
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
public OpenIddictClientSystemIntegrationService(
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options,
IServiceProvider provider)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Handles the specified protocol activation.
/// </summary>
/// <param name="activation">The protocol activation details.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="activation"/> is <see langword="null"/>.</exception>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public Task HandleProtocolActivationAsync(
OpenIddictClientSystemIntegrationActivation activation, CancellationToken cancellationToken = default)
=> HandleRequestAsync(activation ?? throw new ArgumentNullException(nameof(activation)), cancellationToken);
/// <summary>
/// Handles the specified HTTP request.
/// </summary>
/// <param name="request">The HTTP request received by the embedded web server.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
internal Task HandleHttpRequestAsync(HttpListenerContext request, CancellationToken cancellationToken = default)
=> HandleRequestAsync(request ?? throw new ArgumentNullException(nameof(request)), cancellationToken);
#if SUPPORTS_WINDOWS_RUNTIME
/// <summary>
/// Handles the specified web authentication result.
/// </summary>
/// <param name="result">The web authentication result.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="result"/> is <see langword="null"/>.</exception>
[SupportedOSPlatform("windows10.0.17763")]
internal Task HandleWebAuthenticationResultAsync(WebAuthenticationResult result, CancellationToken cancellationToken = default)
=> HandleRequestAsync(result ?? throw new ArgumentNullException(nameof(result)), cancellationToken);
#endif
/// <summary>
/// Handles the request using the specified property.
/// </summary>
/// <param name="property">The property to add to the transaction.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="property"/> is <see langword="null"/>.</exception>
private async Task HandleRequestAsync<TProperty>(TProperty property, CancellationToken cancellationToken) where TProperty : class
{
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}
cancellationToken.ThrowIfCancellationRequested();
var scope = _provider.CreateScope();
try
{
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
// Create a client transaction and store the specified instance so
// it can be retrieved by the event handlers that need to access it.
var transaction = await factory.CreateTransactionAsync();
transaction.SetProperty(typeof(TProperty).FullName!, property);
var context = new ProcessRequestContext(transaction)
{
CancellationToken = cancellationToken
};
await dispatcher.DispatchAsync(context);
if (context.IsRejected)
{
await dispatcher.DispatchAsync(new ProcessErrorContext(transaction)
{
CancellationToken = cancellationToken,
Error = context.Error ?? Errors.InvalidRequest,
ErrorDescription = context.ErrorDescription,
ErrorUri = context.ErrorUri,
Response = new OpenIddictResponse()
});
}
}
finally
{
if (scope is IAsyncDisposable disposable)
{
await disposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
/// <summary>
/// Redirects a protocol activation to the specified instance.
/// </summary>
/// <param name="activation">The protocol activation to redirect.</param>
/// <param name="identifier">The identifier of the target instance.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="activation"/> is <see langword="null"/>.</exception>
internal async Task RedirectProtocolActivationAsync(
OpenIddictClientSystemIntegrationActivation activation,
string identifier, CancellationToken cancellationToken = default)
{
if (activation is null)
{
throw new ArgumentNullException(nameof(activation));
}
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.FormatID0366(nameof(identifier)), nameof(identifier));
}
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
using var stream = new NamedPipeClientStream(
serverName : ".",
pipeName : $@"{_options.CurrentValue.PipeName}\{identifier}",
direction : PipeDirection.Out,
options : PipeOptions.Asynchronous,
impersonationLevel: TokenImpersonationLevel.None,
inheritability : HandleInheritability.None);
// Wait for the target to accept the pipe connection.
await stream.ConnectAsync(cancellationToken);
// Write the type of message stored in the shared memory and the
// version used to identify the binary serialization format.
writer.Write(0x01);
writer.Write(0x01);
// Write the protocol activation URI.
writer.Write(activation.ActivationUri.AbsoluteUri);
// Transfer the payload to the target.
buffer.Seek(0L, SeekOrigin.Begin);
await buffer.CopyToAsync(stream, bufferSize: 81_920, cancellationToken);
}
}