/* * 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; /// /// 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). /// public sealed class OpenIddictClientSystemIntegrationService { private readonly IOptionsMonitor _options; private readonly IServiceProvider _provider; /// /// Creates a new instance of the class. /// /// The OpenIddict client system integration options. /// The service provider. /// is . public OpenIddictClientSystemIntegrationService( IOptionsMonitor options, IServiceProvider provider) { _options = options ?? throw new ArgumentNullException(nameof(options)); _provider = provider ?? throw new ArgumentNullException(nameof(provider)); } /// /// Handles the specified protocol activation. /// /// The protocol activation details. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// is . [EditorBrowsable(EditorBrowsableState.Advanced)] public Task HandleProtocolActivationAsync( OpenIddictClientSystemIntegrationActivation activation, CancellationToken cancellationToken = default) => HandleRequestAsync(activation ?? throw new ArgumentNullException(nameof(activation)), cancellationToken); /// /// Handles the specified HTTP request. /// /// The HTTP request received by the embedded web server. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// is . internal Task HandleHttpRequestAsync(HttpListenerContext request, CancellationToken cancellationToken = default) => HandleRequestAsync(request ?? throw new ArgumentNullException(nameof(request)), cancellationToken); #if SUPPORTS_WINDOWS_RUNTIME /// /// Handles the specified web authentication result. /// /// The web authentication result. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// is . [SupportedOSPlatform("windows10.0.17763")] internal Task HandleWebAuthenticationResultAsync(WebAuthenticationResult result, CancellationToken cancellationToken = default) => HandleRequestAsync(result ?? throw new ArgumentNullException(nameof(result)), cancellationToken); #endif /// /// Handles the request using the specified property. /// /// The property to add to the transaction. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// is . private async Task HandleRequestAsync(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(); var factory = scope.ServiceProvider.GetRequiredService(); // 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(); } } } /// /// Redirects a protocol activation to the specified instance. /// /// The protocol activation to redirect. /// The identifier of the target instance. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// is . 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); } }