diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index bb7f6692..3b121383 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -1459,6 +1459,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId A pipe name must be manually set in the OpenIddict Windows integration options when no application name was configured in the .NET generic host options. To set the pipe name, call 'services.AddOpenIddict().AddClient().UseWindows().SetPipeName()'. + + The type extracted from the inter-process notification ({0}) is unknown or not valid, which may indicate that different versions of the OpenIddict client are used for the same application. + + + The payload extracted from the inter-process notification is malformed, incomplete or was created by a different version of the OpenIddict client library. + The security token is missing. diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs index f40b076c..20bc68fa 100644 --- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs +++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsListener.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.IO.Pipes; +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -44,100 +45,120 @@ public sealed class OpenIddictClientWindowsListener : BackgroundService /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + do { - using var buffer = new MemoryStream(); - using var reader = new BinaryReader(buffer); + try + { + using var buffer = new MemoryStream(); + using var reader = new BinaryReader(buffer); #if SUPPORTS_NAMED_PIPE_CONSTRUCTOR_WITH_ACL - using var stream = new NamedPipeServerStream( + using var stream = new NamedPipeServerStream( #elif SUPPORTS_NAMED_PIPE_STATIC_FACTORY_WITH_ACL - using var stream = NamedPipeServerStreamAcl.Create( + using var stream = NamedPipeServerStreamAcl.Create( #else - using var stream = NamedPipeServerStreamConstructors.New( + 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); - - var scope = _provider.CreateScope(); + 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 + => HandleProtocolActivationAsync(_provider, 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 + { + ActivationUri = uri, + IsActivationRedirected = true + }; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static async Task HandleProtocolActivationAsync(IServiceProvider provider, + OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken) + { + var scope = provider.CreateScope(); try { var dispatcher = scope.ServiceProvider.GetRequiredService(); var factory = scope.ServiceProvider.GetRequiredService(); - switch (reader.ReadInt32()) + // 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) { - case 0x01: // Protocol activations + CancellationToken = cancellationToken + }; + + await dispatcher.DispatchAsync(context); + + if (context.IsRejected) + { + await dispatcher.DispatchAsync(new ProcessErrorContext(transaction) { - // Ensure the binary serialization format is supported. - var version = reader.ReadInt32(); - if (version is not 0x01) - { - continue; - } - - var value = reader.ReadString(); - if (string.IsNullOrEmpty(value) || !Uri.TryCreate(value, UriKind.Absolute, out Uri? uri)) - { - continue; - } - - // Create a client transaction and store the command line arguments 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!, - new OpenIddictClientWindowsActivation - { - ActivationUri = uri, - IsActivationRedirected = true - }); - - var context = new ProcessRequestContext(transaction) - { - CancellationToken = stoppingToken - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - await dispatcher.DispatchAsync(new ProcessErrorContext(transaction) - { - CancellationToken = stoppingToken, - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }); - } - - break; - } + CancellationToken = cancellationToken, + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }); } } - // Swallow all exceptions to ensure the service doesn't exit when encountering an exception. - catch (Exception exception) - { - _logger.LogWarning(exception, SR.GetResourceString(SR.ID6213)); - } - finally { if (scope is IAsyncDisposable disposable) diff --git a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs index 3ed44bee..a605d5fb 100644 --- a/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs +++ b/src/OpenIddict.Client.Windows/OpenIddictClientWindowsService.cs @@ -67,7 +67,7 @@ public sealed class OpenIddictClientWindowsService : IHostedService return Task.CompletedTask; } - return ExecuteAsync(_provider, activation, cancellationToken); + return HandleProtocolActivationAsync(_provider, activation, cancellationToken); [MethodImpl(MethodImplOptions.NoInlining)] static OpenIddictClientWindowsActivation? GetProtocolActivation() @@ -99,7 +99,7 @@ public sealed class OpenIddictClientWindowsService : IHostedService } [MethodImpl(MethodImplOptions.NoInlining)] - static async Task ExecuteAsync(IServiceProvider provider, + static async Task HandleProtocolActivationAsync(IServiceProvider provider, OpenIddictClientWindowsActivation activation, CancellationToken cancellationToken) { var scope = provider.CreateScope();