Splits user manager into a base class that can be reused changes base class of IdentityUser to UmbracoIdentityUser
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
<Rule Id="SA1311" Action="None" />
|
||||
<Rule Id="SA1402" Action="None" />
|
||||
<Rule Id="SA1413" Action="None" />
|
||||
<Rule Id="SA1611" Action="None" />
|
||||
<Rule Id="SA1615" Action="None" />
|
||||
<Rule Id="SA1629" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Identity
|
||||
/// to a user. We will continue using this approach since it works fine for what we need which does the change tracking of
|
||||
/// claims, roles and logins directly on the user model.
|
||||
/// </remarks>
|
||||
public abstract class IdentityUser : IRememberBeingDirty
|
||||
public abstract class UmbracoIdentityUser : IRememberBeingDirty
|
||||
{
|
||||
private int _id;
|
||||
private string _email;
|
||||
@@ -32,9 +32,9 @@ namespace Umbraco.Core.Models.Identity
|
||||
private ObservableCollection<IdentityUserRole> _roles;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IdentityUser"/> class.
|
||||
/// Initializes a new instance of the <see cref="UmbracoIdentityUser"/> class.
|
||||
/// </summary>
|
||||
public IdentityUser()
|
||||
public UmbracoIdentityUser()
|
||||
{
|
||||
// must initialize before setting groups
|
||||
_roles = new ObservableCollection<IdentityUserRole>();
|
||||
@@ -1,10 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
@@ -12,7 +8,10 @@ using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeIdentityUser : IdentityUser
|
||||
/// <summary>
|
||||
/// The identity user used for the back office
|
||||
/// </summary>
|
||||
public class BackOfficeIdentityUser : UmbracoIdentityUser
|
||||
{
|
||||
private string _name;
|
||||
private string _passwordConfig;
|
||||
@@ -34,11 +33,7 @@ namespace Umbraco.Core.Security
|
||||
/// <summary>
|
||||
/// Used to construct a new instance without an identity
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="email">This is allowed to be null (but would need to be filled in if trying to persist this instance)</param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string username, string email, string culture, string name = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
@@ -80,9 +75,6 @@ namespace Umbraco.Core.Security
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeIdentityUser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="groups"></param>
|
||||
public BackOfficeIdentityUser(GlobalSettings globalSettings, int userId, IEnumerable<IReadOnlyUserGroup> groups)
|
||||
: this(globalSettings, groups.ToArray())
|
||||
{
|
||||
@@ -184,7 +176,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Based on the user's lockout end date, this will determine if they are locked out
|
||||
/// Gets a value indicating whether the user is locked out based on the user's lockout end date
|
||||
/// </summary>
|
||||
public bool IsLockedOut
|
||||
{
|
||||
@@ -196,7 +188,7 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a 1:1 mapping with IUser.IsApproved
|
||||
/// Gets or sets a value indicating the IUser IsApproved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -18,6 +17,8 @@ using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.BackOffice
|
||||
{
|
||||
// TODO: Make this into a base class that can be re-used
|
||||
|
||||
public class BackOfficeUserStore : DisposableObjectSlim,
|
||||
IUserPasswordStore<BackOfficeIdentityUser>,
|
||||
IUserEmailStore<BackOfficeIdentityUser>,
|
||||
@@ -28,11 +29,11 @@ namespace Umbraco.Core.BackOffice
|
||||
IUserSessionStore<BackOfficeIdentityUser>
|
||||
|
||||
// TODO: This would require additional columns/tables and then a lot of extra coding support to make this happen natively within umbraco
|
||||
//IUserTwoFactorStore<BackOfficeIdentityUser>,
|
||||
// IUserTwoFactorStore<BackOfficeIdentityUser>,
|
||||
// TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
// IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
// TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
// IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
{
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IUserService _userService;
|
||||
@@ -42,15 +43,16 @@ namespace Umbraco.Core.BackOffice
|
||||
private readonly UmbracoMapper _mapper;
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeUserStore"/> class.
|
||||
/// </summary>
|
||||
public BackOfficeUserStore(IScopeProvider scopeProvider, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IOptions<GlobalSettings> globalSettings, UmbracoMapper mapper)
|
||||
{
|
||||
_scopeProvider = scopeProvider;
|
||||
_userService = userService;
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_entityService = entityService;
|
||||
_externalLoginService = externalLoginService;
|
||||
_externalLoginService = externalLoginService ?? throw new ArgumentNullException(nameof(externalLoginService));
|
||||
_globalSettings = globalSettings.Value;
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
|
||||
_mapper = mapper;
|
||||
_userService = userService;
|
||||
_externalLoginService = externalLoginService;
|
||||
@@ -59,10 +61,7 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
_disposed = true;
|
||||
}
|
||||
protected override void DisposeResources() => _disposed = true;
|
||||
|
||||
public Task<string> GetUserIdAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -105,9 +104,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Insert a new user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<IdentityResult> CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -158,9 +154,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Update a user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<IdentityResult> UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -206,8 +199,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Delete a user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<IdentityResult> DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -227,9 +218,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Finds a user
|
||||
/// </summary>
|
||||
/// <param name="userId"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public async Task<BackOfficeIdentityUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -244,9 +232,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Find a user by name
|
||||
/// </summary>
|
||||
/// <param name="userName"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public async Task<BackOfficeIdentityUser> FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -265,9 +250,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Set the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="passwordHash"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -278,6 +260,7 @@ namespace Umbraco.Core.BackOffice
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
user.PasswordConfig = null; // Clear this so that it's reset at the repository level
|
||||
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -285,9 +268,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Get the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<string> GetPasswordHashAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -300,9 +280,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns true if a user has a password set
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<bool> HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -315,9 +292,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Set the user email
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="email"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetEmailAsync(BackOfficeIdentityUser user, string email, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -333,9 +307,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Get the user email
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<string> GetEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -348,9 +319,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns true if the user email is confirmed
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<bool> GetEmailConfirmedAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -363,9 +331,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Sets whether the user email is confirmed
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="confirmed"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -377,9 +342,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns the user associated with this email
|
||||
/// </summary>
|
||||
/// <param name="email"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<BackOfficeIdentityUser> FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -393,22 +355,14 @@ namespace Umbraco.Core.BackOffice
|
||||
}
|
||||
|
||||
public Task<string> GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetEmailAsync(user, cancellationToken);
|
||||
}
|
||||
=> GetEmailAsync(user, cancellationToken);
|
||||
|
||||
public Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken)
|
||||
{
|
||||
return SetEmailAsync(user, normalizedEmail, cancellationToken);
|
||||
}
|
||||
=> SetEmailAsync(user, normalizedEmail, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user login with the specified provider and key
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="login"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -427,11 +381,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Removes the user login with the specified combination if it exists
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="providerKey"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <param name="loginProvider"></param>
|
||||
/// <returns/>
|
||||
public Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -447,9 +396,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns the linked accounts for this user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<IList<UserLoginInfo>> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -498,10 +444,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Adds a user to a role (user group)
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="normalizedRoleName"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -523,10 +465,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Removes the role (user group) for the user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="normalizedRoleName"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -548,9 +486,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns the roles (user groups) for this user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<IList<string>> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -562,10 +497,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns true if a user is in the role
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="normalizedRoleName"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<bool> IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -597,10 +528,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Set the security stamp for the user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="stamp"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -614,9 +541,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Get the user security stamp
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<string> GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -644,10 +568,7 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out.
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
/// <remarks>
|
||||
/// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status
|
||||
/// </remarks>
|
||||
public Task<DateTimeOffset?> GetLockoutEndDateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
@@ -664,9 +585,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Locks a user out until the specified end date (set to a past date, to unlock a user)
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="lockoutEnd"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
/// <remarks>
|
||||
/// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status
|
||||
/// </remarks>
|
||||
@@ -683,9 +601,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Used to record when an attempt to access the user has failed
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<int> IncrementAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -699,9 +614,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Used to reset the access failed count, typically after the account is successfully accessed
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -716,9 +628,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// Returns the current number of failed access attempts. This number usually will be reset whenever the password is
|
||||
/// verified or the account is locked out.
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<int> GetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -730,9 +639,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Returns true
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task<bool> GetLockoutEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -744,9 +650,6 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// Doesn't actually perform any function, users can always be locked out
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="enabled"/>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns/>
|
||||
public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -904,8 +807,7 @@ namespace Umbraco.Core.BackOffice
|
||||
|
||||
public Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
|
||||
{
|
||||
Guid guidSessionId;
|
||||
if (Guid.TryParse(sessionId, out guidSessionId))
|
||||
if (Guid.TryParse(sessionId, out Guid guidSessionId))
|
||||
{
|
||||
return Task.FromResult(_userService.ValidateLoginSession(UserIdToInt(userId), guidSessionId));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
@@ -20,9 +21,10 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
|
||||
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>, IBackOfficeUserManager
|
||||
public class BackOfficeUserManager : UmbracoUserManager<BackOfficeIdentityUser>, IBackOfficeUserManager
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public BackOfficeUserManager(
|
||||
IIpResolver ipResolver,
|
||||
IUserStore<BackOfficeIdentityUser> store,
|
||||
@@ -36,135 +38,11 @@ namespace Umbraco.Web.Common.Security
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILogger<UserManager<BackOfficeIdentityUser>> logger,
|
||||
IOptions<UserPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, httpContextAccessor, logger, passwordConfiguration)
|
||||
: base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class BackOfficeUserManager<T> : UserManager<T>
|
||||
where T : BackOfficeIdentityUser
|
||||
{
|
||||
private PasswordGenerator _passwordGenerator;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public BackOfficeUserManager(
|
||||
IIpResolver ipResolver,
|
||||
IUserStore<T> store,
|
||||
IOptions<BackOfficeIdentityOptions> optionsAccessor,
|
||||
IPasswordHasher<T> passwordHasher,
|
||||
IEnumerable<IUserValidator<T>> userValidators,
|
||||
IEnumerable<IPasswordValidator<T>> passwordValidators,
|
||||
BackOfficeLookupNormalizer keyNormalizer,
|
||||
BackOfficeIdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILogger<UserManager<T>> logger,
|
||||
IOptions<UserPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver));
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
|
||||
|
||||
}
|
||||
|
||||
// We don't support an IUserClaimStore and don't need to (at least currently)
|
||||
public override bool SupportsUserClaim => false;
|
||||
|
||||
// It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
|
||||
public override bool SupportsQueryableUsers => false;
|
||||
|
||||
/// <summary>
|
||||
/// Developers will need to override this to support custom 2 factor auth
|
||||
/// </summary>
|
||||
public override bool SupportsUserTwoFactor => false;
|
||||
|
||||
// We haven't needed to support this yet, though might be necessary for 2FA
|
||||
public override bool SupportsUserPhoneNumber => false;
|
||||
|
||||
/// <summary>
|
||||
/// Replace the underlying options property with our own strongly typed version
|
||||
/// </summary>
|
||||
public new BackOfficeIdentityOptions Options
|
||||
{
|
||||
get => (BackOfficeIdentityOptions)base.Options;
|
||||
set => base.Options = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate a user's session
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id</param>
|
||||
/// <param name="sessionId">The sesion id</param>
|
||||
/// <returns>True if the sesion is valid, else false</returns>
|
||||
public virtual async Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
|
||||
{
|
||||
var userSessionStore = Store as IUserSessionStore<T>;
|
||||
|
||||
// if this is not set, for backwards compat (which would be super rare), we'll just approve it
|
||||
if (userSessionStore == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will determine which password hasher to use based on what is defined in config
|
||||
/// </summary>
|
||||
/// <param name="passwordConfiguration">The <see cref="IPasswordConfiguration"/></param>
|
||||
/// <returns>An <see cref="IPasswordHasher{T}"/></returns>
|
||||
protected virtual IPasswordHasher<T> GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the default back office user password checker
|
||||
/// </summary>
|
||||
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
|
||||
public IPasswordConfiguration PasswordConfiguration { get; protected set; }
|
||||
public IIpResolver IpResolver { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a user based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns>The generated password</returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
if (_passwordGenerator == null)
|
||||
{
|
||||
_passwordGenerator = new PasswordGenerator(PasswordConfiguration);
|
||||
}
|
||||
|
||||
var password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
|
||||
/// <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">The user</param>
|
||||
/// <returns>True if the user is locked out, else false</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>
|
||||
public override async Task<bool> IsLockedOutAsync(T user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (user.IsApproved == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await base.IsLockedOutAsync(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic used to validate a username and password
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// By default this uses the standard ASP.Net Identity approach which is:
|
||||
@@ -180,7 +58,7 @@ namespace Umbraco.Web.Common.Security
|
||||
/// We've allowed this check to be overridden with a simple callback so that developers don't actually
|
||||
/// have to implement/override this class.
|
||||
/// </remarks>
|
||||
public override async Task<bool> CheckPasswordAsync(T user, string password)
|
||||
public override async Task<bool> CheckPasswordAsync(BackOfficeIdentityUser user, string password)
|
||||
{
|
||||
if (BackOfficeUserPasswordChecker != null)
|
||||
{
|
||||
@@ -198,36 +76,49 @@ namespace Umbraco.Web.Common.Security
|
||||
}
|
||||
}
|
||||
|
||||
// we cannot proceed if the user passed in does not have an identity
|
||||
if (user.HasIdentity == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// use the default behavior
|
||||
return await base.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
|
||||
/// 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="userId">The userId</param>
|
||||
/// <param name="token">The reset password token</param>
|
||||
/// <param name="newPassword">The new password to set it to</param>
|
||||
/// <returns>The <see cref="IdentityResult"/></returns>
|
||||
/// <param name="user">The user</param>
|
||||
/// <returns>True if the user is locked out, else false</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
|
||||
/// 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>
|
||||
public async Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword)
|
||||
public override async Task<bool> IsLockedOutAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
T user = await FindByIdAsync(userId.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find user");
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword);
|
||||
if (user.IsApproved == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await base.IsLockedOutAsync(user);
|
||||
}
|
||||
|
||||
public override async Task<IdentityResult> AccessFailedAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
IdentityResult result = await base.AccessFailedAsync(user);
|
||||
|
||||
// Slightly confusing: this will return a Success if we successfully update the AccessFailed count
|
||||
if (result.Succeeded)
|
||||
{
|
||||
RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword)
|
||||
{
|
||||
IdentityResult result = await base.ChangePasswordWithResetAsync(userId, token, newPassword);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId);
|
||||
@@ -236,79 +127,19 @@ namespace Umbraco.Web.Common.Security
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async Task<IdentityResult> ChangePasswordAsync(T user, string currentPassword, string newPassword)
|
||||
public override async Task<IdentityResult> ChangePasswordAsync(BackOfficeIdentityUser user, string currentPassword, string newPassword)
|
||||
{
|
||||
IdentityResult result = await base.ChangePasswordAsync(user, currentPassword, newPassword);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to determine how to hash the password
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<IdentityResult> UpdatePasswordHash(T user, string newPassword, bool validatePassword)
|
||||
{
|
||||
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
|
||||
|
||||
if (validatePassword)
|
||||
{
|
||||
IdentityResult validate = await ValidatePasswordAsync(user, newPassword);
|
||||
if (!validate.Succeeded)
|
||||
{
|
||||
return validate;
|
||||
}
|
||||
}
|
||||
|
||||
var passwordStore = Store as IUserPasswordStore<T>;
|
||||
if (passwordStore == null)
|
||||
{
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>));
|
||||
}
|
||||
|
||||
var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
|
||||
await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken);
|
||||
await UpdateSecurityStampInternal(user);
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
private async Task UpdateSecurityStampInternal(T user)
|
||||
{
|
||||
if (SupportsUserSecurityStamp == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
private IUserSecurityStampStore<T> GetSecurityStore()
|
||||
{
|
||||
var store = Store as IUserSecurityStampStore<T>;
|
||||
if (store == null)
|
||||
{
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>));
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
private static string NewSecurityStamp() => Guid.NewGuid().ToString();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd)
|
||||
public override async Task<IdentityResult> SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
@@ -320,7 +151,7 @@ namespace Umbraco.Web.Common.Security
|
||||
// The way we unlock is by setting the lockoutEnd date to the current datetime
|
||||
if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow)
|
||||
{
|
||||
RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -334,62 +165,12 @@ namespace Umbraco.Web.Common.Security
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> ResetAccessFailedCountAsync(T user)
|
||||
public override async Task<IdentityResult> ResetAccessFailedCountAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var lockoutStore = (IUserLockoutStore<T>)Store;
|
||||
var accessFailedCount = await GetAccessFailedCountAsync(user);
|
||||
|
||||
if (accessFailedCount == 0)
|
||||
{
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None);
|
||||
IdentityResult result = await base.ResetAccessFailedCountAsync(user);
|
||||
|
||||
// raise the event now that it's reset
|
||||
RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
return await UpdateAsync(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the Microsoft ASP.NET user management method
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> AccessFailedAsync(T user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var lockoutStore = Store as IUserLockoutStore<T>;
|
||||
if (lockoutStore == null)
|
||||
{
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>));
|
||||
}
|
||||
|
||||
var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None);
|
||||
|
||||
if (count >= Options.Lockout.MaxFailedAccessAttempts)
|
||||
{
|
||||
await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None);
|
||||
|
||||
// NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0
|
||||
// here we are persisting the value for the back office
|
||||
}
|
||||
|
||||
IdentityResult result = await UpdateAsync(user);
|
||||
|
||||
// Slightly confusing: this will return a Success if we successfully update the AccessFailed count
|
||||
if (result.Succeeded)
|
||||
{
|
||||
RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
241
src/Umbraco.Web.Common/Security/UmbracoUserManager.cs
Normal file
241
src/Umbraco.Web.Common/Security/UmbracoUserManager.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Net;
|
||||
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for Umbraco User Managers for back office users or front-end members
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of user</typeparam>
|
||||
public abstract class UmbracoUserManager<T> : UserManager<T>
|
||||
where T : UmbracoIdentityUser
|
||||
{
|
||||
private PasswordGenerator _passwordGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoUserManager{T}"/> class.
|
||||
/// </summary>
|
||||
public UmbracoUserManager(
|
||||
IIpResolver ipResolver,
|
||||
IUserStore<T> store,
|
||||
IOptions<BackOfficeIdentityOptions> optionsAccessor,
|
||||
IPasswordHasher<T> passwordHasher,
|
||||
IEnumerable<IUserValidator<T>> userValidators,
|
||||
IEnumerable<IPasswordValidator<T>> passwordValidators,
|
||||
BackOfficeLookupNormalizer keyNormalizer,
|
||||
BackOfficeIdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<T>> logger,
|
||||
IOptions<UserPasswordConfigurationSettings> passwordConfiguration)
|
||||
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver));
|
||||
PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
|
||||
}
|
||||
|
||||
// We don't support an IUserClaimStore and don't need to (at least currently)
|
||||
public override bool SupportsUserClaim => false;
|
||||
|
||||
// It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
|
||||
public override bool SupportsQueryableUsers => false;
|
||||
|
||||
/// <summary>
|
||||
/// Developers will need to override this to support custom 2 factor auth
|
||||
/// </summary>
|
||||
public override bool SupportsUserTwoFactor => false;
|
||||
|
||||
// We haven't needed to support this yet, though might be necessary for 2FA
|
||||
public override bool SupportsUserPhoneNumber => false;
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate a user's session
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id</param>
|
||||
/// <param name="sessionId">The sesion id</param>
|
||||
/// <returns>True if the sesion is valid, else false</returns>
|
||||
public virtual async Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
|
||||
{
|
||||
var userSessionStore = Store as IUserSessionStore<T>;
|
||||
|
||||
// if this is not set, for backwards compat (which would be super rare), we'll just approve it
|
||||
if (userSessionStore == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will determine which password hasher to use based on what is defined in config
|
||||
/// </summary>
|
||||
/// <param name="passwordConfiguration">The <see cref="IPasswordConfiguration"/></param>
|
||||
/// <returns>An <see cref="IPasswordHasher{T}"/></returns>
|
||||
protected virtual IPasswordHasher<T> GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) => new PasswordHasher<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default back office user password checker
|
||||
/// </summary>
|
||||
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
|
||||
|
||||
public IPasswordConfiguration PasswordConfiguration { get; protected set; }
|
||||
|
||||
public IIpResolver IpResolver { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to generate a password for a user based on the current password validator
|
||||
/// </summary>
|
||||
/// <returns>The generated password</returns>
|
||||
public string GeneratePassword()
|
||||
{
|
||||
if (_passwordGenerator == null)
|
||||
{
|
||||
_passwordGenerator = new PasswordGenerator(PasswordConfiguration);
|
||||
}
|
||||
|
||||
var password = _passwordGenerator.GeneratePassword();
|
||||
return password;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<bool> CheckPasswordAsync(T user, string password)
|
||||
{
|
||||
// we cannot proceed if the user passed in does not have an identity
|
||||
if (user.HasIdentity == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// use the default behavior
|
||||
return await base.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
/// <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">The userId</param>
|
||||
/// <param name="token">The reset password token</param>
|
||||
/// <param name="newPassword">The new password to set it to</param>
|
||||
/// <returns>The <see cref="IdentityResult"/></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>
|
||||
public virtual async Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword)
|
||||
{
|
||||
T user = await FindByIdAsync(userId.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find user");
|
||||
}
|
||||
|
||||
IdentityResult result = await base.ResetPasswordAsync(user, token, newPassword);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
private IUserSecurityStampStore<T> GetSecurityStore()
|
||||
{
|
||||
var store = Store as IUserSecurityStampStore<T>;
|
||||
if (store == null)
|
||||
{
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>));
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is copied from the underlying .NET base class since they decided to not expose it
|
||||
/// </summary>
|
||||
private static string NewSecurityStamp() => Guid.NewGuid().ToString();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd);
|
||||
|
||||
// The way we unlock is by setting the lockoutEnd date to the current datetime
|
||||
if (!result.Succeeded || lockoutEnd < DateTimeOffset.UtcNow)
|
||||
{
|
||||
// Resets the login attempt fails back to 0 when unlock is clicked
|
||||
await ResetAccessFailedCountAsync(user);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> ResetAccessFailedCountAsync(T user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var lockoutStore = (IUserLockoutStore<T>)Store;
|
||||
var accessFailedCount = await GetAccessFailedCountAsync(user);
|
||||
|
||||
if (accessFailedCount == 0)
|
||||
{
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None);
|
||||
|
||||
return await UpdateAsync(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the Microsoft ASP.NET user management method
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public override async Task<IdentityResult> AccessFailedAsync(T user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var lockoutStore = Store as IUserLockoutStore<T>;
|
||||
if (lockoutStore == null)
|
||||
{
|
||||
throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>));
|
||||
}
|
||||
|
||||
var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None);
|
||||
|
||||
if (count >= Options.Lockout.MaxFailedAccessAttempts)
|
||||
{
|
||||
await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken.None);
|
||||
|
||||
// NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0
|
||||
// here we are persisting the value for the back office
|
||||
}
|
||||
|
||||
IdentityResult result = await UpdateAsync(user);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user