diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs index ef11230f..e68f4322 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs @@ -59,6 +59,7 @@ public static class OpenIddictClientSystemIntegrationExtensions // Register the built-in filters used by the default OpenIddict client system integration event handlers. builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs index eedb3953..a57164b1 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlerFilters.cs @@ -34,6 +34,28 @@ public static class OpenIddictClientSystemIntegrationHandlerFilters } } + /// + /// Represents a filter that excludes the associated handlers if the embedded web server was not enabled. + /// + public sealed class RequireEmbeddedWebServerEnabled : IOpenIddictClientHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireEmbeddedWebServerEnabled(IOptionsMonitor options) + => _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new(_options.CurrentValue.EnableEmbeddedWebServer is true); + } + } + /// /// Represents a filter that excludes the associated handlers if no HTTP listener context can be found. /// diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs index 2ee38bf7..de07af9e 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs @@ -1723,6 +1723,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() // Note: only apply the dynamic port replacement logic if the callback request // is going to be received by the system browser to ensure it doesn't apply to // challenge demands handled via a web authentication broker. @@ -1743,10 +1744,11 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // If the redirect_uri uses a loopback host/IP as the authority and doesn't include a non-default port, // determine whether the embedded web server is running: if so, override the port in the redirect_uri // by the port used by the embedded web server (guaranteed to be running if a value is returned). - if (!string.IsNullOrEmpty(context.RedirectUri) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && + if (!string.IsNullOrEmpty(context.RedirectUri) && + Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.IsLoopback && uri.IsDefaultPort && + uri.IsLoopback && + uri.IsDefaultPort && await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) is int port) { var builder = new UriBuilder(context.RedirectUri) @@ -1848,9 +1850,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // contain a value that prevents response_mode=query from being used (token/id_token). ({ Count: > 0 } client, { Count: > 0 } server) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - !string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.Port != await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && + !await IsEmbeddedWebServerRedirectUriAsync() && client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment) && (types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token)) => ResponseModes.Fragment, @@ -1861,10 +1861,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // response_mode=query from being used (token/id_token). ({ Count: > 0 } client, { Count: 0 }) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - !string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.Port != await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && - client.Contains(ResponseModes.Fragment) && + !await IsEmbeddedWebServerRedirectUriAsync() && + client.Contains(ResponseModes.Fragment) && (types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token)) => ResponseModes.Fragment, @@ -1873,10 +1871,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // types contain a value that prevents response_mode=query from being used (token/id_token). ({ Count: > 0 } client, { Count: > 0 } server) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.IsLoopback && - uri.Port == await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && + await IsEmbeddedWebServerRedirectUriAsync() && client.Contains(ResponseModes.FormPost) && server.Contains(ResponseModes.FormPost) && (types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token)) => ResponseModes.FormPost, @@ -1887,10 +1882,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // a value that prevents response_mode=query from being used (token/id_token). ({ Count: > 0 } client, { Count: 0 }) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.IsLoopback && - uri.Port == await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && + await IsEmbeddedWebServerRedirectUriAsync() && client.Contains(ResponseModes.FormPost) && (types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token)) => ResponseModes.FormPost, @@ -1918,9 +1910,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // server, if both the client and the server support response_mode=fragment, use it. ({ Count: > 0 } client, { Count: > 0 } server) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - !string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.Port != await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && + !await IsEmbeddedWebServerRedirectUriAsync() && client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment) => ResponseModes.Fragment, @@ -1928,10 +1918,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // server, if both the client and the server support response_mode=form_post, use it. ({ Count: > 0 } client, { Count: > 0 } server) when IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) && - Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && - string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.IsLoopback && - uri.Port == await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) && + await IsEmbeddedWebServerRedirectUriAsync() && client.Contains(ResponseModes.FormPost) && server.Contains(ResponseModes.FormPost) => ResponseModes.FormPost, @@ -1952,6 +1939,13 @@ public static partial class OpenIddictClientSystemIntegrationHandlers return mode == _options.CurrentValue.AuthenticationMode; } + + async ValueTask IsEmbeddedWebServerRedirectUriAsync() + => _options.CurrentValue.EnableEmbeddedWebServer is true && + Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) && + string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && + uri.IsLoopback && + uri.Port == await _listener.GetEmbeddedServerPortAsync(context.CancellationToken); } } @@ -2099,6 +2093,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() + .AddFilter() // Note: only apply the dynamic port replacement logic if the callback request // is going to be received by the system browser to ensure it doesn't apply to // sign-out demands handled via a web authentication broker are not affected. @@ -2119,10 +2114,11 @@ public static partial class OpenIddictClientSystemIntegrationHandlers // If the post_logout_redirect_uri uses a loopback host/IP as the authority and doesn't include a non-default port, // determine whether the embedded web server is running: if so, override the port in the post_logout_redirect_uri // by the port used by the embedded web server (guaranteed to be running if a value is returned). - if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) && - Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) && + if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) && + Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) && string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && - uri.IsLoopback && uri.IsDefaultPort && + uri.IsLoopback && + uri.IsDefaultPort && await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) is int port) { var builder = new UriBuilder(context.PostLogoutRedirectUri)