Skip to content

Commit a5c087b

Browse files
VCST-781: Publish two different events when changing password (#2780)
1 parent 309ca4b commit a5c087b

File tree

8 files changed

+69
-19
lines changed

8 files changed

+69
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using VirtoCommerce.Platform.Core.Events;
2+
3+
namespace VirtoCommerce.Platform.Core.Security.Events
4+
{
5+
/// <summary>
6+
/// This event is published when a user has changed their password.
7+
/// </summary>
8+
public class UserChangedPasswordEvent : DomainEvent
9+
{
10+
public UserChangedPasswordEvent(string userId, string customPasswordHash)
11+
{
12+
UserId = userId;
13+
CustomPasswordHash = customPasswordHash;
14+
}
15+
16+
public string UserId { get; set; }
17+
18+
/// <summary>
19+
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
20+
/// </summary>
21+
public string CustomPasswordHash { get; set; }
22+
}
23+
}

src/VirtoCommerce.Platform.Core/Security/Events/UserPasswordChangedEvent.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

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

2124
/// <summary>
22-
/// Password hash for external hash storage. This provided as workaround until password hash storage would implemented
25+
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
2326
/// </summary>
2427
public string CustomPasswordHash { get; set; }
2528
}

src/VirtoCommerce.Platform.Core/Security/Events/UserResetPasswordEvent.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

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

1518
/// <summary>
16-
/// Password hash for external hash storage. This provided as workaround until password hash storage would implemented
19+
/// Password hash for external hash storage. This provided as workaround until password hash storage is implemented
1720
/// </summary>
1821
public string CustomPasswordHash { get; set; }
1922
}

src/VirtoCommerce.Platform.Security/CustomUserManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public override Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, s
119119
{
120120
return UpdatePasswordAsync(user, newPassword,
121121
(user, newPassword) => base.ChangePasswordAsync(user, currentPassword, newPassword),
122-
(userId, customPasswordHash) => new UserPasswordChangedEvent(userId, customPasswordHash));
122+
(userId, customPasswordHash) => new UserChangedPasswordEvent(userId, customPasswordHash));
123123
}
124124

