From 33af961b0c5c2190d7224116b652e07682e6411a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 23 May 2018 16:38:59 +0200 Subject: [PATCH] Rename options.AddMvcBinders() to options.UseMvc() and add an option allowing to disable binding exceptions --- samples/Mvc.Server/Startup.cs | 4 +- src/OpenIddict.Mvc/OpenIddictExtensions.cs | 47 ------ ...tModelBinder.cs => OpenIddictMvcBinder.cs} | 67 ++++---- .../OpenIddictMvcBinderProvider.cs | 42 +++++ src/OpenIddict.Mvc/OpenIddictMvcBuilder.cs | 67 ++++++++ src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs | 157 ++++++++++++++++++ src/OpenIddict.Mvc/OpenIddictMvcOptions.cs | 24 +++ src/OpenIddict/OpenIddict.csproj | 1 + ...penIddictEntityFrameworkExtensionsTests.cs | 9 +- ...ddictEntityFrameworkCoreExtensionsTests.cs | 12 +- .../OpenIddictExtensionsTests.cs | 35 ---- .../OpenIddictMvcBuilderTests.cs | 43 +++++ .../OpenIddictMvcExtensionsTests.cs | 75 +++++++++ .../OpenIddictMvcModelBinderProviderTests.cs | 63 +++++++ ...ts.cs => OpenIddictMvcModelBinderTests.cs} | 118 +++++++------ .../OpenIddictServerBuilderTests.cs | 31 +--- 16 files changed, 585 insertions(+), 210 deletions(-) delete mode 100644 src/OpenIddict.Mvc/OpenIddictExtensions.cs rename src/OpenIddict.Mvc/{OpenIddictModelBinder.cs => OpenIddictMvcBinder.cs} (55%) create mode 100644 src/OpenIddict.Mvc/OpenIddictMvcBinderProvider.cs create mode 100644 src/OpenIddict.Mvc/OpenIddictMvcBuilder.cs create mode 100644 src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs create mode 100644 src/OpenIddict.Mvc/OpenIddictMvcOptions.cs delete mode 100644 test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs create mode 100644 test/OpenIddict.Mvc.Tests/OpenIddictMvcBuilderTests.cs create mode 100644 test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs create mode 100644 test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderProviderTests.cs rename test/OpenIddict.Mvc.Tests/{OpenIddictModelBinderTests.cs => OpenIddictMvcModelBinderTests.cs} (64%) diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 9624c63d..c51e82f3 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -79,10 +79,10 @@ namespace Mvc.Server // Register the OpenIddict server handler. .AddServer(options => { - // Register the ASP.NET Core MVC binder used by OpenIddict. + // Register the ASP.NET Core MVC services used by OpenIddict. // Note: if you don't call this method, you won't be able to // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. - options.AddMvcBinders(); + options.UseMvc(); // Enable the authorization, logout, token and userinfo endpoints. options.EnableAuthorizationEndpoint("/connect/authorize") diff --git a/src/OpenIddict.Mvc/OpenIddictExtensions.cs b/src/OpenIddict.Mvc/OpenIddictExtensions.cs deleted file mode 100644 index abe2f8d4..00000000 --- a/src/OpenIddict.Mvc/OpenIddictExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 JetBrains.Annotations; -using Microsoft.AspNetCore.Mvc; -using OpenIddict.Mvc; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class OpenIddictExtensions - { - /// - /// Registers the ASP.NET Core MVC model binders used by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerBuilder AddMvcBinders([NotNull] this OpenIddictServerBuilder builder) - { - if (builder == null) - { - 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 OpenIddictModelBinder) - { - return; - } - } - - options.ModelBinderProviders.Insert(0, new OpenIddictModelBinder()); - }); - - return builder; - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.Mvc/OpenIddictModelBinder.cs b/src/OpenIddict.Mvc/OpenIddictMvcBinder.cs similarity index 55% rename from src/OpenIddict.Mvc/OpenIddictModelBinder.cs rename to src/OpenIddict.Mvc/OpenIddictMvcBinder.cs index 3e314be0..857669b2 100644 --- a/src/OpenIddict.Mvc/OpenIddictModelBinder.cs +++ b/src/OpenIddict.Mvc/OpenIddictMvcBinder.cs @@ -1,10 +1,18 @@ -using System; +/* + * 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.Text; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Primitives; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace OpenIddict.Mvc { @@ -13,8 +21,15 @@ namespace OpenIddict.Mvc /// and /// instances. /// - public class OpenIddictModelBinder : IModelBinder, IModelBinderProvider + public class OpenIddictMvcBinder : IModelBinder { + private readonly IOptionsMonitor _options; + + public OpenIddictMvcBinder([NotNull] IOptionsMonitor options) + { + _options = options; + } + /// /// Tries to bind a model from the request. /// @@ -30,21 +45,27 @@ namespace OpenIddict.Mvc if (context.ModelType == typeof(OpenIdConnectRequest)) { var request = context.HttpContext.GetOpenIdConnectRequest(); - if (request == null) + if (request == null && !_options.CurrentValue.DisableBindingExceptions) { - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved from the ASP.NET context. " + - "Make sure that 'app.UseAuthentication()' is called before 'app.UseMvc()' " + - "and that the action route corresponds to the endpoint path registered via " + - "'services.AddOpenIddict().Enable[...]Endpoint(...)'."); + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The OpenID Connect request cannot be retrieved from the ASP.NET context.") + .Append("Make sure that 'app.UseAuthentication()' is called before 'app.UseMvc()' ") + .Append("and that the action route corresponds to the endpoint path registered via ") + .Append("'services.AddOpenIddict().AddServer().Enable[...]Endpoint(...)'.") + .ToString()); } - // Add a new validation state entry to prevent the built-in - // model validators from validating the OpenID Connect request. - context.ValidationState.Add(request, new ValidationStateEntry + if (request != null) { - SuppressValidation = true - }); + // Add a new validation state entry to prevent the built-in + // model validators from validating the OpenID Connect request. + context.ValidationState.Add(request, new ValidationStateEntry + { + SuppressValidation = true + }); + } + context.BindingSource = BindingSource.Special; context.Result = ModelBindingResult.Success(request); return Task.CompletedTask; @@ -63,6 +84,7 @@ namespace OpenIddict.Mvc }); } + context.BindingSource = BindingSource.Special; context.Result = ModelBindingResult.Success(response); return Task.CompletedTask; @@ -70,26 +92,5 @@ namespace OpenIddict.Mvc throw new InvalidOperationException("The specified model type is not supported by this binder."); } - - /// - /// Tries to resolve the model binder corresponding to the given model. - /// - /// The model binding context. - /// The current instance or null if the model is not supported. - public IModelBinder GetBinder([NotNull] ModelBinderProviderContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Metadata.ModelType == typeof(OpenIdConnectRequest) || - context.Metadata.ModelType == typeof(OpenIdConnectResponse)) - { - return this; - } - - return null; - } } } diff --git a/src/OpenIddict.Mvc/OpenIddictMvcBinderProvider.cs b/src/OpenIddict.Mvc/OpenIddictMvcBinderProvider.cs new file mode 100644 index 00000000..167c0c8f --- /dev/null +++ b/src/OpenIddict.Mvc/OpenIddictMvcBinderProvider.cs @@ -0,0 +1,42 @@ +/* + * 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.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; + +namespace OpenIddict.Mvc +{ + /// + /// Represents an ASP.NET Core MVC model binder provider that is + /// able to provide instances of . + /// + public class OpenIddictMvcBinderProvider : IModelBinderProvider + { + /// + /// Tries to resolve the model binder corresponding to the given model. + /// + /// The model binding context. + /// The current instance or null if the model is not supported. + public IModelBinder GetBinder([NotNull] ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType == typeof(OpenIdConnectRequest) || + context.Metadata.ModelType == typeof(OpenIdConnectResponse)) + { + return new BinderTypeModelBinder(typeof(OpenIddictMvcBinder)); + } + + return null; + } + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictMvcBuilder.cs b/src/OpenIddict.Mvc/OpenIddictMvcBuilder.cs new file mode 100644 index 00000000..e7555294 --- /dev/null +++ b/src/OpenIddict.Mvc/OpenIddictMvcBuilder.cs @@ -0,0 +1,67 @@ +/* + * 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.ComponentModel; +using AspNet.Security.OpenIdConnect.Primitives; +using JetBrains.Annotations; +using OpenIddict.Mvc; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Exposes the necessary methods required to configure the OpenIddict MVC integration. + /// + public class OpenIddictMvcBuilder + { + /// + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictMvcBuilder([NotNull] IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + Services = services; + } + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict MVC configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictMvcBuilder Configure([NotNull] Action configuration) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + Services.Configure(configuration); + + return this; + } + + /// + /// Configures the OpenIddict MVC binder to avoid throwing an exception + /// when it is unable to bind + /// parameters (e.g because the endpoint is not an OpenID Connect endpoint). + /// + /// The . + public OpenIddictMvcBuilder DisableBindingExceptions() + => Configure(options => options.DisableBindingExceptions = true); + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs new file mode 100644 index 00000000..a689e1ca --- /dev/null +++ b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs @@ -0,0 +1,157 @@ +/* + * 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 JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; +using OpenIddict.Mvc; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class OpenIddictMvcExtensions + { + /// + /// Registers the ASP.NET Core MVC services used by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictMvcBuilder UseMvc([NotNull] this OpenIddictServerBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return RegisterMvcServices(builder.Services); + } + + /// + /// Registers the ASP.NET Core MVC model binders used by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the MVC services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder UseMvc( + [NotNull] this OpenIddictServerBuilder builder, + [NotNull] Action configuration) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + configuration(builder.UseMvc()); + + return builder; + } + + /// + /// Registers the ASP.NET Core MVC services used by OpenIddict. + /// + /// The services builder used by ASP.NET Core MVC to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictMvcBuilder UseOpenIddict([NotNull] this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return RegisterMvcServices(builder.Services); + } + + /// + /// Registers the ASP.NET Core MVC model binders used by OpenIddict. + /// + /// The services builder used by ASP.NET Core MVC to register new services. + /// The configuration delegate used to configure the MVC services. + /// This extension can be safely called multiple times. + /// The . + public static IMvcCoreBuilder UseOpenIddict( + [NotNull] this IMvcCoreBuilder builder, + [NotNull] Action configuration) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + configuration(builder.UseOpenIddict()); + + return builder; + } + + /// + /// Registers the ASP.NET Core MVC services used by OpenIddict. + /// + /// The services builder used by ASP.NET Core MVC to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictMvcBuilder UseOpenIddict([NotNull] this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return RegisterMvcServices(builder.Services); + } + + /// + /// Registers the ASP.NET Core MVC model binders used by OpenIddict. + /// + /// The services builder used by ASP.NET Core MVC to register new services. + /// The configuration delegate used to configure the MVC services. + /// This extension can be safely called multiple times. + /// The . + public static IMvcBuilder UseOpenIddict( + [NotNull] this IMvcBuilder builder, + [NotNull] Action configuration) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + configuration(builder.UseOpenIddict()); + + return builder; + } + + /// + /// Registers the ASP.NET Core MVC services used by OpenIddict. + /// + /// The services collection. + /// The . + private static OpenIddictMvcBuilder RegisterMvcServices([NotNull] IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + 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()); + }); + + return new OpenIddictMvcBuilder(services); + } + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictMvcOptions.cs b/src/OpenIddict.Mvc/OpenIddictMvcOptions.cs new file mode 100644 index 00000000..e4e1e820 --- /dev/null +++ b/src/OpenIddict.Mvc/OpenIddictMvcOptions.cs @@ -0,0 +1,24 @@ +/* + * 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 AspNet.Security.OpenIdConnect.Primitives; + +namespace OpenIddict.Mvc +{ + /// + /// Provides various settings needed to configure the OpenIddict MVC integration. + /// + public class OpenIddictMvcOptions + { + /// + /// Gets or sets a boolean indicating whether the OpenIddict MVC binder should throw + /// an exception when it is unable to bind + /// parameters (e.g because the endpoint is not an OpenID Connect endpoint). + /// If exceptions are disabled, the model is automatically set to null. + /// + public bool DisableBindingExceptions { get; set; } + } +} diff --git a/src/OpenIddict/OpenIddict.csproj b/src/OpenIddict/OpenIddict.csproj index af978351..5fe845d7 100644 --- a/src/OpenIddict/OpenIddict.csproj +++ b/src/OpenIddict/OpenIddict.csproj @@ -14,6 +14,7 @@ + diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs index 25a30425..536b0ea3 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs @@ -8,7 +8,6 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Core; -using OpenIddict.EntityFramework; using OpenIddict.EntityFramework.Models; using Xunit; @@ -24,8 +23,8 @@ namespace OpenIddict.EntityFramework.Tests public void AddEntityFrameworkStores_RegistersEntityFrameworkStoreFactories(Type type) { // Arrange - var services = new ServiceCollection(); - var builder = services.AddOpenIddict().AddCore(); + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); // Act builder.UseEntityFramework(); @@ -38,8 +37,8 @@ namespace OpenIddict.EntityFramework.Tests public void UseEntityFrameworkModels_KeyTypeDefaultsToString() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddOpenIddict().AddCore(); + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); // Act builder.UseEntityFramework(); diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs index 28fd1bad..36d548ad 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs @@ -23,8 +23,8 @@ namespace OpenIddict.EntityFrameworkCore.Tests public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreFactories(Type type) { // Arrange - var services = new ServiceCollection(); - var builder = services.AddOpenIddict().AddCore(); + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); // Act builder.UseEntityFrameworkCore(); @@ -37,8 +37,8 @@ namespace OpenIddict.EntityFrameworkCore.Tests public void UseEntityFrameworkCore_KeyTypeDefaultsToString() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddOpenIddict().AddCore(); + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); // Act builder.UseEntityFrameworkCore(); @@ -57,8 +57,8 @@ namespace OpenIddict.EntityFrameworkCore.Tests public void UseEntityFrameworkCore_KeyTypeCanBeOverriden() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddOpenIddict().AddCore(); + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); // Act builder.UseEntityFrameworkCore().ReplaceDefaultEntities(); diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs deleted file mode 100644 index 5aa9c286..00000000 --- a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Xunit; - -namespace OpenIddict.Mvc.Tests -{ - public class OpenIddictExtensionsTests - { - [Fact] - public void AddMvcBinders_RegistersModelBinderProvider() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = services.AddOpenIddict().AddServer(); - - // Act - builder.AddMvcBinders(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictModelBinder); - } - } -} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictMvcBuilderTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcBuilderTests.cs new file mode 100644 index 00000000..f8136342 --- /dev/null +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcBuilderTests.cs @@ -0,0 +1,43 @@ +/* + * 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 Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace OpenIddict.Mvc.Tests +{ + public class OpenIddictMvcBuilderTests + { + [Fact] + public void Configure_OptionsAreCorrectlyAmended() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.Configure(configuration => configuration.DisableBindingExceptions = true); + + var options = GetOptions(services); + + // Assert + Assert.True(options.DisableBindingExceptions); + } + + private static IServiceCollection CreateServices() + => new ServiceCollection().AddOptions(); + + private static OpenIddictMvcBuilder CreateBuilder(IServiceCollection services) + => new OpenIddictMvcBuilder(services); + + private static OpenIddictMvcOptions GetOptions(IServiceCollection services) + { + var provider = services.BuildServiceProvider(); + return provider.GetRequiredService>().CurrentValue; + } + } +} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs new file mode 100644 index 00000000..5d702844 --- /dev/null +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs @@ -0,0 +1,75 @@ +/* + * 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 Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace OpenIddict.Mvc.Tests +{ + public class OpenIddictMvcExtensionsTests + { + [Fact] + public void UseMvc_RegistersModelBinderProvider() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictServerBuilder(services); + + // Act + builder.UseMvc(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + } + + [Fact] + public void UseOpenIddict_MvcCoreBuilder_RegistersModelBinderProvider() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new MvcCoreBuilder(services, new ApplicationPartManager()); + + // Act + builder.UseOpenIddict(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + } + + [Fact] + public void UseOpenIddict_MvcBuilder_RegistersModelBinderProvider() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new MvcBuilder(services, new ApplicationPartManager()); + + // Act + builder.UseOpenIddict(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + } + } +} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderProviderTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderProviderTests.cs new file mode 100644 index 00000000..4d804bb2 --- /dev/null +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderProviderTests.cs @@ -0,0 +1,63 @@ +/* + * 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.Collections.Generic; +using System.Threading.Tasks; +using AspNet.Security.OpenIdConnect.Primitives; +using AspNet.Security.OpenIdConnect.Server; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Moq; +using Xunit; + +namespace OpenIddict.Mvc.Tests +{ + public class OpenIddictMvcModelBinderProviderTests + { + [Theory] + [InlineData(typeof(object))] + [InlineData(typeof(IList))] + [InlineData(typeof(int[]))] + public void GetBinder_ReturnsNullForUnsupportedTypes(Type type) + { + // Arrange + var provider = new OpenIddictMvcBinderProvider(); + + var metadata = new Mock(ModelMetadataIdentity.ForType(type)); + + var context = new Mock(); + context.Setup(mock => mock.Metadata) + .Returns(metadata.Object); + + // Act and assert + Assert.Null(provider.GetBinder(context.Object)); + } + + [Theory] + [InlineData(typeof(OpenIdConnectRequest))] + [InlineData(typeof(OpenIdConnectResponse))] + public void GetBinder_ReturnsNonNullForSupportedTypes(Type type) + { + // Arrange + var provider = new OpenIddictMvcBinderProvider(); + + var metadata = new Mock(ModelMetadataIdentity.ForType(type)); + + var context = new Mock(); + context.Setup(mock => mock.Metadata) + .Returns(metadata.Object); + + // Act and assert + Assert.NotNull((BinderTypeModelBinder) provider.GetBinder(context.Object)); + } + } +} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderTests.cs similarity index 64% rename from test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs rename to test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderTests.cs index 7b0cbc64..ae5de244 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcModelBinderTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; @@ -13,14 +14,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Options; using Moq; using Xunit; namespace OpenIddict.Mvc.Tests { - public class OpenIddictModelBinderTests + public class OpenIddictMvcModelBinderTests { [Theory] [InlineData(typeof(object))] @@ -29,7 +30,10 @@ namespace OpenIddict.Mvc.Tests public async Task BindModelAsync_ThrowsAnExceptionForUnsupportedTypes(Type type) { // Arrange - var binder = new OpenIddictModelBinder(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions()); + + var binder = new OpenIddictMvcBinder(options); var provider = new EmptyModelMetadataProvider(); var context = new DefaultModelBindingContext @@ -50,7 +54,10 @@ namespace OpenIddict.Mvc.Tests public async Task BindModelAsync_ThrowsAnExceptionWhenRequestCannotBeFound() { // Arrange - var binder = new OpenIddictModelBinder(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions()); + + var binder = new OpenIddictMvcBinder(options); var provider = new EmptyModelMetadataProvider(); var context = new DefaultModelBindingContext @@ -69,17 +76,54 @@ namespace OpenIddict.Mvc.Tests return binder.BindModelAsync(context); }); - Assert.Equal("The OpenID Connect request cannot be retrieved from the ASP.NET context. " + - "Make sure that 'app.UseAuthentication()' is called before 'app.UseMvc()' " + - "and that the action route corresponds to the endpoint path registered via " + - "'services.AddOpenIddict().Enable[...]Endpoint(...)'.", exception.Message); + Assert.Equal(new StringBuilder() + .AppendLine("The OpenID Connect request cannot be retrieved from the ASP.NET context.") + .Append("Make sure that 'app.UseAuthentication()' is called before 'app.UseMvc()' ") + .Append("and that the action route corresponds to the endpoint path registered via ") + .Append("'services.AddOpenIddict().AddServer().Enable[...]Endpoint(...)'.") + .ToString(), exception.Message); + } + + [Fact] + public async Task BindModelAsync_ReturnsNullWhenRequestCannotBeFoundAndExceptionsAreDisabled() + { + // Arrange + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions + { + DisableBindingExceptions = true + }); + + var binder = new OpenIddictMvcBinder(options); + var provider = new EmptyModelMetadataProvider(); + + var context = new DefaultModelBindingContext + { + ActionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext(), + }, + + ModelMetadata = provider.GetMetadataForType(typeof(OpenIdConnectRequest)) + }; + + // Act + await binder.BindModelAsync(context); + + // Assert + Assert.True(context.Result.IsModelSet); + Assert.Null(context.Result.Model); + Assert.Equal(BindingSource.Special, context.BindingSource); } [Fact] public async Task BindModelAsync_ReturnsNullWhenResponseCannotBeFound() { // Arrange - var binder = new OpenIddictModelBinder(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions()); + + var binder = new OpenIddictMvcBinder(options); var provider = new EmptyModelMetadataProvider(); var context = new DefaultModelBindingContext @@ -100,13 +144,17 @@ namespace OpenIddict.Mvc.Tests // Assert Assert.True(context.Result.IsModelSet); Assert.Null(context.Result.Model); + Assert.Equal(BindingSource.Special, context.BindingSource); } [Fact] public async Task BindModelAsync_ReturnsAmbientRequest() { // Arrange - var binder = new OpenIddictModelBinder(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions()); + + var binder = new OpenIddictMvcBinder(options); var provider = new EmptyModelMetadataProvider(); var request = new OpenIdConnectRequest(); @@ -136,13 +184,17 @@ namespace OpenIddict.Mvc.Tests Assert.True(context.Result.IsModelSet); Assert.Same(request, context.Result.Model); Assert.True(context.ValidationState[request].SuppressValidation); + Assert.Equal(BindingSource.Special, context.BindingSource); } [Fact] public async Task BindModelAsync_ReturnsAmbientResponse() { // Arrange - var binder = new OpenIddictModelBinder(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMvcOptions()); + + var binder = new OpenIddictMvcBinder(options); var provider = new EmptyModelMetadataProvider(); var response = new OpenIdConnectResponse(); @@ -172,49 +224,7 @@ namespace OpenIddict.Mvc.Tests Assert.True(context.Result.IsModelSet); Assert.Same(response, context.Result.Model); Assert.True(context.ValidationState[response].SuppressValidation); - } - - [Theory] - [InlineData(typeof(object))] - [InlineData(typeof(IList))] - [InlineData(typeof(int[]))] - public void GetBinder_ReturnsNullForUnsupportedTypes(Type type) - { - // Arrange - var provider = new OpenIddictModelBinder(); - - var metadata = new Mock(ModelMetadataIdentity.ForType(type)); - - var context = new Mock(); - context.Setup(mock => mock.Metadata) - .Returns(metadata.Object); - - // Act - var result = provider.GetBinder(context.Object); - - // Assert - Assert.Null(result); - } - - [Theory] - [InlineData(typeof(OpenIdConnectRequest))] - [InlineData(typeof(OpenIdConnectResponse))] - public void GetBinder_ReturnsNonNullForSupportedTypes(Type type) - { - // Arrange - var binder = new OpenIddictModelBinder(); - - var metadata = new Mock(ModelMetadataIdentity.ForType(type)); - - var context = new Mock(); - context.Setup(mock => mock.Metadata) - .Returns(metadata.Object); - - // Act - var result = binder.GetBinder(context.Object); - - // Assert - Assert.Same(binder, result); + Assert.Equal(BindingSource.Special, context.BindingSource); } } } diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index 0216b863..242a0dd0 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -8,13 +8,9 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using AspNet.Security.OpenIdConnect.Primitives; -using AspNet.Security.OpenIdConnect.Server; using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Moq; @@ -669,36 +665,15 @@ namespace OpenIddict.Server.Tests Assert.True(options.UseReferenceTokens); } - private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services) - => services.AddOpenIddict() - .AddCore(options => - { - options.SetDefaultApplicationEntity() - .SetDefaultAuthorizationEntity() - .SetDefaultScopeEntity() - .SetDefaultTokenEntity(); - }) - - .AddServer(); - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - services.AddAuthentication(); - services.AddDistributedMemoryCache(); - services.AddLogging(); - services.AddSingleton(); + => new ServiceCollection().AddOptions(); - return services; - } + private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services) + => new OpenIddictServerBuilder(services); private static OpenIddictServerOptions GetOptions(IServiceCollection services) { - services.RemoveAll>(); - services.RemoveAll>(); - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); return options.Get(OpenIddictServerDefaults.AuthenticationScheme); }