diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs index 4db65aba..3a1e2ed1 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs @@ -115,28 +115,17 @@ public static partial class OpenIddictClientAspNetCoreHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = request.Host.HasValue ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { HasValue: true } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: new Uri(request.GetEncodedUrl(), UriKind.Absolute)), - - { HasValue: false } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase)); + context.RequestUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase, request.Path, request.QueryString)); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs index b1add0b4..b46ddaaf 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs @@ -112,28 +112,17 @@ public static partial class OpenIddictClientOwinHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = !string.IsNullOrEmpty(request.Host.Value) ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { Value.Length: > 0 } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: request.Uri), - - { Value: null or { Length: 0 } } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase); + context.RequestUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase + request.Path + request.QueryString); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs index 9ed00b50..30711c83 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs @@ -260,9 +260,10 @@ public static partial class OpenIddictClientSystemIntegrationHandlers throw new ArgumentNullException(nameof(context)); } + // If the base or request URIs couldn't be resolved, don't try to infer the endpoint type. if (context is not { BaseUri.IsAbsoluteUri: true, RequestUri.IsAbsoluteUri: true }) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0127)); + return default; } // If an endpoint was already inferred by the generic handler, don't override it. diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index 7545ca39..48b9028d 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -243,9 +243,10 @@ public static partial class OpenIddictClientHandlers throw new ArgumentNullException(nameof(context)); } + // If the base or request URIs couldn't be resolved, don't try to infer the endpoint type. if (context is not { BaseUri.IsAbsoluteUri: true, RequestUri.IsAbsoluteUri: true }) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0127)); + return default; } context.EndpointType = diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index 61e4ee2e..c59ea5c7 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -100,28 +100,17 @@ public static partial class OpenIddictServerAspNetCoreHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = request.Host.HasValue ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { HasValue: true } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: new Uri(request.GetEncodedUrl(), UriKind.Absolute)), - - { HasValue: false } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase)); + context.RequestUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase, request.Path, request.QueryString)); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index a21c4c19..b96599f7 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -98,28 +98,17 @@ public static partial class OpenIddictServerOwinHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = !string.IsNullOrEmpty(request.Host.Value) ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { Value.Length: > 0 } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: request.Uri), - - { Value: null or { Length: 0 } } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase); + context.RequestUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase + request.Path + request.QueryString); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 660e6fe8..100f3524 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -152,9 +152,10 @@ public static partial class OpenIddictServerHandlers throw new ArgumentNullException(nameof(context)); } + // If the base or request URIs couldn't be resolved, don't try to infer the endpoint type. if (context is not { BaseUri.IsAbsoluteUri: true, RequestUri.IsAbsoluteUri: true }) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0127)); + return default; } context.EndpointType = diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index 0131b084..e1ae1113 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs @@ -98,28 +98,17 @@ public static partial class OpenIddictValidationAspNetCoreHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = request.Host.HasValue ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { HasValue: true } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: new Uri(request.GetEncodedUrl(), UriKind.Absolute)), - - { HasValue: false } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase)); + context.RequestUri = CreateUri(UriHelper.BuildAbsolute(request.Scheme, host, request.PathBase, request.Path, request.QueryString)); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index c7c0a681..c2020705 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -100,28 +100,17 @@ public static partial class OpenIddictValidationOwinHandlers // used to build an absolute base URI and a request URI that will be used to determine whether the // received request matches one of the URIs assigned to an OpenIddict endpoint. If the request // is later handled by OpenIddict, an additional check will be made to require the Host header. + var host = !string.IsNullOrEmpty(request.Host.Value) ? request.Host : new HostString("localhost"); - (context.BaseUri, context.RequestUri) = request.Host switch - { - { Value.Length: > 0 } host => ( - BaseUri: new Uri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase, UriKind.Absolute), - RequestUri: request.Uri), - - { Value: null or { Length: 0 } } => ( - BaseUri: new UriBuilder - { - Scheme = request.Scheme, - Path = request.PathBase.ToUriComponent() - }.Uri, - RequestUri: new UriBuilder - { - Scheme = request.Scheme, - Path = (request.PathBase + request.Path).ToUriComponent(), - Query = request.QueryString.ToUriComponent() - }.Uri) - }; + context.BaseUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase); + context.RequestUri = CreateUri(request.Scheme + Uri.SchemeDelimiter + host + request.PathBase + request.Path + request.QueryString); return default; + + // Note: the BCL System.Uri class has strict rules (e.g it rejects specific characters and enforces a + // limit of 65519 characters for the complete URI representation). To ensure no exception is thrown if the + // URI cannot be built (which would also affect non-OpenIddict endpoints), Uri.TryCreate() is used here. + static Uri? CreateUri(string value) => Uri.TryCreate(value, UriKind.Absolute, out Uri? uri) ? uri : null; } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index 84b00eb4..62dd72c1 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Security.Claims; using System.Text.Json; using System.Text.Json.Nodes; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -32,6 +33,84 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ { } + [Fact] + public async Task ProcessRequest_IgnoresInvalidBaseUris() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + var request = context.Transaction.GetHttpRequest()!; + request.Host = new HostString("fabrikam.com:100000"); + + return default; + }); + + builder.SetOrder(int.MinValue); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Null(context.BaseUri); + Assert.Null(context.RequestUri); + Assert.Equal(OpenIddictServerEndpointType.Unknown, context.EndpointType); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + await client.GetAsync("/.well-known/openid-configuration", new OpenIddictRequest()); + } + + [Fact] + public async Task ProcessRequest_IgnoresInvalidRequestUris() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + var request = context.Transaction.GetHttpRequest()!; + request.QueryString = new QueryString("?" + new string([.. Enumerable.Repeat('x', 100_000)])); + + return default; + }); + + builder.SetOrder(int.MinValue); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.NotNull(context.BaseUri); + Assert.Null(context.RequestUri); + Assert.Equal(OpenIddictServerEndpointType.Unknown, context.EndpointType); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + await client.GetAsync("/.well-known/openid-configuration", new OpenIddictRequest()); + } + [Fact] public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() { diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index 2b11d0f1..bb5dfa75 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs @@ -29,6 +29,84 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte { } + [Fact] + public async Task ProcessRequest_IgnoresInvalidBaseUris() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + var request = context.Transaction.GetOwinRequest()!; + request.Host = new HostString("fabrikam.com:100000"); + + return default; + }); + + builder.SetOrder(int.MinValue); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Null(context.BaseUri); + Assert.Null(context.RequestUri); + Assert.Equal(OpenIddictServerEndpointType.Unknown, context.EndpointType); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + await client.GetAsync("/.well-known/openid-configuration", new OpenIddictRequest()); + } + + [Fact] + public async Task ProcessRequest_IgnoresInvalidRequestUris() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + var request = context.Transaction.GetOwinRequest()!; + request.QueryString = new QueryString("?" + new string([.. Enumerable.Repeat('x', 100_000)])); + + return default; + }); + + builder.SetOrder(int.MinValue); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.NotNull(context.BaseUri); + Assert.Null(context.RequestUri); + Assert.Equal(OpenIddictServerEndpointType.Unknown, context.EndpointType); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + await client.GetAsync("/.well-known/openid-configuration", new OpenIddictRequest()); + } + [Fact] public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() {