/* * 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; /// /// Contains the methods required to ensure that the OpenIddict client system integration configuration is valid. /// [EditorBrowsable(EditorBrowsableState.Advanced)] public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureOptions, IPostConfigureOptions, IPostConfigureOptions { private readonly IHostEnvironment _environment; /// /// Creates a new instance of the class. /// /// The host environment. public OpenIddictClientSystemIntegrationConfiguration(IHostEnvironment environment) => _environment = environment ?? throw new ArgumentNullException(nameof(environment)); /// 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); } /// 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)); } } /// 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 } } }