Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PT-12140: Fix ResetPasswordAsync and ChangePasswordAsync #2653

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ namespace VirtoCommerce.Platform.Core.Security.Events
{
public class UserPasswordChangedEvent : DomainEvent
{
public UserPasswordChangedEvent(string userId, string сustomPasswordHash)
public UserPasswordChangedEvent(string userId, string customPasswordHash)
{
UserId = userId;
CustomPasswordHash = сustomPasswordHash;
CustomPasswordHash = customPasswordHash;
}

public UserPasswordChangedEvent(ApplicationUser applicationUser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ namespace VirtoCommerce.Platform.Core.Security.Events
{
public class UserResetPasswordEvent : DomainEvent
{
public UserResetPasswordEvent(string userId, string сustomPasswordHash)
public UserResetPasswordEvent(string userId, string customPasswordHash)
{
UserId = userId;
CustomPasswordHash = сustomPasswordHash;
CustomPasswordHash = customPasswordHash;
}

public string UserId { get; set; }
Expand Down
55 changes: 29 additions & 26 deletions src/VirtoCommerce.Platform.Security/CustomUserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand All @@ -28,7 +27,7 @@ public class CustomUserManager : AspNetUserManager<ApplicationUser>
private readonly PasswordOptionsExtended _passwordOptionsExtended;
private readonly IPasswordHasher<ApplicationUser> _passwordHasher;

public CustomUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher,
public CustomUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher,
IOptions<UserOptionsExtended> userOptionsExtended,
IEnumerable<IUserValidator<ApplicationUser>> userValidators, IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services,
Expand Down Expand Up @@ -109,41 +108,45 @@ public override async Task<ApplicationUser> FindByIdAsync(string userId)
return result;
}

public override async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string token, string newPassword)
public override Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string token, string newPassword)
{
//It is important to call base.FindByIdAsync method to avoid of update a cached user.
var existUser = await base.FindByIdAsync(user.Id);
existUser.LastPasswordChangedDate = DateTime.UtcNow;

var result = await base.ResetPasswordAsync(existUser, token, newPassword);
if (result == IdentityResult.Success)
{
SecurityCacheRegion.ExpireUser(user);

await SavePasswordHistory(user, newPassword);

// Calculate password hash for external hash storage. This provided as workaround until password hash storage would implemented
var customPasswordHash = _passwordHasher.HashPassword(user, newPassword);
await _eventPublisher.Publish(new UserResetPasswordEvent(user.Id, customPasswordHash));
}
return UpdatePasswordAsync(user, newPassword,
(user, newPassword) => base.ResetPasswordAsync(user, token, newPassword),
(userId, customPasswordHash) => new UserResetPasswordEvent(userId, customPasswordHash));
}

return result;
public override Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
{
return UpdatePasswordAsync(user, newPassword,
(user, newPassword) => base.ChangePasswordAsync(user, currentPassword, newPassword),
(userId, customPasswordHash) => new UserPasswordChangedEvent(userId, customPasswordHash));
}

public override async Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
protected virtual async Task<IdentityResult> UpdatePasswordAsync<TEvent>(
ApplicationUser user,
string newPassword,
Func<ApplicationUser, string, Task<IdentityResult>> updatePassword,
Func<string, string, TEvent> buildEvent)
where TEvent : class, IEvent
{
var previousPasswordChangedDate = user.LastPasswordChangedDate;
user.LastPasswordChangedDate = DateTime.UtcNow;

var result = await base.ChangePasswordAsync(user, currentPassword, newPassword);
var result = await updatePassword(user, newPassword);
if (result == IdentityResult.Success)
{
SecurityCacheRegion.ExpireUser(user);

await SavePasswordHistory(user, newPassword);

// Calculate password hash for external hash storage. This provided as workaround until password hash storage would implemented
// Calculate password hash for external hash storage. This provided as workaround until password hash storage is implemented.
var customPasswordHash = _passwordHasher.HashPassword(user, newPassword);
await _eventPublisher.Publish(new UserPasswordChangedEvent(user.Id, customPasswordHash));
var @event = buildEvent(user.Id, customPasswordHash);
await _eventPublisher.Publish(@event);
}
else
{
user.LastPasswordChangedDate = previousPasswordChangedDate;
}

return result;
Expand Down Expand Up @@ -235,7 +238,7 @@ protected virtual async Task UpdateUserRolesAsync(ApplicationUser user)
}

var targetRoles = await GetRolesAsync(user);
var sourceRoles = user.Roles.Select(x => x.Name);
var sourceRoles = user.Roles.Select(x => x.Name).ToList();

//Add
foreach (var newRole in sourceRoles.Except(targetRoles))
Expand All @@ -258,7 +261,7 @@ protected virtual async Task UpdateUserLoginsAsync(ApplicationUser user)
}

var targetLogins = await GetLoginsAsync(user);
var sourceLogins = user.Logins.Select(x => new UserLoginInfo(x.LoginProvider, x.ProviderKey, null));
var sourceLogins = user.Logins.Select(x => new UserLoginInfo(x.LoginProvider, x.ProviderKey, null)).ToList();

foreach (var item in sourceLogins.Where(x => targetLogins.All(y => x.LoginProvider + x.ProviderKey != y.LoginProvider + y.ProviderKey)))
{
Expand Down Expand Up @@ -376,7 +379,7 @@ protected virtual async Task LoadUserDetailsAsync(ApplicationUser user)

// Read associated logins
var logins = await base.GetLoginsAsync(user);
user.Logins = logins.Select(x => new ApplicationUserLogin() { LoginProvider = x.LoginProvider, ProviderKey = x.ProviderKey }).ToArray();
user.Logins = logins.Select(x => new ApplicationUserLogin { LoginProvider = x.LoginProvider, ProviderKey = x.ProviderKey }).ToArray();
}

/// <summary>
Expand Down
Loading