diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 3c0057bcb5..870ed9452c 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Models.Identity; namespace Umbraco.Core.Security { + //TODO: In v8 we need to change this to use an int? nullable TKey instead, see notes against overridden TwoFactorSignInAsync public class BackOfficeSignInManager : SignInManager { private readonly ILogger _logger; @@ -250,5 +251,67 @@ namespace Umbraco.Core.Security } return null; } + + /// + /// Two factor verification step + /// + /// + /// + /// + /// + /// + /// + /// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it + /// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that + /// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate + /// all of this code to check for -1 instead. + /// + public override async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser) + { + var userId = await GetVerifiedUserIdAsync(); + if (userId == -1) + { + return SignInStatus.Failure; + } + var user = await UserManager.FindByIdAsync(userId); + if (user == null) + { + return SignInStatus.Failure; + } + if (await UserManager.IsLockedOutAsync(user.Id)) + { + return SignInStatus.LockedOut; + } + if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code)) + { + // When token is verified correctly, clear the access failed count used for lockout + await UserManager.ResetAccessFailedCountAsync(user.Id); + await SignInAsync(user, isPersistent, rememberBrowser); + return SignInStatus.Success; + } + // If the token is incorrect, record the failure which also may cause the user to be locked out + await UserManager.AccessFailedAsync(user.Id); + return SignInStatus.Failure; + } + + /// Send a two factor code to a user + /// + /// + /// + /// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it + /// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that + /// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate + /// all of this code to check for -1 instead. + /// + public override async Task SendTwoFactorCodeAsync(string provider) + { + var userId = await GetVerifiedUserIdAsync(); + if (userId == -1) + return false; + + var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider); + var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token); + return identityResult.Succeeded; + } } } \ No newline at end of file