125125
protected virtual async Task<IdentityResult> UpdatePasswordAsync<TEvent>(

src/VirtoCommerce.Platform.Security/Handlers/LogChangesUserChangedEventHandler.cs

+15-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88

99
namespace VirtoCommerce.Platform.Security.Handlers
1010
{
11-
public class LogChangesUserChangedEventHandler : IEventHandler<UserChangedEvent>, IEventHandler<UserLoginEvent>,
12-
IEventHandler<UserLogoutEvent>, IEventHandler<UserPasswordChangedEvent>,
13-
IEventHandler<UserResetPasswordEvent>, IEventHandler<UserRoleAddedEvent>,
14-
IEventHandler<UserRoleRemovedEvent>
11+
public class LogChangesUserChangedEventHandler :
12+
IEventHandler<UserChangedEvent>,
13+
IEventHandler<UserLoginEvent>,
14+
IEventHandler<UserLogoutEvent>,
15+
IEventHandler<UserPasswordChangedEvent>,
16+
IEventHandler<UserResetPasswordEvent>,
17+
IEventHandler<UserChangedPasswordEvent>,
18+
IEventHandler<UserRoleAddedEvent>,
19+
IEventHandler<UserRoleRemovedEvent>
1520
{
1621
private readonly IChangeLogService _changeLogService;
1722

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

6570
public virtual async Task Handle(UserResetPasswordEvent message)
6671
{
67-
await SaveOperationLogAsync(message.UserId, "Password resets", EntryState.Modified);
72+
await SaveOperationLogAsync(message.UserId, "Reset password", EntryState.Modified);
73+
}
74+
75+
public async Task Handle(UserChangedPasswordEvent message)
76+
{
77+
await SaveOperationLogAsync(message.UserId, "Changed password", EntryState.Modified);
6878
}
6979

7080
public virtual Task Handle(UserRoleAddedEvent message)

src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ public static IApplicationBuilder UsePlatformPermissions(this IApplicationBuilde
2929

3030
public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder appBuilder)
3131
{
32-
appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
3332
appBuilder.RegisterEventHandler<UserChangedEvent, UserApiKeyActualizeEventHandler>();
33+
34+
appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
3435
appBuilder.RegisterEventHandler<UserPasswordChangedEvent, LogChangesUserChangedEventHandler>();
3536
appBuilder.RegisterEventHandler<UserResetPasswordEvent, LogChangesUserChangedEventHandler>();
37+
appBuilder.RegisterEventHandler<UserChangedPasswordEvent, LogChangesUserChangedEventHandler>();
3638
appBuilder.RegisterEventHandler<UserLoginEvent, LogChangesUserChangedEventHandler>();
3739
appBuilder.RegisterEventHandler<UserLogoutEvent, LogChangesUserChangedEventHandler>();
3840
appBuilder.RegisterEventHandler<UserRoleAddedEvent, LogChangesUserChangedEventHandler>();

tests/VirtoCommerce.Platform.Web.Tests/Security/CustomUserManagerEventsUnitTests.cs

+6-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using FluentAssertions;
77
using Microsoft.AspNetCore.Identity;
88
using Moq;
9+
using VirtoCommerce.Platform.Core.Common;
910
using VirtoCommerce.Platform.Core.Events;
1011
using VirtoCommerce.Platform.Core.Security;
1112
using VirtoCommerce.Platform.Core.Security.Events;
@@ -23,7 +24,7 @@ public async Task Create_CheckEvents(ApplicationUser user, Action<IEvent>[] asse
2324
//Arrange
2425

2526
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
26-
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
27+
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
2728
userStoreMock.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None))
2829
.ReturnsAsync(IdentityResult.Success);
2930
var eventPublisher = new EventPublisherStub();
@@ -63,10 +64,7 @@ public async Task ResetPassword_CheckEvents(ApplicationUser user, Action<IEvent>
6364
{
6465
//Arrange
6566
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
66-
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
67-
userStoreMock.As<IUserPasswordStore<ApplicationUser>>()
68-
.Setup(x => x.SetPasswordHashAsync(user, user.PasswordHash, CancellationToken.None))
69-
.Returns(Task.CompletedTask);
67+
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
7068
var eventPublisher = new EventPublisherStub();
7169

7270
var userManager = SecurityMockHelper.TestCustomUserManager(userStoreMock, eventPublisher);
@@ -88,10 +86,7 @@ public async Task ChangePassword_CheckEvents(ApplicationUser user, Action<IEvent
8886
{
8987
//Arrange
9088
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
91-
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user);
92-
userStoreMock.As<IUserPasswordStore<ApplicationUser>>()
93-
.Setup(x => x.GetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<CancellationToken>()))
94-
.ReturnsAsync(user.PasswordHash);
89+
userStoreMock.Setup(x => x.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user.CloneTyped());
9590
var eventPublisher = new EventPublisherStub();
9691

9792
var userManager = SecurityMockHelper.TestCustomUserManager(userStoreMock, eventPublisher);
@@ -164,6 +159,7 @@ public IEnumerator<object[]> GetEnumerator()
164159
new Action<IEvent>[]
165160
{
166161
x => x.GetType().Should().Be<UserChangingEvent>(),
162+
x => x.GetType().Should().Be<UserPasswordChangedEvent>(),
167163
x => x.GetType().Should().Be<UserResetPasswordEvent>(),
168164
}
169165
};
@@ -183,6 +179,7 @@ public IEnumerator<object[]> GetEnumerator()
183179
{
184180
x => x.GetType().Should().Be<UserChangingEvent>(),
185181
x => x.GetType().Should().Be<UserPasswordChangedEvent>(),
182+
x => x.GetType().Should().Be<UserChangedPasswordEvent>(),
186183
}
187184
};
188185
}

tests/VirtoCommerce.Platform.Web.Tests/Security/SecurityMockHelper.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading;
4+
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Identity;
56
using Microsoft.Extensions.Logging;
67
using Microsoft.Extensions.Options;
@@ -11,7 +12,6 @@
1112
using VirtoCommerce.Platform.Core.Security;
1213
using VirtoCommerce.Platform.Security;
1314
using VirtoCommerce.Platform.Security.Repositories;
14-
using VirtoCommerce.Platform.Web.Security;
1515

1616
namespace VirtoCommerce.Platform.Web.Tests.Security
1717
{
@@ -46,6 +46,16 @@ public static CustomUserManager TestCustomUserManager(
4646
.ReturnsAsync(Array.Empty<UserLoginInfo>());
4747
storeMock.Setup(x => x.UpdateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None))
4848
.ReturnsAsync(IdentityResult.Success);
49+
storeMock.As<IUserPasswordStore<ApplicationUser>>()
50+
.Setup(x => x.GetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<CancellationToken>()))
51+
.Returns<ApplicationUser, CancellationToken>((user, _) => Task.FromResult(user.PasswordHash));
52+
storeMock.As<IUserPasswordStore<ApplicationUser>>()
53+
.Setup(x => x.SetPasswordHashAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
54+
.Returns<ApplicationUser, string, CancellationToken>((user, hash, _) =>
55+
{
56+
user.PasswordHash = hash;
57+
return Task.CompletedTask;
58+
});
4959

5060
var identityOptionsMock = new Mock<IOptions<IdentityOptions>>();
5161
if (identityOptions != null)
@@ -56,6 +66,8 @@ public static CustomUserManager TestCustomUserManager(
5666
if (passwordHasher == null)
5767
{
5868
passwordHasher = new Mock<IPasswordHasher<ApplicationUser>>();
69+
passwordHasher.Setup(x => x.HashPassword(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
70+
.Returns(Guid.NewGuid().ToString());
5971
passwordHasher.Setup(x => x.VerifyHashedPassword(It.IsAny<ApplicationUser>(), It.IsAny<string>(), It.IsAny<string>()))
6072
.Returns(PasswordVerificationResult.Success);
6173
}

0 commit comments

Comments
 (0)