Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing FormatException when the login screen is posted with values other than true/false for RememberMe (Lombiq Technologies: OCORE-132) #14845

Merged
merged 7 commits into from
Dec 14, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.Entities;
using OrchardCore.Modules;
using OrchardCore.Mvc.Core.Utilities;
using OrchardCore.Settings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using OrchardCore.Users.ViewModels;

namespace OrchardCore.Users.Services;

/// <summary>
/// Model binder to produce a validation error when a Boolean field contains a value that's not a valid bool. The
/// default model binder would throw a <see cref="FormatException"/>. That's an issue for <see
/// cref="LoginViewModel.RememberMe"/> that can get invalid values from automated cracking attempts.
/// </summary>
internal class SafeBoolModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
ArgumentNullException.ThrowIfNull(nameof(bindingContext));

var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}

MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
if (bool.TryParse(valueProviderResult.FirstValue, out bool result))
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}

var localizer = bindingContext.HttpContext.RequestServices.GetService<IStringLocalizer<SafeBoolModelBinder>>();

bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, localizer["Invalid Boolean value."]);

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace OrchardCore.Users.Services;

internal class SafeBoolModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
ArgumentNullException.ThrowIfNull(context, nameof(context));

if (context.Metadata.ModelType == typeof(bool))
{
return new SafeBoolModelBinder();
}

return null;
}
}
10 changes: 9 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Users/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
using OrchardCore.Admin.Models;
using OrchardCore.Data;
using OrchardCore.Data.Migration;
using OrchardCore.DisplayManagement.Descriptors;
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Descriptors;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Theming;
using OrchardCore.Environment.Commands;
Expand Down Expand Up @@ -258,6 +258,14 @@ public override void ConfigureServices(IServiceCollection services)

services.AddScoped<CustomUserSettingsService>();
services.AddRecipeExecutionStep<CustomUserSettingsStep>();

// When the value of LoginViewModel.RememberMe is set to anything else than "true" or "false" by the
// client, which is the case with automated cracking attempts, then we'd get a FormatException from the
// default model binder.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new SafeBoolModelBinderProvider());
});
}
}

Expand Down