diff --git a/src/VirtoCommerce.Platform.Core/Security/Events/UserChangedPasswordEvent.cs b/src/VirtoCommerce.Platform.Core/Security/Events/UserChangedPasswordEvent.cs new file mode 100644 index 00000000000..ae9c6e71616 --- /dev/null +++ b/src/VirtoCommerce.Platform.Core/Security/Events/UserChangedPasswordEvent.cs @@ -0,0 +1,23 @@ +using VirtoCommerce.Platform.Core.Events; + +namespace VirtoCommerce.Platform.Core.Security.Events +{ + /// + /// This event is published when a user has changed their password. + /// + public class UserChangedPasswordEvent : DomainEvent + { + public UserChangedPasswordEvent(string userId, string customPasswordHash) + { + UserId = userId; + CustomPasswordHash = customPasswordHash; + } + + public string UserId { get; set; } + + /// + /// Password hash for external hash storage. This provided as workaround until password hash storage is implemented + /// + public string CustomPasswordHash { get; set; } + } +} diff --git a/src/VirtoCommerce.Platform.Core/Security/Events/UserPasswordChangedEvent.cs b/src/VirtoCommerce.Platform.Core/Security/Events/UserPasswordChangedEvent.cs index 60895156cc4..dc380458ca6 100644 --- a/src/VirtoCommerce.Platform.Core/Security/Events/UserPasswordChangedEvent.cs +++ b/src/VirtoCommerce.Platform.Core/Security/Events/UserPasswordChangedEvent.cs @@ -2,6 +2,9 @@ namespace VirtoCommerce.Platform.Core.Security.Events { + /// + /// This event is published when a user's password is changed for any reason, including when the user changes or resets the password. + /// public class UserPasswordChangedEvent : DomainEvent { public UserPasswordChangedEvent(string userId, string customPasswordHash) @@ -19,7 +22,7 @@ public UserPasswordChangedEvent(ApplicationUser applicationUser) public string UserId { get; set; } /// - /// 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 /// public string CustomPasswordHash { get; set; } } diff --git a/src/VirtoCommerce.Platform.Core/Security/Events/UserResetPasswordEvent.cs b/src/VirtoCommerce.Platform.Core/Security/Events/UserResetPasswordEvent.cs index 1c42785d699..a1a6847efe5 100644 --- a/src/VirtoCommerce.Platform.Core/Security/Events/UserResetPasswordEvent.cs +++ b/src/VirtoCommerce.Platform.Core/Security/Events/UserResetPasswordEvent.cs @@ -2,6 +2,9 @@ namespace VirtoCommerce.Platform.Core.Security.Events { + /// + /// This event is published when a user has reset their password. + /// public class UserResetPasswordEvent : DomainEvent { public UserResetPasswordEvent(string userId, string customPasswordHash) @@ -13,7 +16,7 @@ public UserResetPasswordEvent(string userId, string customPasswordHash) public string UserId { get; set; } /// - /// 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 /// public string CustomPasswordHash { get; set; } } diff --git a/src/VirtoCommerce.Platform.Security/CustomUserManager.cs b/src/VirtoCommerce.Platform.Security/CustomUserManager.cs index f473e5b73fe..789310a5243 100644 --- a/src/VirtoCommerce.Platform.Security/CustomUserManager.cs +++ b/src/VirtoCommerce.Platform.Security/CustomUserManager.cs @@ -119,7 +119,7 @@ public override Task 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 UpdatePasswordAsync( diff --git a/src/VirtoCommerce.Platform.Security/Handlers/LogChangesUserChangedEventHandler.cs b/src/VirtoCommerce.Platform.Security/Handlers/LogChangesUserChangedEventHandler.cs index d83ecb64f00..ed1f618834a 100644 --- a/src/VirtoCommerce.Platform.Security/Handlers/LogChangesUserChangedEventHandler.cs +++ b/src/VirtoCommerce.Platform.Security/Handlers/LogChangesUserChangedEventHandler.cs @@ -8,10 +8,15 @@ namespace VirtoCommerce.Platform.Security.Handlers { - public class LogChangesUserChangedEventHandler : IEventHandler, IEventHandler, - IEventHandler, IEventHandler, - IEventHandler, IEventHandler, - IEventHandler + public class LogChangesUserChangedEventHandler : + IEventHandler, + IEventHandler, + IEventHandler, + IEventHandler, + IEventHandler, + IEventHandler, + IEventHandler, + IEventHandler { private readonly IChangeLogService _changeLogService; @@ -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) diff --git a/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs b/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs index 82f8f5d44d6..5c609814658 100644 --- a/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs +++ b/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs @@ -29,10 +29,12 @@ public static IApplicationBuilder UsePlatformPermissions(this IApplicationBuilde public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder appBuilder) { - appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); + + appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); + appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); appBuilder.RegisterEventHandler(); diff --git a/tests/VirtoCommerce.Platform.Web.Tests/Security/CustomUserManagerEventsUnitTests.cs b/tests/VirtoCommerce.Platform.Web.Tests/Security/CustomUserManagerEventsUnitTests.cs index 536217de8e1..4b27c2afc08 100644 --- a/tests/VirtoCommerce.Platform.Web.Tests/Security/CustomUserManagerEventsUnitTests.cs +++ b/tests/VirtoCommerce.Platform.Web.Tests/Security/CustomUserManagerEventsUnitTests.cs @@ -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; @@ -23,7 +24,7 @@ public async Task Create_CheckEvents(ApplicationUser user, Action[] asse //Arrange var userStoreMock = new Mock>(); - 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(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); var eventPublisher = new EventPublisherStub(); @@ -63,10 +64,7 @@ public async Task ResetPassword_CheckEvents(ApplicationUser user, Action { //Arrange var userStoreMock = new Mock>(); - userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user); - userStoreMock.As>() - .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); @@ -88,10 +86,7 @@ public async Task ChangePassword_CheckEvents(ApplicationUser user, Action>(); - userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user); - userStoreMock.As>() - .Setup(x => x.GetPasswordHashAsync(It.IsAny(), It.IsAny())) - .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); @@ -164,6 +159,7 @@ public IEnumerator GetEnumerator() new Action[] { x => x.GetType().Should().Be(), + x => x.GetType().Should().Be(), x => x.GetType().Should().Be(), } }; @@ -183,6 +179,7 @@ public IEnumerator GetEnumerator() { x => x.GetType().Should().Be(), x => x.GetType().Should().Be(), + x => x.GetType().Should().Be(), } }; } diff --git a/tests/VirtoCommerce.Platform.Web.Tests/Security/SecurityMockHelper.cs b/tests/VirtoCommerce.Platform.Web.Tests/Security/SecurityMockHelper.cs index 7122f7dea71..2c7164b2dd5 100644 --- a/tests/VirtoCommerce.Platform.Web.Tests/Security/SecurityMockHelper.cs +++ b/tests/VirtoCommerce.Platform.Web.Tests/Security/SecurityMockHelper.cs @@ -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; @@ -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 { @@ -46,6 +46,16 @@ public static CustomUserManager TestCustomUserManager( .ReturnsAsync(Array.Empty()); storeMock.Setup(x => x.UpdateAsync(It.IsAny(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); + storeMock.As>() + .Setup(x => x.GetPasswordHashAsync(It.IsAny(), It.IsAny())) + .Returns((user, _) => Task.FromResult(user.PasswordHash)); + storeMock.As>() + .Setup(x => x.SetPasswordHashAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((user, hash, _) => + { + user.PasswordHash = hash; + return Task.CompletedTask; + }); var identityOptionsMock = new Mock>(); if (identityOptions != null) @@ -56,6 +66,8 @@ public static CustomUserManager TestCustomUserManager( if (passwordHasher == null) { passwordHasher = new Mock>(); + passwordHasher.Setup(x => x.HashPassword(It.IsAny(), It.IsAny())) + .Returns(Guid.NewGuid().ToString()); passwordHasher.Setup(x => x.VerifyHashedPassword(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(PasswordVerificationResult.Success); }