From d657bdf34adbb25c9cc11920dffe9c8e9b238e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 14 May 2024 03:14:00 +0200 Subject: [PATCH] Update the OpenIddict validation ASP.NET Core and OWIN hosts to allow controlling access token extraction --- .../OpenIddictValidationAspNetCoreBuilder.cs | 24 +++++++ ...penIddictValidationAspNetCoreExtensions.cs | 3 + ...ddictValidationAspNetCoreHandlerFilters.cs | 70 +++++++++++++++++++ .../OpenIddictValidationAspNetCoreHandlers.cs | 3 + .../OpenIddictValidationAspNetCoreOptions.cs | 21 ++++++ .../OpenIddictValidationOwinBuilder.cs | 24 +++++++ .../OpenIddictValidationOwinExtensions.cs | 3 + .../OpenIddictValidationOwinHandlerFilters.cs | 70 +++++++++++++++++++ .../OpenIddictValidationOwinHandlers.cs | 4 ++ .../OpenIddictValidationOwinOptions.cs | 21 ++++++ ...alidationServerIntegrationConfiguration.cs | 2 +- 11 files changed, 244 insertions(+), 1 deletion(-) diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs index 17234f81..bce7128d 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs @@ -46,6 +46,30 @@ public sealed class OpenIddictValidationAspNetCoreBuilder return this; } + /// + /// Prevents OpenIddict from extracting access tokens from the standard "Authorization" header. + /// + /// + /// Disabling access token extraction from the "Authorization" header is NOT recommended. + /// + /// The instance. + public OpenIddictValidationAspNetCoreBuilder DisableAccessTokenExtractionFromAuthorizationHeader() + => Configure(options => options.DisableAccessTokenExtractionFromAuthorizationHeader = true); + + /// + /// Prevents OpenIddict from extracting access tokens from the standard "access_token" body form parameter. + /// + /// The instance. + public OpenIddictValidationAspNetCoreBuilder DisableAccessTokenExtractionFromBodyForm() + => Configure(options => options.DisableAccessTokenExtractionFromBodyForm = true); + + /// + /// Prevents OpenIddict from extracting access tokens from the standard "access_token" query string parameter. + /// + /// The instance. + public OpenIddictValidationAspNetCoreBuilder DisableAccessTokenExtractionFromQueryString() + => Configure(options => options.DisableAccessTokenExtractionFromQueryString = true); + /// /// Sets the realm returned to the caller as part of the WWW-Authenticate header. /// diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs index 4e51fe75..343206f0 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs @@ -38,6 +38,9 @@ public static class OpenIddictValidationAspNetCoreExtensions builder.Services.TryAdd(OpenIddictValidationAspNetCoreHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); // Register the built-in filters used by the default OpenIddict ASP.NET Core validation event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); // Register the option initializer used by the OpenIddict ASP.NET Core validation integration services. diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs index 906d7c6e..29b54219 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using Microsoft.AspNetCore; +using Microsoft.Extensions.Options; namespace OpenIddict.Validation.AspNetCore; @@ -15,6 +16,75 @@ namespace OpenIddict.Validation.AspNetCore; [EditorBrowsable(EditorBrowsableState.Advanced)] public static class OpenIddictValidationAspNetCoreHandlerFilters { + /// + /// Represents a filter that excludes the associated handlers if + /// access token extraction from the Authorization header was disabled. + /// + public sealed class RequireAccessTokenExtractionFromAuthorizationHeaderEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromAuthorizationHeaderEnabled(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.DisableAccessTokenExtractionFromAuthorizationHeader); + } + } + + /// + /// Represents a filter that excludes the associated handlers if access token + /// extraction from the "access_token" body form parameter was disabled. + /// + public sealed class RequireAccessTokenExtractionFromBodyFormEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromBodyFormEnabled(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.DisableAccessTokenExtractionFromBodyForm); + } + } + + /// + /// Represents a filter that excludes the associated handlers if access token + /// extraction from the "access_token" query string parameter was disabled. + /// + public sealed class RequireAccessTokenExtractionFromQueryStringEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromQueryStringEnabled(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.DisableAccessTokenExtractionFromQueryString); + } + } + /// /// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found. /// diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index 4d36183d..be88fe55 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs @@ -182,6 +182,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) .SetType(OpenIddictValidationHandlerType.BuiltIn) @@ -233,6 +234,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) @@ -288,6 +290,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs index 72e02c81..62b3d7b0 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs @@ -11,6 +11,27 @@ namespace OpenIddict.Validation.AspNetCore; /// public sealed class OpenIddictValidationAspNetCoreOptions : AuthenticationSchemeOptions { + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting + /// access tokens from the standard "Authorization" header should be disabled. + /// + /// + /// Disabling access token extraction from the "Authorization" header is NOT recommended. + /// + public bool DisableAccessTokenExtractionFromAuthorizationHeader { get; set; } + + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting access + /// tokens from the standard "access_token" body form parameter should be disabled. + /// + public bool DisableAccessTokenExtractionFromBodyForm { get; set; } + + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting access + /// tokens from the standard "access_token" query string parameter should be disabled. + /// + public bool DisableAccessTokenExtractionFromQueryString { get; set; } + /// /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. /// diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs index 7080a3a1..75bd27c6 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs @@ -46,6 +46,30 @@ public sealed class OpenIddictValidationOwinBuilder return this; } + /// + /// Prevents OpenIddict from extracting access tokens from the standard "Authorization" header. + /// + /// + /// Disabling access token extraction from the "Authorization" header is NOT recommended. + /// + /// The instance. + public OpenIddictValidationOwinBuilder DisableAccessTokenExtractionFromAuthorizationHeader() + => Configure(options => options.DisableAccessTokenExtractionFromAuthorizationHeader = true); + + /// + /// Prevents OpenIddict from extracting access tokens from the standard "access_token" body form parameter. + /// + /// The instance. + public OpenIddictValidationOwinBuilder DisableAccessTokenExtractionFromBodyForm() + => Configure(options => options.DisableAccessTokenExtractionFromBodyForm = true); + + /// + /// Prevents OpenIddict from extracting access tokens from the standard "access_token" query string parameter. + /// + /// The instance. + public OpenIddictValidationOwinBuilder DisableAccessTokenExtractionFromQueryString() + => Configure(options => options.DisableAccessTokenExtractionFromQueryString = true); + /// /// Configures the OpenIddict validation OWIN integration to use active authentication. /// When using active authentication, the principal resolved from the access token is diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs index 4a5eb61a..e89cd440 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs @@ -39,6 +39,9 @@ public static class OpenIddictValidationOwinExtensions builder.Services.TryAdd(OpenIddictValidationOwinHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); // Register the built-in filters used by the default OpenIddict OWIN validation event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); // Register the option initializers used by the OpenIddict OWIN validation integration services. diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs index c1e89144..d1ddfd16 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using Microsoft.Extensions.Options; using Owin; namespace OpenIddict.Validation.Owin; @@ -13,6 +14,75 @@ namespace OpenIddict.Validation.Owin; /// public static class OpenIddictValidationOwinHandlerFilters { + /// + /// Represents a filter that excludes the associated handlers if + /// access token extraction from the Authorization header was disabled. + /// + public sealed class RequireAccessTokenExtractionFromAuthorizationHeaderEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromAuthorizationHeaderEnabled(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.DisableAccessTokenExtractionFromAuthorizationHeader); + } + } + + /// + /// Represents a filter that excludes the associated handlers if access token + /// extraction from the "access_token" body form parameter was disabled. + /// + public sealed class RequireAccessTokenExtractionFromBodyFormEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromBodyFormEnabled(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.DisableAccessTokenExtractionFromBodyForm); + } + } + + /// + /// Represents a filter that excludes the associated handlers if access token + /// extraction from the "access_token" query string parameter was disabled. + /// + public sealed class RequireAccessTokenExtractionFromQueryStringEnabled : IOpenIddictValidationHandlerFilter + { + private readonly IOptionsMonitor _options; + + public RequireAccessTokenExtractionFromQueryStringEnabled(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.DisableAccessTokenExtractionFromQueryString); + } + } + /// /// Represents a filter that excludes the associated handlers if no OWIN request can be found. /// diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index 6ebbd5c5..49c13e80 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -31,6 +31,7 @@ public static partial class OpenIddictValidationOwinHandlers /* * Authentication processing: */ + ValidateHostHeader.Descriptor, ExtractAccessTokenFromAuthorizationHeader.Descriptor, ExtractAccessTokenFromBodyForm.Descriptor, ExtractAccessTokenFromQueryString.Descriptor, @@ -181,6 +182,7 @@ public static partial class OpenIddictValidationOwinHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) .SetType(OpenIddictValidationHandlerType.BuiltIn) @@ -232,6 +234,7 @@ public static partial class OpenIddictValidationOwinHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) @@ -288,6 +291,7 @@ public static partial class OpenIddictValidationOwinHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs index 8352d86c..aeed41ce 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs @@ -18,6 +18,27 @@ public sealed class OpenIddictValidationOwinOptions : AuthenticationOptions : base(OpenIddictValidationOwinDefaults.AuthenticationType) => AuthenticationMode = AuthenticationMode.Passive; + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting + /// access tokens from the standard "Authorization" header should be disabled. + /// + /// + /// Disabling access token extraction from the "Authorization" header is NOT recommended. + /// + public bool DisableAccessTokenExtractionFromAuthorizationHeader { get; set; } + + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting access + /// tokens from the standard "access_token" body form parameter should be disabled. + /// + public bool DisableAccessTokenExtractionFromBodyForm { get; set; } + + /// + /// Gets or sets a boolean indicating whether the built-in logic extracting access + /// tokens from the standard "access_token" query string parameter should be disabled. + /// + public bool DisableAccessTokenExtractionFromQueryString { get; set; } + /// /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. /// diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs index 775256d9..18dae6af 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs +++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs @@ -63,7 +63,7 @@ public sealed class OpenIddictValidationServerIntegrationConfiguration : IConfig throw new ArgumentNullException(nameof(options)); } - if (options.ValidationType != OpenIddictValidationType.Direct) + if (options.ValidationType is not OpenIddictValidationType.Direct) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0170)); }