Introduced interface on BackOfficeUserManager (#8913)

* Introduced IBackOfficeUserManager

* Fixed test

* Moved class into own file

Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
This commit is contained in:
Bjarke Berg
2020-09-22 14:44:41 +02:00
committed by GitHub
parent e6fba11279
commit 0c908a7bbb
15 changed files with 316 additions and 25 deletions

View File

@@ -14,7 +14,7 @@ using Umbraco.Net;
namespace Umbraco.Core.BackOffice
{
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>, IBackOfficeUserManager
{
public BackOfficeUserManager(
IIpResolver ipResolver,

View File

@@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Core.BackOffice
{
public interface IBackOfficeUserManager : IBackOfficeUserManager<BackOfficeIdentityUser>
{
}
public interface IBackOfficeUserManager<TUser>: IDisposable
where TUser : BackOfficeIdentityUser
{
/// <summary>
/// Finds and returns a user, if any, who has the specified <paramref name="userId"/>.
/// </summary>
/// <param name="userId">The user ID to search for.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the user matching the specified <paramref name="userId"/> if it exists.
/// </returns>
Task<TUser> FindByIdAsync(string userId);
/// <summary>
/// Generates a password reset token for the specified <paramref name="user"/>, using
/// the configured password reset token provider.
/// </summary>
/// <param name="user">The user to generate a password reset token for.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation,
/// containing a password reset token for the specified <paramref name="user"/>.</returns>
Task<string> GeneratePasswordResetTokenAsync(TUser user);
/// <summary>
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// We use this because in the back office the only way an admin can change another user's password without first knowing their password
/// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset
/// </remarks>
Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword);
/// <summary>
/// Validates that an email confirmation token matches the specified <paramref name="user"/>.
/// </summary>
/// <param name="user">The user to validate the token against.</param>
/// <param name="token">The email confirmation token to validate.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> ConfirmEmailAsync(TUser user, string token);
/// <summary>
/// Gets the user, if any, associated with the normalized value of the specified email address.
/// Note: Its recommended that identityOptions.User.RequireUniqueEmail be set to true when using this method, otherwise
/// the store may throw if there are users with duplicate emails.
/// </summary>
/// <param name="email">The email address to return the user for.</param>
/// <returns>
/// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a normalized value of the specified email address.
/// </returns>
Task<TUser> FindByEmailAsync(string email);
/// <summary>
/// Resets the <paramref name="user"/>'s password to the specified <paramref name="newPassword"/> after
/// validating the given password reset <paramref name="token"/>.
/// </summary>
/// <param name="user">The user whose password should be reset.</param>
/// <param name="token">The password reset token to verify.</param>
/// <param name="newPassword">The new password to set if reset token verification succeeds.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> ResetPasswordAsync(TUser user, string token, string newPassword);
/// <summary>
/// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
/// <remarks>
/// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values
/// </remarks>
Task<bool> IsLockedOutAsync(TUser user);
/// <summary>
/// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user.
/// </summary>
/// <param name="user">The user whose lockout date should be set.</param>
/// <param name="lockoutEnd">The <see cref="DateTimeOffset"/> after which the <paramref name="user"/>'s lockout should end.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
Task<IdentityResult> SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd);
/// <summary>
/// Gets a flag indicating whether the email address for the specified <paramref name="user"/> has been verified, true if the email address is verified otherwise
/// false.
/// </summary>
/// <param name="user">The user whose email confirmation status should be returned.</param>
/// <returns>
/// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified <paramref name="user"/>
/// has been confirmed or not.
/// </returns>
Task<bool> IsEmailConfirmedAsync(TUser user);
/// <summary>
/// Updates the specified <paramref name="user"/> in the backing store.
/// </summary>
/// <param name="user">The user to update.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> UpdateAsync(TUser user);
/// <summary>
/// Returns a flag indicating whether the specified <paramref name="token"/> is valid for
/// the given <paramref name="user"/> and <paramref name="purpose"/>.
/// </summary>
/// <param name="user">The user to validate the token against.</param>
/// <param name="tokenProvider">The token provider used to generate the token.</param>
/// <param name="purpose">The purpose the token should be generated for.</param>
/// <param name="token">The token to validate</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, returning true if the <paramref name="token"/>
/// is valid, otherwise false.
/// </returns>
Task<bool> VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose,
string token);
/// <summary>
/// Adds the <paramref name="password"/> to the specified <paramref name="user"/> only if the user
/// does not already have a password.
/// </summary>
/// <param name="user">The user whose password should be set.</param>
/// <param name="password">The password to set.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> AddPasswordAsync(TUser user, string password);
/// <summary>
/// Returns a flag indicating whether the given <paramref name="password"/> is valid for the
/// specified <paramref name="user"/>.
/// </summary>
/// <param name="user">The user whose password should be validated.</param>
/// <param name="password">The password to validate</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing true if
/// the specified <paramref name="password" /> matches the one store for the <paramref name="user"/>,
/// otherwise false.</returns>
Task<bool> CheckPasswordAsync(TUser user, string password);
/// <summary>
/// Changes a user's password after confirming the specified <paramref name="currentPassword"/> is correct,
/// as an asynchronous operation.
/// </summary>
/// <param name="user">The user whose password should be set.</param>
/// <param name="currentPassword">The current password to validate before changing.</param>
/// <param name="newPassword">The new password to set for the specified <paramref name="user"/>.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> ChangePasswordAsync(TUser user, string currentPassword,
string newPassword);
/// <summary>
/// Used to validate a user's session
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
Task<bool> ValidateSessionIdAsync(string userId, string sessionId);
/// <summary>
/// Creates the specified <paramref name="user"/> in the backing store with no password,
/// as an asynchronous operation.
/// </summary>
/// <param name="user">The user to create.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
Task<IdentityResult> CreateAsync(TUser user);
/// <summary>
/// Helper method to generate a password for a user based on the current password validator
/// </summary>
/// <returns></returns>
string GeneratePassword();
/// <summary>
/// Generates an email confirmation token for the specified user.
/// </summary>
/// <param name="user">The user to generate an email confirmation token for.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, an email confirmation token.
/// </returns>
Task<string> GenerateEmailConfirmationTokenAsync(TUser user);
/// <summary>
/// Finds and returns a user, if any, who has the specified user name.
/// </summary>
/// <param name="userName">The user name to search for.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, containing the user matching the specified <paramref name="userName"/> if it exists.
/// </returns>
Task<TUser> FindByNameAsync(string userName);
/// <summary>
/// Increments the access failed count for the user as an asynchronous operation.
/// If the failed access account is greater than or equal to the configured maximum number of attempts,
/// the user will be locked out for the configured lockout time span.
/// </summary>
/// <param name="user">The user whose failed access count to increment.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
Task<IdentityResult> AccessFailedAsync(TUser user);
/// <summary>
/// Returns a flag indicating whether the specified <paramref name="user"/> has two factor authentication enabled or not,
/// as an asynchronous operation.
/// </summary>
/// <param name="user">The user whose two factor authentication enabled status should be retrieved.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation, true if the specified <paramref name="user "/>
/// has two factor authentication enabled, otherwise false.
/// </returns>
Task<bool> GetTwoFactorEnabledAsync(TUser user);
/// <summary>
/// Gets a list of valid two factor token providers for the specified <paramref name="user"/>,
/// as an asynchronous operation.
/// </summary>
/// <param name="user">The user the whose two factor authentication providers will be returned.</param>
/// <returns>
/// The <see cref="Task"/> that represents result of the asynchronous operation, a list of two
/// factor authentication providers for the specified user.
/// </returns>
Task<IList<string>> GetValidTwoFactorProvidersAsync(TUser user);
/// <summary>
/// Verifies the specified two factor authentication <paramref name="token" /> against the <paramref name="user"/>.
/// </summary>
/// <param name="user">The user the token is supposed to be for.</param>
/// <param name="tokenProvider">The provider which will verify the token.</param>
/// <param name="token">The token to verify.</param>
/// <returns>
/// The <see cref="Task"/> that represents result of the asynchronous operation, true if the token is valid,
/// otherwise false.
/// </returns>
Task<bool> VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token);
Task<IdentityResult> ResetAccessFailedCountAsync(TUser user);
void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId);
void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId);
void RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId);
void RaiseLoginSuccessEvent(TUser currentUser, int userId);
}
}

