Revert "Moves auto linking logic to the BackOfficeSignInManager"

Signed-off-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Bjarke Berg
2020-11-27 13:17:13 +01:00
parent 7d61020214
commit efce67fe8a
5 changed files with 168 additions and 253 deletions

View File

@@ -10,9 +10,7 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration.Models;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Web.Common.Security
{
@@ -29,25 +27,18 @@ namespace Umbraco.Web.Common.Security
private const string XsrfKey = "XsrfId";
private BackOfficeUserManager _userManager;
private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly GlobalSettings _globalSettings;
public BackOfficeSignInManager(
BackOfficeUserManager userManager,
IHttpContextAccessor contextAccessor,
IBackOfficeExternalLoginProviders externalLogins,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
IOptions<GlobalSettings> globalSettings,
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<BackOfficeIdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_userManager = userManager;
_externalLogins = externalLogins;
_globalSettings = globalSettings.Value;
}
// TODO: Need to migrate more from Umbraco.Web.Security.BackOfficeSignInManager
@@ -309,51 +300,7 @@ namespace Umbraco.Web.Common.Security
};
}
/// <summary>
/// Custom ExternalLoginSignInAsync overload for handling external sign in with auto-linking
/// </summary>
/// <param name="loginProvider"></param>
/// <param name="providerKey"></param>
/// <param name="isPersistent"></param>
/// <param name="bypassTwoFactor"></param>
/// <returns></returns>
public async Task<SignInResult> ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false)
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// to be able to deal with auto-linking and reduce duplicate lookups
var autoLinkOptions = _externalLogins.Get(loginInfo.LoginProvider)?.Options?.AutoLinkOptions;
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user == null)
{
// user doesn't exist so see if we can auto link
return await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions);
}
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
{
var shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo);
if (shouldSignIn == false)
{
Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id);
}
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginInfo.LoginProvider, bypassTwoFactor);
}
public override Task<IEnumerable<AuthenticationScheme>> GetExternalAuthenticationSchemesAsync()
{
// TODO: We can filter these so that they only include the back office ones.
// That can be done by either checking the scheme (maybe) or comparing it to what we have registered in the collection of BackOfficeExternalLoginProvider
return base.GetExternalAuthenticationSchemesAsync();
}
/// <inheritdoc />
protected override async Task<SignInResult> SignInOrTwoFactorAsync(BackOfficeIdentityUser user, bool isPersistent, string loginProvider = null, bool bypassTwoFactor = false)
{
@@ -524,117 +471,5 @@ namespace Umbraco.Web.Common.Security
public string UserId { get; set; }
public string LoginProvider { get; set; }
}
/// <summary>
/// Used for auto linking/creating user accounts for external logins
/// </summary>
/// <param name="loginInfo"></param>
/// <param name="autoLinkOptions"></param>
/// <returns></returns>
private async Task<SignInResult> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
{
// If there are no autolink options then the attempt is failed (user does not exist)
if (autoLinkOptions == null || !autoLinkOptions.AutoLinkExternalAccount)
{
return SignInResult.Failed;
}
var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
//we are allowing auto-linking/creating of local accounts
if (email.IsNullOrWhiteSpace())
{
return AutoLinkSignInResult.FailedNoEmail;
}
else
{
//Now we need to perform the auto-link, so first we need to lookup/create a user with the email address
var autoLinkUser = await UserManager.FindByEmailAsync(email);
if (autoLinkUser != null)
{
try
{
//call the callback if one is assigned
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
return AutoLinkSignInResult.FailedException(ex.Message);
}
return await LinkUser(autoLinkUser, loginInfo);
}
else
{
var name = loginInfo.Principal?.Identity?.Name;
if (name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null");
autoLinkUser = BackOfficeIdentityUser.CreateNew(_globalSettings, email, email, autoLinkOptions.GetUserAutoLinkCulture(_globalSettings), name);
foreach (var userGroup in autoLinkOptions.DefaultUserGroups)
{
autoLinkUser.AddRole(userGroup);
}
//call the callback if one is assigned
try
{
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
return AutoLinkSignInResult.FailedException(ex.Message);
}
var userCreationResult = await _userManager.CreateAsync(autoLinkUser);
if (!userCreationResult.Succeeded)
{
return AutoLinkSignInResult.FailedCreatingUser(userCreationResult.Errors.Select(x => x.Description).ToList());
}
else
{
return await LinkUser(autoLinkUser, loginInfo);
}
}
}
}
private async Task<SignInResult> LinkUser(BackOfficeIdentityUser autoLinkUser, ExternalLoginInfo loginInfo)
{
var existingLogins = await _userManager.GetLoginsAsync(autoLinkUser);
var exists = existingLogins.FirstOrDefault(x => x.LoginProvider == loginInfo.LoginProvider && x.ProviderKey == loginInfo.ProviderKey);
// if it already exists (perhaps it was added in the AutoLink callbak) then we just continue
if (exists != null)
{
//sign in
return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider);
}
var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
if (linkResult.Succeeded)
{
//we're good! sign in
return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider);
}
//If this fails, we should really delete the user since it will be in an inconsistent state!
var deleteResult = await _userManager.DeleteAsync(autoLinkUser);
if (deleteResult.Succeeded)
{
var errors = linkResult.Errors.Select(x => x.Description).ToList();
return AutoLinkSignInResult.FailedLinkingUser(errors);
}
else
{
//DOH! ... this isn't good, combine all errors to be shown
var errors = linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList();
return AutoLinkSignInResult.FailedLinkingUser(errors);
}
}
}
}