46 changed files with 3781 additions and 867 deletions
@ -0,0 +1,30 @@ |
|||
/* |
|||
* 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.Runtime.Versioning; |
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict client system integration.
|
|||
/// </summary>
|
|||
public enum OpenIddictClientSystemIntegrationAuthenticationMode |
|||
{ |
|||
/// <summary>
|
|||
/// Browser-based authentication.
|
|||
/// </summary>
|
|||
SystemBrowser = 0, |
|||
|
|||
/// <summary>
|
|||
/// Windows web authentication broker-based authentication.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note: the web authentication broker is only supported in UWP applications
|
|||
/// and its use is generally not recommended due to its inherent limitations.
|
|||
/// </remarks>
|
|||
[SupportedOSPlatform("windows10.0.17763")] |
|||
WebAuthenticationBroker = 1 |
|||
} |
|||
@ -0,0 +1,238 @@ |
|||
/* |
|||
* 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.Runtime.InteropServices; |
|||
using System.Runtime.Versioning; |
|||
using OpenIddict.Client.SystemIntegration; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
|
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure
|
|||
/// the OpenIddict client system integration.
|
|||
/// </summary>
|
|||
public sealed class OpenIddictClientSystemIntegrationBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictClientSystemIntegrationBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictClientSystemIntegrationBuilder(IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict client system integration configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder Configure(Action<OpenIddictClientSystemIntegrationOptions> configuration) |
|||
{ |
|||
if (configuration is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Uses the Windows web authentication broker to start authentication flows.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note: the web authentication broker is only supported in UWP applications
|
|||
/// and its use is generally not recommended due to its inherent limitations.
|
|||
/// </remarks>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
[SupportedOSPlatform("windows10.0.17763")] |
|||
public OpenIddictClientSystemIntegrationBuilder UseWebAuthenticationBroker() |
|||
{ |
|||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392)); |
|||
} |
|||
|
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
if (!OpenIddictClientSystemIntegrationHelpers.IsWindowsRuntimeSupported()) |
|||
{ |
|||
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392)); |
|||
} |
|||
|
|||
return Configure(options => options.AuthenticationMode = |
|||
OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker); |
|||
#else
|
|||
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392)); |
|||
#endif
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Uses the system browser to start authentication flows.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder UseSystemBrowser() |
|||
=> Configure(options => options.AuthenticationMode = |
|||
OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser); |
|||
|
|||
/// <summary>
|
|||
/// Sets the timeout after which authentication demands that
|
|||
/// are not completed are automatically aborted by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="timeout">The authentication timeout.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder SetAuthenticationTimeout(TimeSpan timeout) |
|||
=> Configure(options => options.AuthenticationTimeout = timeout); |
|||
|
|||
/// <summary>
|
|||
/// Disables the built-in protocol activation processing logic.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder DisableActivationHandling() |
|||
=> Configure(options => options.EnableActivationHandling = false); |
|||
|
|||
/// <summary>
|
|||
/// Enables the built-in protocol activation processing logic.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder EnableActivationHandling() |
|||
=> Configure(options => options.EnableActivationHandling = true); |
|||
|
|||
/// <summary>
|
|||
/// Disables the built-in protocol activation redirection logic.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder DisableActivationRedirection() |
|||
=> Configure(options => options.EnableActivationRedirection = false); |
|||
|
|||
/// <summary>
|
|||
/// Enables the built-in protocol activation redirection logic.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder EnableActivationRedirection() |
|||
=> Configure(options => options.EnableActivationRedirection = true); |
|||
|
|||
/// <summary>
|
|||
/// Disables the built-in web server used to handle callbacks.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder DisableEmbeddedWebServer() |
|||
=> Configure(options => options.EnableEmbeddedWebServer = false); |
|||
|
|||
/// <summary>
|
|||
/// Enables the built-in web server used to handle callbacks.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder EnableEmbeddedWebServer() |
|||
=> Configure(options => options.EnableEmbeddedWebServer = true); |
|||
|
|||
/// <summary>
|
|||
/// Disables the pipe server used to process notifications (e.g protocol
|
|||
/// activation redirections) sent by other instances of the application.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder DisablePipeServer() |
|||
=> Configure(options => options.EnablePipeServer = false); |
|||
|
|||
/// <summary>
|
|||
/// Enables the pipe server used to process protocol
|
|||
/// activations redirected by other instances of the application.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictClientSystemIntegrationBuilder EnablePipeServer() |
|||
=> Configure(options => options.EnablePipeServer = true); |
|||
|
|||
/// <summary>
|
|||
/// Sets the identifier used to represent the current application
|
|||
/// instance and redirect protocol activations when necessary.
|
|||
/// </summary>
|
|||
/// <param name="identifier">The identifier of the current instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientSystemIntegrationBuilder SetInstanceIdentifier(string identifier) |
|||
{ |
|||
if (string.IsNullOrEmpty(identifier)) |
|||
{ |
|||
throw new ArgumentException(SR.FormatID0366(nameof(identifier)), nameof(identifier)); |
|||
} |
|||
|
|||
return Configure(options => options.InstanceIdentifier = identifier); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the base name of the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the pipe.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientSystemIntegrationBuilder SetPipeName(string name) |
|||
{ |
|||
if (string.IsNullOrEmpty(name)) |
|||
{ |
|||
throw new ArgumentException(SR.FormatID0366(nameof(name)), nameof(name)); |
|||
} |
|||
|
|||
return Configure(options => options.PipeName = name); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the options applied to the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <param name="flags">The options flags applied to the pipe.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientSystemIntegrationBuilder SetPipeOptions(PipeOptions flags) |
|||
=> Configure(options => options.PipeOptions = flags); |
|||
|
|||
/// <summary>
|
|||
/// Sets the security policy applied to the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <param name="security">The security policy applied to the pipe.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientSystemIntegrationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced), SupportedOSPlatform("windows")] |
|||
public OpenIddictClientSystemIntegrationBuilder SetPipeSecurity(PipeSecurity security) |
|||
{ |
|||
if (security is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(security)); |
|||
} |
|||
|
|||
return Configure(options => options.PipeSecurity = security); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals(object? obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string? ToString() => base.ToString(); |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
/* |
|||
* 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.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Versioning; |
|||
using System.Security.AccessControl; |
|||
using System.Security.Principal; |
|||
using System.Text; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Extensions; |
|||
|
|||
#if !SUPPORTS_HOST_ENVIRONMENT
|
|||
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment; |
|||
#endif
|
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict client system integration configuration is valid.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureOptions<OpenIddictClientOptions>, |
|||
IPostConfigureOptions<OpenIddictClientOptions>, |
|||
IPostConfigureOptions<OpenIddictClientSystemIntegrationOptions> |
|||
{ |
|||
private readonly IHostEnvironment _environment; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientSystemIntegrationConfiguration"/> class.
|
|||
/// </summary>
|
|||
/// <param name="environment">The host environment.</param>
|
|||
public OpenIddictClientSystemIntegrationConfiguration(IHostEnvironment environment) |
|||
=> _environment = environment ?? throw new ArgumentNullException(nameof(environment)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Configure(OpenIddictClientOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict client system integration components.
|
|||
options.Handlers.AddRange(OpenIddictClientSystemIntegrationHandlers.DefaultHandlers); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PostConfigure(string? name, OpenIddictClientOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Ensure an explicit client URI was set when using the system integration.
|
|||
if (options.ClientUri is not { IsAbsoluteUri: true }) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0384)); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PostConfigure(string? name, OpenIddictClientSystemIntegrationOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && |
|||
!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389)); |
|||
} |
|||
|
|||
// Note: the OpenIddict client system integration is currently only supported on Windows
|
|||
// and Linux. As such, using the system browser as the default authentication method in
|
|||
// conjunction with the embedded web server and activation handling should be always supported.
|
|||
options.AuthenticationMode ??= OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser; |
|||
options.EnableActivationHandling ??= true; |
|||
options.EnableActivationRedirection ??= true; |
|||
options.EnablePipeServer ??= true; |
|||
options.EnableEmbeddedWebServer ??= HttpListener.IsSupported; |
|||
|
|||
// If no explicit instance identifier was specified, use a random GUID.
|
|||
if (string.IsNullOrEmpty(options.InstanceIdentifier)) |
|||
{ |
|||
options.InstanceIdentifier = Guid.NewGuid().ToString(); |
|||
} |
|||
|
|||
// If no explicit pipe name was specified, compute the SHA-256 hash of the
|
|||
// application name resolved from the host and use it as a unique identifier.
|
|||
if (string.IsNullOrEmpty(options.PipeName)) |
|||
{ |
|||
if (string.IsNullOrEmpty(_environment.ApplicationName)) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0386)); |
|||
} |
|||
|
|||
var builder = new StringBuilder(); |
|||
|
|||
// Note: on Windows, the name is deliberately prefixed with "LOCAL\" to support
|
|||
// partial trust/sandboxed applications that are executed in an AppContainer
|
|||
// and cannot communicate with applications outside the sandbox container.
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
builder.Append(@"LOCAL\"); |
|||
} |
|||
|
|||
builder.Append(@"OpenIddict.Client.SystemIntegration-"); |
|||
builder.Append(Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash( |
|||
Encoding.UTF8.GetBytes(_environment.ApplicationName)))); |
|||
|
|||
options.PipeName = builder.ToString(); |
|||
} |
|||
|
|||
#if SUPPORTS_CURRENT_USER_ONLY_PIPE_OPTION
|
|||
if (options.PipeOptions is null && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
// Note: the CurrentUserOnly option is also supported on Windows, but is less
|
|||
// flexible than using a PipeSecurity object (e.g cross-process communication
|
|||
// between elevated and non-elevated processes is not possible with this option).
|
|||
// As such, it's not used on Windows (instead, an ACL-based PipeSecurity is used).
|
|||
options.PipeOptions = PipeOptions.CurrentUserOnly; |
|||
} |
|||
#endif
|
|||
|
|||
// Always configure the pipe to use asynchronous operations,
|
|||
// even if the flag was not explicitly set by the user.
|
|||
options.PipeOptions |= PipeOptions.Asynchronous; |
|||
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
// If no explicit pipe security policy was specified, grant the current user
|
|||
// full control over the created pipe and allow cross-process communication
|
|||
// between elevated and non-elevated processes. Note: if the process executes
|
|||
// inside an AppContainer, don't override the default OS pipe security policy
|
|||
// to allow all applications with the same identity to access the named pipe.
|
|||
if (options.PipeSecurity is null) |
|||
{ |
|||
using var identity = WindowsIdentity.GetCurrent(TokenAccessLevels.Query); |
|||
|
|||
if (!IsRunningInAppContainer(identity)) |
|||
{ |
|||
options.PipeSecurity = new PipeSecurity(); |
|||
options.PipeSecurity.SetOwner(identity.User!); |
|||
options.PipeSecurity.AddAccessRule(new PipeAccessRule(identity.User!, |
|||
PipeAccessRights.FullControl, AccessControlType.Allow)); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
[SupportedOSPlatform("windows")] |
|||
static bool IsRunningInAppContainer(WindowsIdentity identity) |
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
=> OpenIddictClientSystemIntegrationHelpers.IsWindowsRuntimeSupported() && |
|||
OpenIddictClientSystemIntegrationHelpers.HasAppContainerToken(identity); |
|||
#else
|
|||
=> false; |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,170 @@ |
|||
/* |
|||
* 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 Microsoft.Extensions.Options; |
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Contains a collection of event handler filters commonly used by the system integration handlers.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public static class OpenIddictClientSystemIntegrationHandlerFilters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers
|
|||
/// if no explicit nonce was attached to the authentication context.
|
|||
/// </summary>
|
|||
public sealed class RequireAuthenticationNonce : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(!string.IsNullOrEmpty(context.Nonce)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no HTTP listener context can be found.
|
|||
/// </summary>
|
|||
public sealed class RequireHttpListenerContext : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(context.Transaction.GetHttpListenerContext() is not null); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no interactive user session was detected.
|
|||
/// </summary>
|
|||
public sealed class RequireInteractiveSession : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(Environment.UserInteractive); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no protocol activation was found.
|
|||
/// </summary>
|
|||
public sealed class RequireProtocolActivation : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(context.Transaction.GetProtocolActivation() is not null); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers
|
|||
/// if the system browser integration was not enabled.
|
|||
/// </summary>
|
|||
public sealed class RequireSystemBrowser : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options; |
|||
|
|||
public RequireSystemBrowser(IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options) |
|||
=> _options = options ?? throw new ArgumentNullException(nameof(options)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (!context.Transaction.Properties.TryGetValue( |
|||
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) || |
|||
result is not OpenIddictClientSystemIntegrationAuthenticationMode mode) |
|||
{ |
|||
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault(); |
|||
} |
|||
|
|||
return new(mode is OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if
|
|||
/// the web authentication broker integration was not enabled.
|
|||
/// </summary>
|
|||
public sealed class RequireWebAuthenticationBroker : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options; |
|||
|
|||
public RequireWebAuthenticationBroker(IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options) |
|||
=> _options = options ?? throw new ArgumentNullException(nameof(options)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (!context.Transaction.Properties.TryGetValue( |
|||
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) || |
|||
result is not OpenIddictClientSystemIntegrationAuthenticationMode mode) |
|||
{ |
|||
mode = _options.CurrentValue.AuthenticationMode.GetValueOrDefault(); |
|||
} |
|||
|
|||
#pragma warning disable CA1416
|
|||
return new(mode is OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker); |
|||
#pragma warning restore CA1416
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no
|
|||
/// web authentication operation was triggered during the transaction.
|
|||
/// </summary>
|
|||
public sealed class RequireWebAuthenticationResult : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
return new(context.Transaction.GetWebAuthenticationResult() is not null); |
|||
#else
|
|||
return new(false); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,308 @@ |
|||
/* |
|||
* 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.Immutable; |
|||
using System.Diagnostics; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using Microsoft.Extensions.Primitives; |
|||
using OpenIddict.Extensions; |
|||
|
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
using Windows.Security.Authentication.Web; |
|||
#endif
|
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
public static partial class OpenIddictClientSystemIntegrationHandlers |
|||
{ |
|||
public static class Authentication |
|||
{ |
|||
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Authorization request processing: |
|||
*/ |
|||
InvokeWebAuthenticationBroker.Descriptor, |
|||
LaunchSystemBrowser.Descriptor, |
|||
|
|||
/* |
|||
* Redirection request extraction: |
|||
*/ |
|||
ExtractGetHttpListenerRequest<ExtractRedirectionRequestContext>.Descriptor, |
|||
ExtractProtocolActivationParameters<ExtractRedirectionRequestContext>.Descriptor, |
|||
ExtractWebAuthenticationResultData<ExtractRedirectionRequestContext>.Descriptor, |
|||
|
|||
/* |
|||
* Redirection response handling: |
|||
*/ |
|||
AttachHttpResponseCode<ApplyRedirectionResponseContext>.Descriptor, |
|||
AttachCacheControlHeader<ApplyRedirectionResponseContext>.Descriptor, |
|||
ProcessEmptyHttpResponse.Descriptor, |
|||
ProcessUnactionableResponse<ApplyRedirectionResponseContext>.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible for initiating authorization requests using the web authentication broker.
|
|||
/// Note: this handler is not used when the user session is not interactive.
|
|||
/// </summary>
|
|||
public class InvokeWebAuthenticationBroker : IOpenIddictClientHandler<ApplyAuthorizationRequestContext> |
|||
{ |
|||
private readonly OpenIddictClientSystemIntegrationService _service; |
|||
|
|||
public InvokeWebAuthenticationBroker(OpenIddictClientSystemIntegrationService service) |
|||
=> _service = service ?? throw new ArgumentNullException(nameof(service)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictClientHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyAuthorizationRequestContext>() |
|||
.AddFilter<RequireInteractiveSession>() |
|||
.AddFilter<RequireWebAuthenticationBroker>() |
|||
.UseSingletonHandler<InvokeWebAuthenticationBroker>() |
|||
.SetOrder(100_000) |
|||
.SetType(OpenIddictClientHandlerType.BuiltIn) |
|||
.Build(); |
|||
|
|||
/// <inheritdoc/>
|
|||
#pragma warning disable CS1998
|
|||
public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context) |
|||
#pragma warning restore CS1998
|
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); |
|||
|
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
if (string.IsNullOrEmpty(context.RedirectUri)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// OpenIddict represents the complete interactive authentication dance as a two-phase process:
|
|||
// - The challenge, during which the user is redirected to the authorization server, either
|
|||
// by launching the system browser or, as in this case, using a web-view-like approach.
|
|||
//
|
|||
// - The authentication that takes place after the authorization server and the user approved
|
|||
// the demand and redirected the user agent to the client (using either protocol activation,
|
|||
// an embedded web server or by tracking the return URL of the web view created for the process).
|
|||
//
|
|||
// Unlike OpenIddict, WebAuthenticationBroker materializes this process as a single/one-shot API
|
|||
// that opens the system-managed authentication host, navigates to the specified request URI and
|
|||
// doesn't return until the specified callback URI is reached or the modal closed by the user.
|
|||
// To accomodate OpenIddict's model, successful results are processed as any other callback request.
|
|||
|
|||
try |
|||
{ |
|||
// Note: IAsyncOperation<T>.AsTask(context.CancellationToken) is deliberately not used here as
|
|||
// the asynchronous operation returned by the web authentication broker is not cancellable.
|
|||
switch (await WebAuthenticationBroker.AuthenticateAsync( |
|||
options : WebAuthenticationOptions.None, |
|||
requestUri : OpenIddictHelpers.AddQueryStringParameters( |
|||
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), |
|||
parameters: context.Transaction.Request.GetParameters().ToDictionary( |
|||
parameter => parameter.Key, |
|||
parameter => new StringValues((string?[]?) parameter.Value))), |
|||
callbackUri: new Uri(context.RedirectUri, UriKind.Absolute))) |
|||
{ |
|||
case { ResponseStatus: WebAuthenticationStatus.Success } result: |
|||
await _service.HandleWebAuthenticationResultAsync(result, context.CancellationToken); |
|||
context.HandleRequest(); |
|||
return; |
|||
|
|||
// Since the result of this operation is known by the time WebAuthenticationBroker.AuthenticateAsync()
|
|||
// returns, some errors can directly be handled and surfaced here, as part of the challenge handling.
|
|||
|
|||
case { ResponseStatus: WebAuthenticationStatus.UserCancel }: |
|||
context.Reject( |
|||
error: Errors.AccessDenied, |
|||
description: SR.GetResourceString(SR.ID2149), |
|||
uri: SR.FormatID8000(SR.ID2149)); |
|||
|
|||
return; |
|||
|
|||
case { ResponseStatus: WebAuthenticationStatus.ErrorHttp } result: |
|||
context.Reject( |
|||
error: result.ResponseErrorDetail switch |
|||
{ |
|||
400 => Errors.InvalidRequest, |
|||
401 => Errors.InvalidToken, |
|||
403 => Errors.InsufficientAccess, |
|||
429 => Errors.SlowDown, |
|||
500 => Errors.ServerError, |
|||
503 => Errors.TemporarilyUnavailable, |
|||
_ => Errors.ServerError |
|||
}, |
|||
description: SR.FormatID2161(result.ResponseErrorDetail), |
|||
uri: SR.FormatID8000(SR.ID2161)); |
|||
|
|||
return; |
|||
|
|||
default: |
|||
context.Reject( |
|||
error: Errors.ServerError, |
|||
description: SR.GetResourceString(SR.ID2136), |
|||
uri: SR.FormatID8000(SR.ID2136)); |
|||
|
|||
return; |
|||
} |
|||
} |
|||
|
|||
catch |
|||
{ |
|||
context.Reject( |
|||
error: Errors.ServerError, |
|||
description: SR.GetResourceString(SR.ID2136), |
|||
uri: SR.FormatID8000(SR.ID2136)); |
|||
|
|||
return; |
|||
} |
|||
#else
|
|||
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392)); |
|||
#endif
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible for initiating authorization requests using the system browser.
|
|||
/// Note: this handler is not used when the user session is not interactive.
|
|||
/// </summary>
|
|||
public class LaunchSystemBrowser : IOpenIddictClientHandler<ApplyAuthorizationRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictClientHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyAuthorizationRequestContext>() |
|||
.AddFilter<RequireInteractiveSession>() |
|||
.AddFilter<RequireSystemBrowser>() |
|||
.UseSingletonHandler<LaunchSystemBrowser>() |
|||
.SetOrder(InvokeWebAuthenticationBroker.Descriptor.Order + 1_000) |
|||
.SetType(OpenIddictClientHandlerType.BuiltIn) |
|||
.Build(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); |
|||
|
|||
var uri = OpenIddictHelpers.AddQueryStringParameters( |
|||
uri: new Uri(context.AuthorizationEndpoint, UriKind.Absolute), |
|||
parameters: context.Transaction.Request.GetParameters().ToDictionary( |
|||
parameter => parameter.Key, |
|||
parameter => new StringValues((string?[]?) parameter.Value))); |
|||
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
// Note: on Windows, multiple application models exist and must be supported to cover most scenarios:
|
|||
//
|
|||
// - Classical Win32 applications, for which no application-specific restriction is enforced.
|
|||
// - Win32 applications running in an AppContainer, that are very similar to UWP applications.
|
|||
// - Classical UWP applications, for which strict application restrictions are enforced.
|
|||
// - Full-trust UWP applications, that are rare but very similar to classical Win32 applications.
|
|||
// - Modern/hybrid Windows applications, that can be sandboxed or run as full-trust applications.
|
|||
//
|
|||
// Since .NET Standard 2.0 support for UWP was only introduced in Windows 10 1709 (also known
|
|||
// as Fall Creators Update) and OpenIddict requires Windows 10 1809 as the minimum supported
|
|||
// version, Windows 8/8.1's Metro-style/universal applications are deliberately not supported.
|
|||
//
|
|||
// While Process.Start()/ShellExecuteEx() can typically be used without any particular restriction
|
|||
// by non-sandboxed desktop applications to launch the default system browser, calling these
|
|||
// APIs in sandboxed applications will result in an UnauthorizedAccessException being thrown.
|
|||
//
|
|||
// To avoid that, the OpenIddict host needs to determine whether the platform supports Windows
|
|||
// Runtime APIs and favor the Launcher.LaunchUriAsync() API when it's offered by the platform.
|
|||
|
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
if (OpenIddictClientSystemIntegrationHelpers.IsWindowsRuntimeSupported() && await |
|||
OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithWindowsRuntimeAsync(uri)) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
#endif
|
|||
if (await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithShellExecuteAsync(uri)) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && |
|||
await OpenIddictClientSystemIntegrationHelpers.TryLaunchBrowserWithXdgOpenAsync(uri)) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
|
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0385)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible for processing OpenID Connect responses that don't specify any parameter.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
|
|||
/// </summary>
|
|||
public sealed class ProcessEmptyHttpResponse : IOpenIddictClientHandler<ApplyRedirectionResponseContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictClientHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyRedirectionResponseContext>() |
|||
.AddFilter<RequireHttpListenerContext>() |
|||
.UseSingletonHandler<ProcessEmptyHttpResponse>() |
|||
.SetOrder(int.MaxValue - 100_000) |
|||
.SetType(OpenIddictClientHandlerType.BuiltIn) |
|||
.Build(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public async ValueTask HandleAsync(ApplyRedirectionResponseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); |
|||
|
|||
// This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved,
|
|||
// this may indicate that the request was incorrectly processed by another server stack.
|
|||
var response = context.Transaction.GetHttpListenerContext()?.Response ?? |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390)); |
|||
|
|||
// Always return a 200 status, even for responses indicating that the authentication failed.
|
|||
response.StatusCode = 200; |
|||
response.ContentType = "text/plain"; |
|||
|
|||
// Return a message indicating whether the authentication process
|
|||
// succeeded or failed and that will be visible by the user.
|
|||
var buffer = Encoding.UTF8.GetBytes(context.Transaction.Response.Error switch |
|||
{ |
|||
null or { Length: 0 } => "Login completed. Please return to the application.", |
|||
Errors.AccessDenied => "Authorization denied. Please return to the application.", |
|||
_ => "Authentication failed. Please return to the application." |
|||
}); |
|||
|
|||
#if SUPPORTS_STREAM_MEMORY_METHODS
|
|||
await response.OutputStream.WriteAsync(buffer); |
|||
#else
|
|||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length); |
|||
#endif
|
|||
await response.OutputStream.FlushAsync(); |
|||
|
|||
context.HandleRequest(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,190 @@ |
|||
/* |
|||
* 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.Globalization; |
|||
using System.Net; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Extensions; |
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic necessary to handle URI protocol activations that
|
|||
/// are redirected by other instances using inter-process communication.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note: initial URI protocol activations are handled by <see cref="OpenIddictClientSystemIntegrationActivationHandler"/>.
|
|||
/// </remarks>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public sealed class OpenIddictClientSystemIntegrationHttpListener : BackgroundService |
|||
{ |
|||
private readonly TaskCompletionSource<int?> _source = new(); |
|||
private readonly ILogger<OpenIddictClientSystemIntegrationHttpListener> _logger; |
|||
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options; |
|||
private readonly OpenIddictClientSystemIntegrationService _service; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientSystemIntegrationHttpListener"/> class.
|
|||
/// </summary>
|
|||
/// <param name="logger">The logger.</param>
|
|||
/// <param name="options">The OpenIddict client system integration options.</param>
|
|||
/// <param name="service">The OpenIddict client system integration service.</param>
|
|||
public OpenIddictClientSystemIntegrationHttpListener( |
|||
ILogger<OpenIddictClientSystemIntegrationHttpListener> logger, |
|||
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options, |
|||
OpenIddictClientSystemIntegrationService service) |
|||
{ |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
_options = options ?? throw new ArgumentNullException(nameof(options)); |
|||
_service = service ?? throw new ArgumentNullException(nameof(service)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
|||
{ |
|||
// If the embedded web server instantiation was not enabled, signal the task completion source with a
|
|||
// null value to inform the handlers that no HTTP listener is going to be created and return immediately.
|
|||
if (_options.CurrentValue.EnableEmbeddedWebServer is not true) |
|||
{ |
|||
_source.SetResult(result: null); |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
// Note: finding a free port in the IANA dynamic port range can take a bit of time on busy systems.
|
|||
// To ensure the host initialization is not blocked, the whole process is offloaded to the thread pool.
|
|||
await Task.Run(cancellationToken: stoppingToken, function: async () => |
|||
{ |
|||
var (listener, port) = CreateHttpListener(stoppingToken) ?? |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0391)); |
|||
|
|||
// Inform the handlers that the HTTP listener was created and can now be accessed via the specified port.
|
|||
_source.SetResult(port); |
|||
|
|||
using (listener) |
|||
{ |
|||
// Note: while the received load should be minimal, 3 task workers are used
|
|||
// to be able to process multiple requests at the same time, if necessary.
|
|||
var tasks = new Task[3]; |
|||
|
|||
for (var index = 0; index < tasks.Length; index++) |
|||
{ |
|||
tasks[index] = ProcessRequestsAsync(listener, _service, _logger, stoppingToken); |
|||
} |
|||
|
|||
// Wait for all the workers to indicate they finished processing incoming requests.
|
|||
await Task.WhenAll(tasks); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// Ignore exceptions indicating that the host is shutting down and return immediately.
|
|||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
static (HttpListener Listener, int Port)? CreateHttpListener(CancellationToken cancellationToken) |
|||
{ |
|||
// Note: HttpListener doesn't offer a native way to select a random, non-busy port.
|
|||
// To work around this limitation, this local function tries to bind an HttpListener
|
|||
// on the first free port in the IANA dynamic ports range (typically: 49152 to 65535).
|
|||
//
|
|||
// For more information, see
|
|||
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml.
|
|||
|
|||
for (var port = 49152; port < 65535; port++) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
var listener = new HttpListener |
|||
{ |
|||
AuthenticationSchemes = AuthenticationSchemes.Anonymous, |
|||
IgnoreWriteExceptions = true, |
|||
|
|||
// Note: the prefix registration is deliberately not configurable to ensure
|
|||
// only the "localhost" authority is used, which enforces the built-in host
|
|||
// validation performed by HTTP.sys (or the managed .NET implementation on
|
|||
// non-Windows operating systems) and doesn't require running the application
|
|||
// as an administrator or adding a namespace reservation/ACL rule on Windows.
|
|||
Prefixes = { $"http://localhost:{port.ToString(CultureInfo.InvariantCulture)}/" } |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
listener.Start(); |
|||
|
|||
return (listener, port); |
|||
} |
|||
|
|||
catch (HttpListenerException) |
|||
{ |
|||
listener.Close(); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
static async Task ProcessRequestsAsync(HttpListener listener, OpenIddictClientSystemIntegrationService service, |
|||
ILogger<OpenIddictClientSystemIntegrationHttpListener> logger, CancellationToken cancellationToken) |
|||
{ |
|||
while (true) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
try |
|||
{ |
|||
// Note: HttpListener.GetContextAsync() doesn't support cooperative cancellation. To ensure the host
|
|||
// can gracefully shut down without being blocked by an asynchronous call that would never complete,
|
|||
// Task.WaitAsync() is used to stop waiting on the task returned by HttpListener.GetContextAsync()
|
|||
// when the CancellationToken provided by the host indicates that the application is about to shut down.
|
|||
var context = await listener.GetContextAsync().WaitAsync(cancellationToken); |
|||
|
|||
using (context.Response) |
|||
{ |
|||
// Only process requests for which the request URL could be decoded/parsed correctly.
|
|||
if (context.Request.Url is { IsAbsoluteUri: true }) |
|||
{ |
|||
await service.HandleHttpRequestAsync(context, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Surface operation canceled exceptions when the host is shutting down.
|
|||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) |
|||
{ |
|||
throw; |
|||
} |
|||
|
|||
// Swallow other exceptions to ensure the worker doesn't exit when encountering an exception.
|
|||
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) |
|||
{ |
|||
logger.LogWarning(exception, SR.GetResourceString(SR.ID6214)); |
|||
|
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resolves the port associated to the <see cref="HttpListener"/> created by this service, or
|
|||
/// <see langword="null"/> if the embedded web server instantiation was disabled in the options.
|
|||
/// </summary>
|
|||
/// <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, whose result
|
|||
/// returns the port associated to the <see cref="HttpListener"/> created by this service, or
|
|||
/// <see langword="null"/> if the embedded web server instantiation was disabled in the options.
|
|||
/// </returns>
|
|||
internal Task<int?> GetEmbeddedServerPortAsync(CancellationToken cancellationToken = default) |
|||
=> _source.Task.WaitAsync(cancellationToken); |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
/* |
|||
* 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.IO.Pipes; |
|||
using System.Runtime.Versioning; |
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict client system integration.
|
|||
/// </summary>
|
|||
public sealed class OpenIddictClientSystemIntegrationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the authentication mode used to start authentication flows.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If this property is not explicitly set, its value is automatically set by OpenIddict.
|
|||
/// </remarks>
|
|||
public OpenIddictClientSystemIntegrationAuthenticationMode? AuthenticationMode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the timeout after which authentication demands
|
|||
/// that are not completed are automatically aborted by OpenIddict.
|
|||
/// </summary>
|
|||
public TimeSpan AuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(10); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether protocol activation processing should be enabled.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If this property is not explicitly set, its value is automatically set by OpenIddict
|
|||
/// depending on the capabilities of the system on which the application is running.
|
|||
/// </remarks>
|
|||
public bool? EnableActivationHandling { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether protocol activation redirection should be enabled.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If this property is not explicitly set, its value is automatically set by OpenIddict
|
|||
/// depending on the capabilities of the system on which the application is running.
|
|||
/// </remarks>
|
|||
public bool? EnableActivationRedirection { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether a local web server
|
|||
/// should be started on a random port to handle callbacks.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If this property is not explicitly set, its value is automatically set by OpenIddict
|
|||
/// depending on the capabilities of the system on which the application is running.
|
|||
/// </remarks>
|
|||
public bool? EnableEmbeddedWebServer { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether a pipe server should be started to process notifications
|
|||
/// (e.g protocol activation redirections) sent by other instances of the application.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If this property is not explicitly set, its value is automatically set by OpenIddict
|
|||
/// depending on the capabilities of the system on which the application is running.
|
|||
/// </remarks>
|
|||
public bool? EnablePipeServer { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the identifier used to represent the current application
|
|||
/// instance and redirect protocol activations when necessary.
|
|||
/// </summary>
|
|||
public string? InstanceIdentifier { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the base name of the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If no value is explicitly set, a default name is automatically computed.
|
|||
/// </remarks>
|
|||
public string PipeName { get; set; } = default!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the pipe options applied to the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If no value is explicitly set, a default combination is automatically used.
|
|||
/// </remarks>
|
|||
public PipeOptions? PipeOptions { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the security policy applied to the pipe created by OpenIddict
|
|||
/// to enable inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If no value is explicitly set, a default policy is automatically created
|
|||
/// (unless the application is running inside an AppContainer sandbox).
|
|||
/// </remarks>
|
|||
[SupportedOSPlatform("windows")] |
|||
public PipeSecurity? PipeSecurity { get; set; } |
|||
} |
|||
@ -0,0 +1,183 @@ |
|||
/* |
|||
* 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.Runtime.InteropServices; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Extensions; |
|||
|
|||
namespace OpenIddict.Client.SystemIntegration; |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic necessary to handle URI protocol activations that
|
|||
/// are redirected by other instances using inter-process communication.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note: initial URI protocol activations are handled by <see cref="OpenIddictClientSystemIntegrationActivationHandler"/>.
|
|||
/// </remarks>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public sealed class OpenIddictClientSystemIntegrationPipeListener : BackgroundService |
|||
{ |
|||
private readonly ILogger<OpenIddictClientSystemIntegrationPipeListener> _logger; |
|||
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options; |
|||
private readonly OpenIddictClientSystemIntegrationService _service; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientSystemIntegrationPipeListener"/> class.
|
|||
/// </summary>
|
|||
/// <param name="logger">The logger.</param>
|
|||
/// <param name="options">The OpenIddict client system integration options.</param>
|
|||
/// <param name="service">The OpenIddict client system integration service.</param>
|
|||
public OpenIddictClientSystemIntegrationPipeListener( |
|||
ILogger<OpenIddictClientSystemIntegrationPipeListener> logger, |
|||
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options, |
|||
OpenIddictClientSystemIntegrationService service) |
|||
{ |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
_options = options ?? throw new ArgumentNullException(nameof(options)); |
|||
_service = service ?? throw new ArgumentNullException(nameof(service)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
|||
{ |
|||
if (_options.CurrentValue.EnablePipeServer is not true) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
// Offload the whole process to avoid delaying the initialization of the host.
|
|||
await Task.Run(cancellationToken: stoppingToken, function: async () => |
|||
{ |
|||
// Note: while the received load should be minimal, 3 task workers are used
|
|||
// to be able to process multiple notifications at the same time, if necessary.
|
|||
var tasks = new Task[3]; |
|||
|
|||
for (var index = 0; index < tasks.Length; index++) |
|||
{ |
|||
tasks[index] = ProcessNotificationsAsync(_service, _logger, _options.CurrentValue, stoppingToken); |
|||
} |
|||
|
|||
// Wait for all the workers to indicate they finished processing incoming notifications.
|
|||
await Task.WhenAll(tasks); |
|||
}); |
|||
} |
|||
|
|||
// Ignore exceptions indicating that the host is shutting down and return immediately.
|
|||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
static async Task ProcessNotificationsAsync( |
|||
OpenIddictClientSystemIntegrationService service, ILogger<OpenIddictClientSystemIntegrationPipeListener> logger, |
|||
OpenIddictClientSystemIntegrationOptions options, CancellationToken cancellationToken) |
|||
{ |
|||
while (true) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
try |
|||
{ |
|||
using var buffer = new MemoryStream(); |
|||
using var reader = new BinaryReader(buffer); |
|||
using var stream = CreatePipeServerStream(options); |
|||
|
|||
// Wait for a writer to connect to the named pipe.
|
|||
//
|
|||
// Note: NamedPipeServerStream supports cooperative cancellation but it appears that cancellations
|
|||
// are not always properly handled in some obscure circumstances. To ensure the application shutdown
|
|||
// is not delayed by this issue, the Task.WaitAsync(CancellationToken) API is used to stop waiting
|
|||
// for the task returned by WaitForConnectionAsync() to complete when the application shuts down.
|
|||
await stream.WaitForConnectionAsync(cancellationToken).WaitAsync(cancellationToken); |
|||
|
|||
// Copy the content to the memory stream asynchronously and rewind it.
|
|||
await stream.CopyToAsync(buffer, bufferSize: 81_920, cancellationToken); |
|||
buffer.Seek(0L, SeekOrigin.Begin); |
|||
|
|||
// Process the inter-process notification based on its declared type.
|
|||
await (reader.ReadInt32() switch |
|||
{ |
|||
0x01 when ReadProtocolActivation(reader) is var activation |
|||
=> service.HandleProtocolActivationAsync(activation, cancellationToken), |
|||
|
|||
var value => throw new InvalidOperationException(SR.FormatID0387(value)) |
|||
}); |
|||
} |
|||
|
|||
// Ignore exceptions indicating that the host is shutting down and return immediately.
|
|||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) |
|||
{ |
|||
throw; |
|||
} |
|||
|
|||
// Swallow other exceptions to ensure the service doesn't exit when encountering an exception.
|
|||
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) |
|||
{ |
|||
logger.LogWarning(exception, SR.GetResourceString(SR.ID6213)); |
|||
|
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static NamedPipeServerStream CreatePipeServerStream(OpenIddictClientSystemIntegrationOptions options) |
|||
// Note: the ACL-based PipeSecurity class is only supported on Windows. On other operating systems,
|
|||
// PipeOptions.CurrentUserOnly can be used as an alternative, but only for TFMs that implement it.
|
|||
=> RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? |
|||
#if SUPPORTS_NAMED_PIPE_CONSTRUCTOR_WITH_ACL
|
|||
new NamedPipeServerStream( |
|||
#elif SUPPORTS_NAMED_PIPE_STATIC_FACTORY_WITH_ACL
|
|||
NamedPipeServerStreamAcl.Create( |
|||
#else
|
|||
NamedPipeServerStreamConstructors.New( |
|||
#endif
|
|||
pipeName : $@"{options.PipeName}\{options.InstanceIdentifier}", |
|||
direction : PipeDirection.In, |
|||
maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances, |
|||
transmissionMode : PipeTransmissionMode.Byte, |
|||
options : options.PipeOptions.GetValueOrDefault(), |
|||
inBufferSize : 0, |
|||
outBufferSize : 0, |
|||
pipeSecurity : options.PipeSecurity, |
|||
inheritability : HandleInheritability.None, |
|||
additionalAccessRights : default) : |
|||
new NamedPipeServerStream( |
|||
pipeName : $@"{options.PipeName}\{options.InstanceIdentifier}", |
|||
direction : PipeDirection.In, |
|||
maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances, |
|||
transmissionMode : PipeTransmissionMode.Byte, |
|||
options : options.PipeOptions.GetValueOrDefault(), |
|||
inBufferSize : 0, |
|||
outBufferSize : 0); |
|||
|
|||
static OpenIddictClientSystemIntegrationActivation ReadProtocolActivation(BinaryReader reader) |
|||
{ |
|||
// Ensure the binary serialization format is supported.
|
|||
var version = reader.ReadInt32(); |
|||
if (version is not 0x01) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0388)); |
|||
} |
|||
|
|||
var value = reader.ReadString(); |
|||
if (string.IsNullOrEmpty(value) || !Uri.TryCreate(value, UriKind.Absolute, out Uri? uri)) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0388)); |
|||
} |
|||
|
|||
return new OpenIddictClientSystemIntegrationActivation(uri) |
|||
{ |
|||
IsActivationRedirected = true |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
/* |
|||
* 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.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) |
|||
{ |
|||
if (activation is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(activation)); |
|||
} |
|||
|
|||
return HandleRequestAsync(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) |
|||
{ |
|||
if (request is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
return HandleRequestAsync(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>
|
|||
internal Task HandleWebAuthenticationResultAsync(WebAuthenticationResult result, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (result is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(result)); |
|||
} |
|||
|
|||
return HandleRequestAsync(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); |
|||
} |
|||
} |
|||
@ -1,139 +0,0 @@ |
|||
/* |
|||
* 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 OpenIddict.Client.Windows; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
|
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure
|
|||
/// the OpenIddict client Windows integration.
|
|||
/// </summary>
|
|||
public sealed class OpenIddictClientWindowsBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictClientWindowsBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictClientWindowsBuilder(IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict client Windows configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
public OpenIddictClientWindowsBuilder Configure(Action<OpenIddictClientWindowsOptions> configuration) |
|||
{ |
|||
if (configuration is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disables the built-in protocol activation processing logic, which
|
|||
/// can be used to offload this task a separate dedicated executable.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
public OpenIddictClientWindowsBuilder DisableProtocolActivationProcessing() |
|||
=> Configure(options => options.DisableProtocolActivationProcessing = true); |
|||
|
|||
/// <summary>
|
|||
/// Sets the timeout after which authentication demands that
|
|||
/// are not completed are automatically aborted by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="timeout">The authentication timeout.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
public OpenIddictClientWindowsBuilder SetAuthenticationTimeout(TimeSpan timeout) |
|||
=> Configure(options => options.AuthenticationTimeout = timeout); |
|||
|
|||
/// <summary>
|
|||
/// Sets the identifier used to represent the current application
|
|||
/// instance and redirect protocol activations when necessary.
|
|||
/// </summary>
|
|||
/// <param name="identifier">The identifier of the current instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientWindowsBuilder SetInstanceIdentifier(string identifier) |
|||
{ |
|||
if (string.IsNullOrEmpty(identifier)) |
|||
{ |
|||
throw new ArgumentException(SR.FormatID0366(nameof(identifier)), nameof(identifier)); |
|||
} |
|||
|
|||
return Configure(options => options.InstanceIdentifier = identifier); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the base name of the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the pipe.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientWindowsBuilder SetPipeName(string name) |
|||
{ |
|||
if (string.IsNullOrEmpty(name)) |
|||
{ |
|||
throw new ArgumentException(SR.FormatID0366(nameof(name)), nameof(name)); |
|||
} |
|||
|
|||
return Configure(options => options.PipeName = name); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the security policy applied to the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <param name="security">The security policy applied to the pipe.</param>
|
|||
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictClientWindowsBuilder SetPipeSecurity(PipeSecurity security) |
|||
{ |
|||
if (security is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(security)); |
|||
} |
|||
|
|||
return Configure(options => options.PipeSecurity = security); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals(object? obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string? ToString() => base.ToString(); |
|||
} |
|||
@ -1,128 +0,0 @@ |
|||
/* |
|||
* 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.Runtime.CompilerServices; |
|||
using System.Security.AccessControl; |
|||
using System.Security.Principal; |
|||
using System.Text; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Extensions; |
|||
|
|||
#if !SUPPORTS_HOST_ENVIRONMENT
|
|||
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment; |
|||
#endif
|
|||
|
|||
namespace OpenIddict.Client.Windows; |
|||
|
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict client configuration is valid.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public sealed class OpenIddictClientWindowsConfiguration : IConfigureOptions<OpenIddictClientOptions>, |
|||
IPostConfigureOptions<OpenIddictClientOptions>, |
|||
IPostConfigureOptions<OpenIddictClientWindowsOptions> |
|||
{ |
|||
private readonly IHostEnvironment _environment; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientWindowsConfiguration"/> class.
|
|||
/// </summary>
|
|||
/// <param name="environment">The host environment.</param>
|
|||
public OpenIddictClientWindowsConfiguration(IHostEnvironment environment) |
|||
=> _environment = environment ?? throw new ArgumentNullException(nameof(environment)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Configure(OpenIddictClientOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict Windows client components.
|
|||
options.Handlers.AddRange(OpenIddictClientWindowsHandlers.DefaultHandlers); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PostConfigure(string? name, OpenIddictClientOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Ensure an explicit client URI was set when using the Windows integration.
|
|||
if (options.ClientUri is not { IsAbsoluteUri: true }) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0384)); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PostConfigure(string? name, OpenIddictClientWindowsOptions options) |
|||
{ |
|||
if (options is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// If no explicit instance identifier was specified, use a random GUID.
|
|||
if (string.IsNullOrEmpty(options.InstanceIdentifier)) |
|||
{ |
|||
options.InstanceIdentifier = Guid.NewGuid().ToString(); |
|||
} |
|||
|
|||
// If no explicit pipe name was specified, compute the SHA-256 hash of the
|
|||
// application name resolved from the host and use it as a unique identifier.
|
|||
//
|
|||
// Note: the pipe name is deliberately prefixed with "LOCAL\" to support
|
|||
// partial trust/sandboxed applications that are executed in an AppContainer
|
|||
// and cannot communicate with applications outside the sandbox container.
|
|||
if (string.IsNullOrEmpty(options.PipeName)) |
|||
{ |
|||
if (string.IsNullOrEmpty(_environment.ApplicationName)) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0386)); |
|||
} |
|||
|
|||
options.PipeName = $@"LOCAL\OpenIddict.Client.Windows\{
|
|||
Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash( |
|||
Encoding.UTF8.GetBytes(_environment.ApplicationName))) |
|||
}";
|
|||
} |
|||
|
|||
// If no explicit pipe security policy was specified, grant the current user
|
|||
// full control over the created pipe and allow cross-process communication
|
|||
// between elevated and non-elevated processes. Note: if the process executes
|
|||
// inside an AppContainer, don't override the default OS pipe security policy
|
|||
// to allow all applications with the same identity to access the named pipe.
|
|||
if (options.PipeSecurity is null) |
|||
{ |
|||
using var identity = WindowsIdentity.GetCurrent(TokenAccessLevels.Query); |
|||
|
|||
if (!IsRunningInAppContainer(identity)) |
|||
{ |
|||
options.PipeSecurity = new PipeSecurity(); |
|||
options.PipeSecurity.SetOwner(identity.User!); |
|||
options.PipeSecurity.AddAccessRule(new PipeAccessRule(identity.User!, |
|||
PipeAccessRights.FullControl, AccessControlType.Allow)); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
static bool IsRunningInAppContainer(WindowsIdentity identity) |
|||
#if SUPPORTS_WINDOWS_RUNTIME
|
|||
=> OpenIddictClientWindowsHelpers.IsWindowsRuntimeSupported() && |
|||
OpenIddictClientWindowsHelpers.HasAppContainerToken(identity); |
|||
#else
|
|||
=> false; |
|||
#endif
|
|||
} |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
/* |
|||
* 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; |
|||
|
|||
namespace OpenIddict.Client.Windows; |
|||
|
|||
/// <summary>
|
|||
/// Contains a collection of event handler filters commonly used by the Windows handlers.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public static class OpenIddictClientWindowsHandlerFilters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers
|
|||
/// if no explicit nonce was attached to the authentication context.
|
|||
/// </summary>
|
|||
public sealed class RequireAuthenticationNonce : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(!string.IsNullOrEmpty(context.Nonce)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no interactive user session was detected.
|
|||
/// </summary>
|
|||
public sealed class RequireInteractiveSession : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(Environment.UserInteractive); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no Windows activation was found.
|
|||
/// </summary>
|
|||
public sealed class RequireWindowsActivation : IOpenIddictClientHandlerFilter<BaseContext> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public ValueTask<bool> IsActiveAsync(BaseContext context) |
|||
{ |
|||
if (context is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new(context.Transaction.GetWindowsActivation() is not null); |
|||
} |
|||
} |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
/* |
|||
* 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 Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace OpenIddict.Client.Windows; |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic necessary to handle URI protocol activations that
|
|||
/// are redirected by other instances using inter-process communication.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note: initial URI protocol activations are handled by <see cref="OpenIddictClientWindowsHandler"/>.
|
|||
/// </remarks>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public sealed class OpenIddictClientWindowsListener : BackgroundService |
|||
{ |
|||
private readonly ILogger<OpenIddictClientWindowsListener> _logger; |
|||
private readonly IOptionsMonitor<OpenIddictClientWindowsOptions> _options; |
|||
private readonly OpenIddictClientWindowsService _service; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientWindowsHandler"/> class.
|
|||
/// </summary>
|
|||
/// <param name="logger">The logger.</param>
|
|||
/// <param name="options">The OpenIddict client Windows integration options.</param>
|
|||
/// <param name="service">The OpenIddict client Windows service.</param>
|
|||
public OpenIddictClientWindowsListener( |
|||
ILogger<OpenIddictClientWindowsListener> logger, |
|||
IOptionsMonitor<OpenIddictClientWindowsOptions> options, |
|||
OpenIddictClientWindowsService service) |
|||
{ |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
_options = options ?? throw new ArgumentNullException(nameof(options)); |
|||
_service = service ?? throw new ArgumentNullException(nameof(service)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
|||
{ |
|||
do |
|||
{ |
|||
try |
|||
{ |
|||
using var buffer = new MemoryStream(); |
|||
using var reader = new BinaryReader(buffer); |
|||
|
|||
#if SUPPORTS_NAMED_PIPE_CONSTRUCTOR_WITH_ACL
|
|||
using var stream = new NamedPipeServerStream( |
|||
#elif SUPPORTS_NAMED_PIPE_STATIC_FACTORY_WITH_ACL
|
|||
using var stream = NamedPipeServerStreamAcl.Create( |
|||
#else
|
|||
using var stream = NamedPipeServerStreamConstructors.New( |
|||
#endif
|
|||
pipeName : $@"{_options.CurrentValue.PipeName}\{_options.CurrentValue.InstanceIdentifier}", |
|||
direction : PipeDirection.In, |
|||
maxNumberOfServerInstances: 1, |
|||
transmissionMode : PipeTransmissionMode.Message, |
|||
options : PipeOptions.Asynchronous, |
|||
inBufferSize : 0, |
|||
outBufferSize : 0, |
|||
pipeSecurity : _options.CurrentValue.PipeSecurity, |
|||
inheritability : HandleInheritability.None, |
|||
additionalAccessRights : default); |
|||
|
|||
// Wait for a writer to connect to the named pipe.
|
|||
await stream.WaitForConnectionAsync(stoppingToken); |
|||
|
|||
// Copy the content to the memory stream asynchronously and rewind it.
|
|||
await stream.CopyToAsync(buffer, bufferSize: 81_920, stoppingToken); |
|||
buffer.Seek(0L, SeekOrigin.Begin); |
|||
|
|||
// Process the inter-process notification based on its declared type.
|
|||
await (reader.ReadInt32() switch |
|||
{ |
|||
0x01 when GetProtocolActivation(reader) is OpenIddictClientWindowsActivation activation |
|||
=> _service.HandleProtocolActivationAsync(activation, stoppingToken), |
|||
|
|||
var value => throw new InvalidOperationException(SR.FormatID0387(value)) |
|||
}); |
|||
} |
|||
|
|||
// Ignore operation canceled exceptions when the host is shutting down.
|
|||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) |
|||
{ |
|||
} |
|||
|
|||
// Swallow all exceptions to ensure the service doesn't exit when encountering an exception.
|
|||
catch (Exception exception) |
|||
{ |
|||
_logger.LogWarning(exception, SR.GetResourceString(SR.ID6213)); |
|||
} |
|||
} |
|||
|
|||
while (!stoppingToken.IsCancellationRequested); |
|||
|
|||
static OpenIddictClientWindowsActivation GetProtocolActivation(BinaryReader reader) |
|||
{ |
|||
// Ensure the binary serialization format is supported.
|
|||
var version = reader.ReadInt32(); |
|||
if (version is not 0x01) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0388)); |
|||
} |
|||
|
|||
var value = reader.ReadString(); |
|||
if (string.IsNullOrEmpty(value) || !Uri.TryCreate(value, UriKind.Absolute, out Uri? uri)) |
|||
{ |
|||
throw new InvalidOperationException(SR.GetResourceString(SR.ID0388)); |
|||
} |
|||
|
|||
return new OpenIddictClientWindowsActivation(uri) |
|||
{ |
|||
IsActivationRedirected = true |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
/* |
|||
* 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.IO.Pipes; |
|||
|
|||
#if !SUPPORTS_HOST_ENVIRONMENT
|
|||
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment; |
|||
#endif
|
|||
|
|||
namespace OpenIddict.Client.Windows; |
|||
|
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict Windows client integration.
|
|||
/// </summary>
|
|||
public sealed class OpenIddictClientWindowsOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the timeout after which authentication demands
|
|||
/// that are not completed are automatically aborted by OpenIddict.
|
|||
/// </summary>
|
|||
public TimeSpan AuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(10); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether protocol activation processing should be
|
|||
/// disabled, which can be used to offload this task to a separate dedicated executable.
|
|||
/// </summary>
|
|||
public bool DisableProtocolActivationProcessing { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the identifier used to represent the current application
|
|||
/// instance and redirect protocol activations when necessary.
|
|||
/// </summary>
|
|||
public string? InstanceIdentifier { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the base name of the pipe created by OpenIddict to enable
|
|||
/// inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If no value is explicitly set, a default name is automatically computed.
|
|||
/// </remarks>
|
|||
public string PipeName { get; set; } = default!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the security policy applied to the pipe created by OpenIddict
|
|||
/// to enable inter-process communication and handle protocol activation redirections.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If no value is explicitly set, a default policy is automatically created
|
|||
/// (unless the application is running inside an AppContainer sandbox).
|
|||
/// </remarks>
|
|||
public PipeSecurity PipeSecurity { get; set; } = default!; |
|||
} |
|||
@ -1,92 +0,0 @@ |
|||
/* |
|||
* 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 Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace OpenIddict.Client.Windows; |
|||
|
|||
/// <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 OpenIddictClientWindowsService |
|||
{ |
|||
private readonly IServiceProvider _provider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictClientWindowsService"/> class.
|
|||
/// </summary>
|
|||
/// <param name="provider">The service provider.</param>
|
|||
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
|
|||
public OpenIddictClientWindowsService(IServiceProvider provider) |
|||
=> _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 async Task HandleProtocolActivationAsync( |
|||
OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (activation is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(activation)); |
|||
} |
|||
|
|||
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 protocol activation details so they can be
|
|||
// retrieved by the Windows-specific client event handlers that need to access them.
|
|||
var transaction = await factory.CreateTransactionAsync(); |
|||
transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, activation); |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue