Skip to content

Commit

Permalink
VCST-781: Publish two different events when changing password (#2780)
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-dudarev authored Apr 11, 2024
1 parent 309ca4b commit a5c087b
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using VirtoCommerce.Platform.Core.Events;

namespace VirtoCommerce.Platform.Core.Security.Events
{
/// <summary>
/// This event is published when a user has changed their password.
/// </summary>
public class UserChangedPasswordEvent : DomainEvent
{
public UserChangedPasswordEvent(string userId, string customPasswordHash)
{
UserId = userId;
CustomPasswordHash = customPasswordHash;
}

public string UserId { get; set; }

/// <summary>
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
/// </summary>
public string CustomPasswordHash { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace VirtoCommerce.Platform.Core.Security.Events
{
/// <summary>
/// This event is published when a user's password is changed for any reason, including when the user changes or resets the password.
/// </summary>
public class UserPasswordChangedEvent : DomainEvent
{
public UserPasswordChangedEvent(string userId, string customPasswordHash)
Expand All @@ -19,7 +22,7 @@ public UserPasswordChangedEvent(ApplicationUser applicationUser)
public string UserId { get; set; }

/// <summary>
/// Password hash for external hash storage. This provided as workaround until password hash storage would implemented
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
/// </summary>
public string CustomPasswordHash { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace VirtoCommerce.Platform.Core.Security.Events
{
/// <summary>
/// This event is published when a user has reset their password.
/// </summary>
public class UserResetPasswordEvent : DomainEvent
{
public UserResetPasswordEvent(string userId, string customPasswordHash)
Expand All @@ -13,7 +16,7 @@ public UserResetPasswordEvent(string userId, string customPasswordHash)
public string UserId { get; set; }

/// <summary>
/// Password hash for external hash storage. This provided as workaround until password hash storage would implemented
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
/// </summary>
public string CustomPasswordHash { get; set; }
}
Expand Down
2 changes: 1 addition & 1 deletion src/VirtoCommerce.Platform.Security/CustomUserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public override Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, s
{
return UpdatePasswordAsync(user, newPassword,
(user, newPassword) => base.ChangePasswordAsync(user, currentPassword, newPassword),
(userId, customPasswordHash) => new UserPasswordChangedEvent(userId, customPasswordHash));
(userId, customPasswordHash) => new UserChangedPasswordEvent(userId, customPasswordHash));
}

protected virtual async Task<IdentityResult> UpdatePasswordAsync<TEvent>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@

namespace VirtoCommerce.Platform.Security.Handlers
{
public class LogChangesUserChangedEventHandler : IEventHandler<UserChangedEvent>, IEventHandler<UserLoginEvent>,
IEventHandler<UserLogoutEvent>, IEventHandler<UserPasswordChangedEvent>,
IEventHandler<UserResetPasswordEvent>, IEventHandler<UserRoleAddedEvent>,
IEventHandler<UserRoleRemovedEvent>
public class LogChangesUserChangedEventHandler :
IEventHandler<UserChangedEvent>,
IEventHandler<UserLoginEvent>,
IEventHandler<UserLogoutEvent>,
IEventHandler<UserPasswordChangedEvent>,
IEventHandler<UserResetPasswordEvent>,
IEventHandler<UserChangedPasswordEvent>,
IEventHandler<UserRoleAddedEvent>,
IEventHandler<UserRoleRemovedEvent>
{
private readonly IChangeLogService _changeLogService;

Expand Down Expand Up @@ -64,7 +69,12 @@ public virtual async Task Handle(UserPasswordChangedEvent message)

public virtual async Task Handle(UserResetPasswordEvent message)
{
await SaveOperationLogAsync(message.UserId, "Password resets", EntryState.Modified);
await SaveOperationLogAsync(message.UserId, "Reset password", EntryState.Modified);
}

public async Task Handle(UserChangedPasswordEvent message)
{
await SaveOperationLogAsync(message.UserId, "Changed password", EntryState.Modified);
}

public virtual Task Handle(UserRoleAddedEvent message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ public static IApplicationBuilder UsePlatformPermissions(this IApplicationBuilde

public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder appBuilder)
{
appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserChangedEvent, UserApiKeyActualizeEventHandler>();

appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserPasswordChangedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserResetPasswordEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserChangedPasswordEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserLoginEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserLogoutEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserRoleAddedEvent, LogChangesUserChangedEventHandler>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Moq;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Core.Events;
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Core.Security.Events;
Expand All @@ -23,7 +24,7 @@ public async Task Create_CheckEvents(ApplicationUser user, Action<IEvent>[] asse
//Arrange

var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
userStoreMock.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None))
.ReturnsAsync(IdentityResult.Success);
var eventPublisher = new EventPublisherStub();
Expand Down Expand Up @@ -63,10 +64,7 @@ public async Task ResetPassword_CheckEvents(ApplicationUser user, Action<IEvent>
{
//Arrange
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
userStoreMock.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.SetPasswordHashAsync(user, user.PasswordHash, CancellationToken.None))
.Returns(Task.CompletedTask);
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
var eventPublisher = new EventPublisherStub();

var userManager = SecurityMockHelper.TestCustomUserManager(userStoreMock, eventPublisher);
Expand All @@ -88,10 +86,7 @@ public async Task ChangePassword_CheckEvents(ApplicationUser user, Action<IEvent
{
//Arrange
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
userStoreMock.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.GetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(user.PasswordHash);
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
var eventPublisher = new EventPublisherStub();

var userManager = SecurityMockHelper.TestCustomUserManager(userStoreMock, eventPublisher);
Expand Down Expand Up @@ -164,6 +159,7 @@ public IEnumerator<object[]> GetEnumerator()
new Action<IEvent>[]
{
x => x.GetType().Should().Be<UserChangingEvent>(),
x => x.GetType().Should().Be<UserPasswordChangedEvent>(),
x => x.GetType().Should().Be<UserResetPasswordEvent>(),
}
};
Expand All @@ -183,6 +179,7 @@ public IEnumerator<object[]> GetEnumerator()
{
x => x.GetType().Should().Be<UserChangingEvent>(),
x => x.GetType().Should().Be<UserPasswordChangedEvent>(),
x => x.GetType().Should().Be<UserChangedPasswordEvent>(),
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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;
Expand All @@ -11,7 +12,6 @@
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Security;
using VirtoCommerce.Platform.Security.Repositories;
using VirtoCommerce.Platform.Web.Security;

namespace VirtoCommerce.Platform.Web.Tests.Security
{
Expand Down Expand Up @@ -46,6 +46,16 @@ public static CustomUserManager TestCustomUserManager(
.ReturnsAsync(Array.Empty<UserLoginInfo>());
storeMock.Setup(x => x.UpdateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None))
.ReturnsAsync(IdentityResult.Success);
storeMock.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.GetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<CancellationToken>()))
.Returns<ApplicationUser, CancellationToken>((user, _) => Task.FromResult(user.PasswordHash));
storeMock.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.SetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns<ApplicationUser, string, CancellationToken>((user, hash, _) =>
{
user.PasswordHash = hash;
return Task.CompletedTask;
});

var identityOptionsMock = new Mock<IOptions<IdentityOptions>>();
if (identityOptions != null)
Expand All @@ -56,6 +66,8 @@ public static CustomUserManager TestCustomUserManager(
if (passwordHasher == null)
{
passwordHasher = new Mock<IPasswordHasher<ApplicationUser>>();
passwordHasher.Setup(x => x.HashPassword(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.Returns(Guid.NewGuid().ToString());
passwordHasher.Setup(x => x.VerifyHashedPassword(It.IsAny<ApplicationUser>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(PasswordVerificationResult.Success);
}
Expand Down

0 comments on commit a5c087b

Please sign in to comment.