View File

@@ -34,7 +34,7 @@ namespace Umbraco.Web.Install.InstallSteps
private readonly SecuritySettings _securitySettings;
private readonly ConnectionStrings _connectionStrings;
private readonly ICookieManager _cookieManager;
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeUserManager _userManager;
public NewInstallStep(
IUserService userService,
@@ -43,7 +43,7 @@ namespace Umbraco.Web.Install.InstallSteps
IOptions<SecuritySettings> securitySettings,
IOptions<ConnectionStrings> connectionStrings,
ICookieManager cookieManager,
BackOfficeUserManager userManager)
IBackOfficeUserManager userManager)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions
[Test]
public void AddUmbracoBackOfficeIdentity_ExpectBackOfficeUserManagerResolvable()
{
var userManager = Services.GetService<BackOfficeUserManager>();
var userManager = Services.GetService<IBackOfficeUserManager>();
Assert.NotNull(userManager);
}

View File

@@ -15,14 +15,14 @@ namespace Umbraco.Tests.Web.Controllers
{
[Test,AutoMoqData]
public void PostUnlockUsers_When_User_Lockout_Update_Fails_Expect_Failure_Response(
[Frozen] IUserStore<BackOfficeIdentityUser> userStore,
[Frozen] IBackOfficeUserManager backOfficeUserManager,
UsersController sut,
BackOfficeIdentityUser user,
int[] userIds,
string expectedMessage)
{
Mock.Get(userStore)
.Setup(x => x.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
Mock.Get(backOfficeUserManager)
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
.ReturnsAsync(user);
Assert.ThrowsAsync<HttpResponseException>(() => sut.PostUnlockUsers(userIds));

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Web.BackOffice.Controllers
public class AuthenticationController : UmbracoApiControllerBase
{
private readonly IBackofficeSecurityAccessor _backofficeSecurityAccessor;
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeUserManager _userManager;
private readonly BackOfficeSignInManager _signInManager;
private readonly IUserService _userService;
private readonly ILocalizedTextService _textService;
@@ -60,7 +60,7 @@ namespace Umbraco.Web.BackOffice.Controllers
public AuthenticationController(
IBackofficeSecurityAccessor backofficeSecurityAccessor,
BackOfficeUserManager backOfficeUserManager,
IBackOfficeUserManager backOfficeUserManager,
BackOfficeSignInManager signInManager,
IUserService userService,
ILocalizedTextService textService,

View File

@@ -36,11 +36,10 @@ namespace Umbraco.Web.BackOffice.Controllers
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
public class BackOfficeController : Controller
{
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeUserManager _userManager;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _textService;
private readonly IGridConfig _gridConfig;
private readonly BackOfficeServerVariables _backOfficeServerVariables;
@@ -50,11 +49,10 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly ILogger _logger;
public BackOfficeController(
BackOfficeUserManager userManager,
IBackOfficeUserManager userManager,
IRuntimeMinifier runtimeMinifier,
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContextAccessor,
ILocalizedTextService textService,
IGridConfig gridConfig,
BackOfficeServerVariables backOfficeServerVariables,
@@ -67,7 +65,6 @@ namespace Umbraco.Web.BackOffice.Controllers
_runtimeMinifier = runtimeMinifier;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
_umbracoContextAccessor = umbracoContextAccessor;
_textService = textService;
_gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig));
_backOfficeServerVariables = backOfficeServerVariables;

View File

@@ -43,7 +43,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IBackofficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IUserService _userService;
private readonly UmbracoMapper _umbracoMapper;
private readonly BackOfficeUserManager _backOfficeUserManager;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly ILogger _logger;
private readonly ILocalizedTextService _localizedTextService;
private readonly AppCaches _appCaches;
@@ -57,7 +57,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IBackofficeSecurityAccessor backofficeSecurityAccessor,
IUserService userService,
UmbracoMapper umbracoMapper,
BackOfficeUserManager backOfficeUserManager,
IBackOfficeUserManager backOfficeUserManager,
ILogger logger,
ILocalizedTextService localizedTextService,
AppCaches appCaches,

View File

@@ -72,7 +72,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IMediaService _mediaService;
private readonly IContentService _contentService;
private readonly GlobalSettings _globalSettings;
private readonly BackOfficeUserManager _backOfficeUserManager;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly ILogger _logger;
private readonly LinkGenerator _linkGenerator;
@@ -95,7 +95,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IMediaService mediaService,
IContentService contentService,
IOptions<GlobalSettings> globalSettings,
BackOfficeUserManager backOfficeUserManager,
IBackOfficeUserManager backOfficeUserManager,
ILogger logger,
LinkGenerator linkGenerator)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
@@ -57,7 +58,7 @@ namespace Umbraco.Extensions
services.BuildUmbracoBackOfficeIdentity()
.AddDefaultTokenProviders()
.AddUserStore<BackOfficeUserStore>()
.AddUserManager<BackOfficeUserManager>()
.AddUserManager<IBackOfficeUserManager, BackOfficeUserManager>()
.AddSignInManager<BackOfficeSignInManager>()
.AddClaimsPrincipalFactory<BackOfficeClaimsPrincipalFactory<BackOfficeIdentityUser>>();

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.BackOffice;
namespace Umbraco.Extensions
{
public static class IdentityBuilderExtensions
{
/// <summary>
/// Adds a <see cref="UserManager{TUser}"/> for the <seealso cref="UserType"/>.
/// </summary>
/// <typeparam name="TUserManager">The type of the user manager to add.</typeparam>
/// <typeparam name="TInterface"></typeparam>
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder) where TUserManager : UserManager<BackOfficeIdentityUser>, TInterface
{
identityBuilder.AddUserManager<TUserManager>();
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
return identityBuilder;
}
}
}

View File

@@ -37,9 +37,9 @@ namespace Umbraco.Web.BackOffice.Security
private readonly ISystemClock _systemClock;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeUserManager _userManager;
public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment, BackOfficeUserManager userManager)
public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment, IBackOfficeUserManager userManager)
{
_systemClock = systemClock;
_globalSettings = globalSettings.Value;

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Web.BackOffice.Security
IUser currentUser,
IUser savingUser,
ChangingPasswordModel passwordModel,
BackOfficeUserManager userMgr)
IBackOfficeUserManager userMgr)
{
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
if (userMgr == null) throw new ArgumentNullException(nameof(userMgr));

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Security
public class BackOfficeSignInManager : SignInManager<BackOfficeIdentityUser>
{
private readonly BackOfficeUserManager _userManager;
private readonly IBackOfficeUserManager _userManager;
public BackOfficeSignInManager(
BackOfficeUserManager userManager,

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Web.Security
/// </summary>
public class BackOfficeSignInManager : IDisposable
{
private readonly BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
private readonly IBackOfficeUserManager _userManager;
private readonly IUserClaimsPrincipalFactory<BackOfficeIdentityUser> _claimsPrincipalFactory;
private readonly IAuthenticationManager _authenticationManager;
private readonly ILogger _logger;
@@ -28,7 +28,7 @@ namespace Umbraco.Web.Security
private readonly IOwinRequest _request;
public BackOfficeSignInManager(
BackOfficeUserManager<BackOfficeIdentityUser> userManager,
IBackOfficeUserManager userManager,
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsPrincipalFactory,
IAuthenticationManager authenticationManager,
ILogger logger,