diff --git a/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs b/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs new file mode 100644 index 00000000..23b1ba74 --- /dev/null +++ b/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs @@ -0,0 +1,39 @@ +/* + * 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; +using AspNet.Security.OpenIdConnect.Primitives; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; + +namespace OpenIddict.Mvc.Internal +{ + /// + /// Contains the methods required to ensure that the OpenIddict MVC configuration is valid. + /// Note: this API supports the OpenIddict infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future minor releases. + /// + public class OpenIddictMvcConfiguration : IConfigureOptions + { + /// + /// Registers the OpenIddict MVC components in the MVC options. + /// + /// The options instance to initialize. + public void Configure([NotNull] MvcOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ModelBinderProviders.Insert(0, new OpenIddictMvcBinderProvider()); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(OpenIdConnectRequest))); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(OpenIdConnectResponse))); + } + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs index 19320f1b..ff22394a 100644 --- a/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs +++ b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs @@ -7,6 +7,8 @@ using System; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using OpenIddict.Mvc.Internal; namespace Microsoft.Extensions.DependencyInjection @@ -29,20 +31,9 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - builder.Services.Configure(options => - { - // Skip the binder registration if it was already added to the providers collection. - for (var index = 0; index < options.ModelBinderProviders.Count; index++) - { - var provider = options.ModelBinderProviders[index]; - if (provider is OpenIddictMvcBinderProvider) - { - return; - } - } - - options.ModelBinderProviders.Insert(0, new OpenIddictMvcBinderProvider()); - }); + // Register the options initializer used by the OpenIddict MVC binding/validation components. + // Note: TryAddEnumerable() is used here to ensure the initializer is only registered once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OpenIddictMvcConfiguration>()); return new OpenIddictMvcBuilder(builder.Services); } diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/Internal/OpenIddictServerConfiguration.cs index e449a036..397ee637 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerConfiguration.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerConfiguration.cs @@ -47,8 +47,13 @@ namespace OpenIddict.Server.Internal /// Registers the OpenIddict server handler in the global authentication options. /// /// The options instance to initialize. - public void Configure(AuthenticationOptions options) + public void Configure([NotNull] AuthenticationOptions options) { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. if (options.SchemeMap.TryGetValue(OpenIddictServerDefaults.AuthenticationScheme, out var builder) && builder.HandlerType != typeof(OpenIddictServerHandler)) diff --git a/src/OpenIddict.Validation/Internal/OpenIddictValidationConfiguration.cs b/src/OpenIddict.Validation/Internal/OpenIddictValidationConfiguration.cs index 022d2e7b..fc9e4e44 100644 --- a/src/OpenIddict.Validation/Internal/OpenIddictValidationConfiguration.cs +++ b/src/OpenIddict.Validation/Internal/OpenIddictValidationConfiguration.cs @@ -36,8 +36,13 @@ namespace OpenIddict.Validation.Internal /// Registers the OpenIddict validation handler in the global authentication options. /// /// The options instance to initialize. - public void Configure(AuthenticationOptions options) + public void Configure([NotNull] AuthenticationOptions options) { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. if (options.SchemeMap.TryGetValue(OpenIddictValidationDefaults.AuthenticationScheme, out var builder) && builder.HandlerType != typeof(OpenIddictValidationHandler)) diff --git a/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs b/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs new file mode 100644 index 00000000..8b76a50b --- /dev/null +++ b/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs @@ -0,0 +1,71 @@ +/* + * 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; +using System.Linq; +using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OpenIddict.Mvc.Internal; +using Xunit; + +namespace OpenIddict.Mvc.Tests +{ + public class OpenIddictConfigurationExtensionsTests + { + [Fact] + public void Configure_ThrowsAnExceptionForNullOptions() + { + // Arrange + var configuration = new OpenIddictMvcConfiguration(); + + // Act and assert + var exception = Assert.Throws(() => configuration.Configure(null)); + + Assert.Equal("options", exception.ParamName); + } + + [Fact] + public void Configure_RegistersModelBinderProvider() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictServerBuilder(services); + + // Act + builder.UseMvc(); + + var options = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + } + + [Fact] + public void Configure_RegistersModelMetadataDetailsProviders() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictServerBuilder(services); + + // Act + builder.UseMvc(); + + var options = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + var providers = options.Value.ModelMetadataDetailsProviders.OfType(); + Assert.Contains(providers, provider => provider.Type == typeof(OpenIdConnectRequest)); + Assert.Contains(providers, provider => provider.Type == typeof(OpenIdConnectResponse)); + } + } +} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs index 027eeec6..6421d311 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs @@ -41,7 +41,7 @@ namespace OpenIddict.Mvc.Tests } [Fact] - public void UseMvc_RegistersModelBinderProvider() + public void UseMvc_RegistersConfiguration() { // Arrange var services = new ServiceCollection(); @@ -52,11 +52,9 @@ namespace OpenIddict.Mvc.Tests // Act builder.UseMvc(); - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - // Assert - Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationType == typeof(OpenIddictMvcConfiguration)); } } } diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerConfigurationTests.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerConfigurationTests.cs index c3896d3a..b5d93355 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerConfigurationTests.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerConfigurationTests.cs @@ -27,6 +27,20 @@ namespace OpenIddict.Server.Internal.Tests { public class OpenIddictServerConfigurationTests { + [Fact] + public void Configure_ThrowsAnExceptionForNullOptions() + { + // Arrange + var configuration = new OpenIddictServerConfiguration( + Mock.Of(), + Mock.Of()); + + // Act and assert + var exception = Assert.Throws(() => configuration.Configure(null)); + + Assert.Equal("options", exception.ParamName); + } + [Fact] public void Configure_ThrowsAnExceptionWhenSchemeIsAlreadyRegisteredWithDifferentHandlerType() { diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs index dd0646c4..e890a351 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs @@ -175,7 +175,7 @@ namespace OpenIddict.Server.Tests [Theory] [InlineData(typeof(IPostConfigureOptions), typeof(OpenIddictServerConfiguration))] [InlineData(typeof(IPostConfigureOptions), typeof(OpenIdConnectServerInitializer))] - public void AddServer_RegistersInitializers(Type serviceType, Type implementationType) + public void AddServer_RegistersConfiguration(Type serviceType, Type implementationType) { // Arrange var services = new ServiceCollection(); diff --git a/test/OpenIddict.Validation.Tests/Internal/OpenIddictValidationConfigurationTests.cs b/test/OpenIddict.Validation.Tests/Internal/OpenIddictValidationConfigurationTests.cs index 755f00a0..3b86357b 100644 --- a/test/OpenIddict.Validation.Tests/Internal/OpenIddictValidationConfigurationTests.cs +++ b/test/OpenIddict.Validation.Tests/Internal/OpenIddictValidationConfigurationTests.cs @@ -23,6 +23,18 @@ namespace OpenIddict.Validation.Internal.Tests { public class OpenIddictValidationConfigurationTests { + [Fact] + public void Configure_ThrowsAnExceptionForNullOptions() + { + // Arrange + var configuration = new OpenIddictValidationConfiguration(Mock.Of()); + + // Act and assert + var exception = Assert.Throws(() => configuration.Configure(null)); + + Assert.Equal("options", exception.ParamName); + } + [Theory] [InlineData(new object[] { new string[] { OpenIddictValidationDefaults.AuthenticationScheme, null } })] [InlineData(new object[] { new string[] { null, OpenIddictValidationDefaults.AuthenticationScheme } })] diff --git a/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs b/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs index 7af9b1b7..99be6172 100644 --- a/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs @@ -136,7 +136,7 @@ namespace OpenIddict.Validation.Tests [Theory] [InlineData(typeof(IPostConfigureOptions), typeof(OpenIddictValidationConfiguration))] [InlineData(typeof(IPostConfigureOptions), typeof(OAuthValidationInitializer))] - public void AddValidation_RegistersInitializers(Type serviceType, Type implementationType) + public void AddValidation_RegistersConfiguration(Type serviceType, Type implementationType) { // Arrange var services = new ServiceCollection();