V15: User password resetting notification (#18679)

* Introduce UserPasswordResettingNotification

* Removed changes to IUserService interface.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Nikolaj Geisle
2025-04-09 08:28:12 +01:00
committed by GitHub
parent d9d66465f6
commit 76f27726c5
3 changed files with 56 additions and 13 deletions

View File

@@ -21,6 +21,10 @@ public abstract class SecurityControllerBase : ManagementApiControllerBase
.WithTitle("The password reset token was invalid")
.WithDetail("The specified password reset token was either used already or wrong.")
.Build()),
UserOperationStatus.CancelledByNotification => BadRequest(problemDetailsBuilder
.WithTitle("Cancelled by notification")
.WithDetail("A notification handler prevented the user operation.")
.Build()),
UserOperationStatus.UnknownFailure => BadRequest(problemDetailsBuilder
.WithTitle("Unknown failure")
.WithDetail(errorMessageResult?.Error?.ErrorMessage ?? "The error was unknown")

View File

@@ -0,0 +1,13 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models.Membership;
namespace Umbraco.Cms.Core.Notifications;
public class UserPasswordResettingNotification : CancelableObjectNotification<IUser>
{
public UserPasswordResettingNotification(IUser target, EventMessages messages) : base(target, messages)
{
}
public IUser User => Target;
}

View File

@@ -1180,7 +1180,21 @@ internal partial class UserService : RepositoryService, IUserService
return keys;
}
/// <inheritdoc/>
public async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(Guid performingUserKey, ChangeUserPasswordModel model)
{
IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
IBackOfficeUserStore userStore = serviceScope.ServiceProvider.GetRequiredService<IBackOfficeUserStore>();
IUser? performingUser = await userStore.GetAsync(performingUserKey);
if (performingUser is null)
{
return Attempt.FailWithStatus(UserOperationStatus.MissingUser, new PasswordChangedModel());
}
return await ChangePasswordAsync(performingUser, model);
}
private async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(IUser performingUser, ChangeUserPasswordModel model)
{
IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
using ICoreScope scope = ScopeProvider.CreateCoreScope();
@@ -1197,12 +1211,6 @@ internal partial class UserService : RepositoryService, IUserService
return Attempt.FailWithStatus(UserOperationStatus.InvalidUserType, new PasswordChangedModel());
}
IUser? performingUser = await userStore.GetAsync(performingUserKey);
if (performingUser is null)
{
return Attempt.FailWithStatus(UserOperationStatus.MissingUser, new PasswordChangedModel());
}
// require old password for self change when outside of invite or resetByToken flows
if (performingUser.UserState != UserState.Invited && performingUser.Username == user.Username && string.IsNullOrEmpty(model.OldPassword) && string.IsNullOrEmpty(model.ResetPasswordToken))
{
@@ -1226,12 +1234,13 @@ internal partial class UserService : RepositoryService, IUserService
IBackOfficePasswordChanger passwordChanger = serviceScope.ServiceProvider.GetRequiredService<IBackOfficePasswordChanger>();
Attempt<PasswordChangedModel?> result = await passwordChanger.ChangeBackOfficePassword(
new ChangeBackOfficeUserPasswordModel
{
NewPassword = model.NewPassword,
OldPassword = model.OldPassword,
User = user,
ResetPasswordToken = model.ResetPasswordToken,
}, performingUser);
{
NewPassword = model.NewPassword,
OldPassword = model.OldPassword,
User = user,
ResetPasswordToken = model.ResetPasswordToken,
},
performingUser);
if (result.Success is false)
{
@@ -2184,9 +2193,26 @@ internal partial class UserService : RepositoryService, IUserService
public async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ResetPasswordAsync(Guid userKey, string token, string password)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
EventMessages evtMsgs = EventMessagesFactory.Get();
IBackOfficeUserStore userStore = serviceScope.ServiceProvider.GetRequiredService<IBackOfficeUserStore>();
IUser? user = await userStore.GetAsync(userKey);
if (user is null)
{
return Attempt.FailWithStatus(UserOperationStatus.UserNotFound, new PasswordChangedModel());
}
var savingNotification = new UserPasswordResettingNotification(user, evtMsgs);
if (await scope.Notifications.PublishCancelableAsync(savingNotification))
{
scope.Complete();
return Attempt.FailWithStatus(UserOperationStatus.CancelledByNotification, new PasswordChangedModel());
}
Attempt<PasswordChangedModel, UserOperationStatus> changePasswordAttempt =
await ChangePasswordAsync(userKey, new ChangeUserPasswordModel
await ChangePasswordAsync(user, new ChangeUserPasswordModel
{
NewPassword = password,
UserKey = userKey,