Skip to content

Commit

Permalink
Handle Default Authentication using events (#16884)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored Oct 24, 2024
1 parent 1fb3287 commit bd93e29
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,37 @@ public LoginFormEventEventHandler(ReCaptchaService reCaptchaService)
_reCaptchaService = reCaptchaService;
}

public Task IsLockedOutAsync(IUser user) => Task.CompletedTask;
public Task IsLockedOutAsync(IUser user)
=> Task.CompletedTask;

public Task LoggedInAsync(IUser user)
{
_reCaptchaService.ThisIsAHuman();

return Task.CompletedTask;
}

public async Task LoggingInAsync(string userName, Action<string, string> reportError)
public Task LoggingInAsync(string userName, Action<string, string> reportError)
{
if (_reCaptchaService.IsThisARobot())
{
await _reCaptchaService.ValidateCaptchaAsync(reportError);
return _reCaptchaService.ValidateCaptchaAsync(reportError);
}

return Task.CompletedTask;
}

public Task LoggingInFailedAsync(string userName)
{
_reCaptchaService.MaybeThisIsARobot();

return Task.CompletedTask;
}

public Task LoggingInFailedAsync(IUser user)
{
_reCaptchaService.MaybeThisIsARobot();

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand All @@ -19,30 +17,25 @@
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;
using OrchardCore.Users.ViewModels;
using YesSql.Services;

namespace OrchardCore.Users.Controllers;

[Authorize]
public sealed class AccountController : AccountBaseController
{
[Obsolete("This property will be removed in v3. Instead use ExternalAuthenticationController.DefaultExternalLoginProtector")]
public const string DefaultExternalLoginProtector = ExternalAuthenticationsController.DefaultExternalLoginProtector;
[Obsolete("This property is no longer used and will be removed in v3.")]
public const string DefaultExternalLoginProtector = "DefaultExternalLogin";

private readonly IUserService _userService;
private readonly SignInManager<IUser> _signInManager;
private readonly UserManager<IUser> _userManager;
private readonly ILogger _logger;
private readonly ISiteService _siteService;
private readonly IEnumerable<ILoginFormEvent> _accountEvents;
private readonly ExternalLoginOptions _externalLoginOptions;
private readonly RegistrationOptions _registrationOptions;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IDisplayManager<LoginForm> _loginFormDisplayManager;
private readonly IUpdateModelAccessor _updateModelAccessor;
private readonly INotifier _notifier;
private readonly IClock _clock;
private readonly IDistributedCache _distributedCache;

internal readonly IHtmlLocalizer H;
internal readonly IStringLocalizer S;
Expand All @@ -57,11 +50,7 @@ public AccountController(
IStringLocalizer<AccountController> stringLocalizer,
IEnumerable<ILoginFormEvent> accountEvents,
IOptions<RegistrationOptions> registrationOptions,
IOptions<ExternalLoginOptions> externalLoginOptions,
INotifier notifier,
IClock clock,
IDistributedCache distributedCache,
IDataProtectionProvider dataProtectionProvider,
IDisplayManager<LoginForm> loginFormDisplayManager,
IUpdateModelAccessor updateModelAccessor)
{
Expand All @@ -71,12 +60,8 @@ public AccountController(
_logger = logger;
_siteService = siteService;
_accountEvents = accountEvents;
_externalLoginOptions = externalLoginOptions.Value;
_registrationOptions = registrationOptions.Value;
_notifier = notifier;
_clock = clock;
_distributedCache = distributedCache;
_dataProtectionProvider = dataProtectionProvider;
_loginFormDisplayManager = loginFormDisplayManager;
_updateModelAccessor = updateModelAccessor;

Expand All @@ -96,23 +81,13 @@ public async Task<IActionResult> Login(string returnUrl = null)
// Clear the existing external cookie to ensure a clean login process.
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

if (_externalLoginOptions.UseExternalProviderIfOnlyOneDefined)
foreach (var handler in _accountEvents)
{
var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
if (schemes.Count() == 1)
{
var dataProtector = _dataProtectionProvider.CreateProtector(ExternalAuthenticationsController.DefaultExternalLoginProtector)
.ToTimeLimitedDataProtector();

var token = Guid.NewGuid();
var expiration = new TimeSpan(0, 0, 5);
var protectedToken = dataProtector.Protect(token.ToString(), _clock.UtcNow.Add(expiration));
await _distributedCache.SetAsync(token.ToString(), token.ToByteArray(), new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = expiration,
});
var result = await handler.LoggingInAsync();

return RedirectToAction(nameof(ExternalAuthenticationsController.DefaultExternalLogin), typeof(ExternalAuthenticationsController).ControllerName(), new { protectedToken, returnUrl });
if (result != null)
{
return result;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ namespace OrchardCore.Users.Controllers;
[Feature(UserConstants.Features.ExternalAuthentication)]
public sealed class ExternalAuthenticationsController : AccountBaseController
{
public const string DefaultExternalLoginProtector = "DefaultExternalLogin";

private readonly SignInManager<IUser> _signInManager;
private readonly UserManager<IUser> _userManager;
private readonly ILogger _logger;
Expand Down Expand Up @@ -75,39 +73,6 @@ public ExternalAuthenticationsController(
S = stringLocalizer;
}

[AllowAnonymous]
public async Task<IActionResult> DefaultExternalLogin(string protectedToken, string returnUrl = null)
{
if (_externalLoginOption.UseExternalProviderIfOnlyOneDefined)
{
var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
if (schemes.Count() == 1)
{
var dataProtector = _dataProtectionProvider.CreateProtector(DefaultExternalLoginProtector)
.ToTimeLimitedDataProtector();

try
{
if (Guid.TryParse(dataProtector.Unprotect(protectedToken), out var token))
{
var tokenBytes = await _distributedCache.GetAsync(token.ToString());
var cacheToken = new Guid(tokenBytes);
if (token.Equals(cacheToken))
{
return ExternalLogin(schemes.First().Name, returnUrl);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while validating {DefaultExternalLogin} token", DefaultExternalLoginProtector);
}
}
}

return RedirectToLogin();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using OrchardCore.Mvc.Core.Utilities;
using OrchardCore.Users.Controllers;
using OrchardCore.Users.Events;
using OrchardCore.Users.Models;

namespace OrchardCore.Users.Services;

public sealed class ExternalLoginFormEvents : ILoginFormEvent
{
private readonly ExternalLoginOptions _externalLoginOptions;
private readonly SignInManager<IUser> _signInManager;
private readonly LinkGenerator _linkGenerator;
private readonly IHttpContextAccessor _httpContextAccessor;

public ExternalLoginFormEvents(
IOptions<ExternalLoginOptions> externalLoginOptions,
SignInManager<IUser> signInManager,
LinkGenerator linkGenerator,
IHttpContextAccessor httpContextAccessor)
{
_externalLoginOptions = externalLoginOptions.Value;
_signInManager = signInManager;
_linkGenerator = linkGenerator;
_httpContextAccessor = httpContextAccessor;
}

public Task IsLockedOutAsync(IUser user)
=> Task.CompletedTask;

public Task LoggedInAsync(IUser user)
=> Task.CompletedTask;

public async Task<IActionResult> LoggingInAsync()
{
if (!_externalLoginOptions.UseExternalProviderIfOnlyOneDefined)
{
return null;
}

var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();

if (schemes.Count() == 1)
{
var provider = schemes.First().Name;

var model = new RouteValueDictionary();

if (_httpContextAccessor.HttpContext.Request.Query.TryGetValue("returnUrl", out var returnUrlValue))
{
model.Add("returnUrl", returnUrlValue);
}

var redirectUrl = _linkGenerator.GetPathByAction(_httpContextAccessor.HttpContext,
action: nameof(ExternalAuthenticationsController.ExternalLoginCallback),
controller: typeof(ExternalAuthenticationsController).ControllerName(),
values: model);

return new ChallengeResult(
authenticationScheme: provider,
properties: _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl));
}

return null;
}

public Task LoggingInAsync(string userName, Action<string, string> reportError)
=> Task.CompletedTask;

public Task LoggingInFailedAsync(string userName)
=> Task.CompletedTask;

public Task LoggingInFailedAsync(IUser user)
=> Task.CompletedTask;
}
2 changes: 2 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Users/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
using OrchardCore.Users.DataMigrations;
using OrchardCore.Users.Deployment;
using OrchardCore.Users.Drivers;
using OrchardCore.Users.Events;
using OrchardCore.Users.Handlers;
using OrchardCore.Users.Indexes;
using OrchardCore.Users.Liquid;
Expand Down Expand Up @@ -250,6 +251,7 @@ public sealed class ExternalAuthenticationStartup : StartupBase

public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ILoginFormEvent, ExternalLoginFormEvents>();
services.AddNavigationProvider<RegistrationAdminMenu>();
services.AddScoped<IDisplayDriver<UserMenu>, ExternalAuthenticationUserMenuDisplayDriver>();
services.AddSiteDisplayDriver<ExternalRegistrationSettingsDisplayDriver>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Mvc;

namespace OrchardCore.Users.Events;

/// <summary>
Expand Down Expand Up @@ -35,4 +37,11 @@ public interface ILoginFormEvent
/// </summary>
/// <param name="user">The <see cref="IUser"/>.</param>
Task LoggedInAsync(IUser user);

/// <summary>
/// Occurs when a user visits the login page.
/// </summary>
/// <returns></returns>
Task<IActionResult> LoggingInAsync()
=> Task.FromResult<IActionResult>(null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ public interface IExternalLoginEventHandler
/// </summary>
/// <param name="context">The <see cref="UpdateUserContext"/>.</param>
Task UpdateUserAsync(UpdateUserContext context);

}

0 comments on commit bd93e29

Please sign in to comment.