diff --git a/src/Umbraco.Core/Auditing/AuditEventHandler.cs b/src/Umbraco.Core/Auditing/AuditEventHandler.cs index 07e1ccafc2..44be3cd7ca 100644 --- a/src/Umbraco.Core/Auditing/AuditEventHandler.cs +++ b/src/Umbraco.Core/Auditing/AuditEventHandler.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Auditing { var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity(); return identity == null - ? new User { Id = 0, Name = "(no user)", Email = "" } + ? new User { Id = 0, Name = "SYSTEM", Email = "" } : _userServiceInstance.GetUserById(Convert.ToInt32(identity.Id)); } } @@ -68,6 +68,16 @@ namespace Umbraco.Core.Auditing MemberService.RemovedRoles += OnRemovedRoles; } + private string FormatEmail(IMember member) + { + return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; + } + + private string FormatEmail(IUser user) + { + return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + } + private void OnRemovedRoles(IMemberService sender, RolesEventArgs args) { var performingUser = PerformingUser; @@ -76,10 +86,10 @@ namespace Umbraco.Core.Auditing foreach (var id in args.MemberIds) { members.TryGetValue(id, out var member); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/member/roles/removed", $"modified roles for member id:{id} \"{member?.Name ?? "(unknown)"}\" <{member?.Email ?? ""}>, removed {roles}"); + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", $"roles modified, removed {roles}"); } } @@ -91,10 +101,10 @@ namespace Umbraco.Core.Auditing foreach (var id in args.MemberIds) { members.TryGetValue(id, out var member); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/member/roles/assigned", $"modified roles for member id:{id} \"{member?.Name ?? "(unknown)"}\" <{member?.Email ?? ""}>, assigned {roles}"); + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/assigned", $"roles modified, assigned {roles}"); } } @@ -104,15 +114,18 @@ namespace Umbraco.Core.Auditing var groups = saveEventArgs.SavedEntities; foreach (var group in groups) { - //var dp = string.Join(", ", member.Properties.Where(x => x.WasDirty()).Select(x => x.Alias)); - var dp = string.Join(", ", ((UserGroup) group).GetWereDirtyProperties()); - var sections = string.Join(", ", group.AllowedSections); - var perms = string.Join(", ", group.Permissions); + var dp = string.Join(", ", ((UserGroup) group).GetPreviouslyDirtyProperties()); + var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections") + ? string.Join(", ", group.AllowedSections) + : null; + var perms = ((UserGroup)group).WasPropertyDirty("Permissions") + ? string.Join(", ", group.Permissions) + : null; - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/user-group/save", $"save group id:{group.Id}:{group.Alias} \"{group.Name}\", updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}, sections: {sections}, perms: {perms}"); + -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};{(sections == null ? "" : $", assigned sections: {sections}")}{(perms == null ? "" : $", assigned perms: {perms}")}"); } } @@ -126,10 +139,10 @@ namespace Umbraco.Core.Auditing var assigned = string.Join(", ", perm.AssignedPermissions); var entity = _entityServiceInstance.Get(perm.EntityId); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/user-group/permissions-change", $"assign group {(perm.IsDefaultPermissions ? "default " : "")}perms id:{group.Id}:{group.Alias} \"{group.Name}\", assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\""); + -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\""); } } @@ -139,13 +152,12 @@ namespace Umbraco.Core.Auditing var members = saveEventArgs.SavedEntities; foreach (var member in members) { - //var dp = string.Join(", ", member.Properties.Where(x => x.WasDirty()).Select(x => x.Alias)); - var dp = string.Join(", ", ((Member) member).GetWereDirtyProperties()); + var dp = string.Join(", ", ((Member) member).GetPreviouslyDirtyProperties()); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/member/save", $"save member id:{member.Id} \"{member.Name}\" <{member.Email}>, updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); } } @@ -155,10 +167,10 @@ namespace Umbraco.Core.Auditing var members = deleteEventArgs.DeletedEntities; foreach (var member in members) { - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - 0, null, - "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" <{member.Email}>"); + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); } } @@ -168,17 +180,16 @@ namespace Umbraco.Core.Auditing var affectedUsers = saveEventArgs.SavedEntities; foreach (var affectedUser in affectedUsers) { - var sections = affectedUser.WasPropertyDirty("AllowedSections") - ? string.Join(", ", affectedUser.AllowedSections) - : null; var groups = affectedUser.WasPropertyDirty("Groups") ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) : null; - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + var dp = string.Join(", ", ((User)affectedUser).GetPreviouslyDirtyProperties()); + + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", - "umbraco/user/save", $"save user{(sections == null ? "" : (", sections: " + sections))}{(groups == null ? "" : (", groups: " + groups))}"); + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); } } @@ -187,9 +198,9 @@ namespace Umbraco.Core.Auditing var performingUser = PerformingUser; var affectedUsers = deleteEventArgs.DeletedEntities; foreach (var affectedUser in affectedUsers) - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", PerformingIp, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/delete", "delete user"); } @@ -199,7 +210,7 @@ namespace Umbraco.Core.Auditing { var performingUser = _userServiceInstance.GetUserById(identityArgs.PerformingUser); if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, 0, null, "umbraco/user/sign-in/login", "login success"); @@ -212,7 +223,7 @@ namespace Umbraco.Core.Auditing { var performingUser = _userServiceInstance.GetUserById(identityArgs.PerformingUser); if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, 0, null, "umbraco/user/sign-in/logout", "logout success"); @@ -228,9 +239,9 @@ namespace Umbraco.Core.Auditing if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); var affectedUser = _userServiceInstance.GetUserById(identityArgs.AffectedUser); if (affectedUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.AffectedUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/password/reset", "password reset"); } } @@ -244,9 +255,9 @@ namespace Umbraco.Core.Auditing if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); var affectedUser = _userServiceInstance.GetUserById(identityArgs.AffectedUser); if (affectedUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.AffectedUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/password/change", "password change"); } } @@ -258,7 +269,7 @@ namespace Umbraco.Core.Auditing if (identityArgs.PerformingUser < 0) return; var performingUser = _userServiceInstance.GetUserById(identityArgs.PerformingUser); if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, 0, null, "umbraco/user/sign-in/failed", "login failed"); @@ -273,9 +284,9 @@ namespace Umbraco.Core.Auditing if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); var affectedUser = _userServiceInstance.GetUserById(identityArgs.AffectedUser); if (affectedUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.AffectedUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/password/forgot/change", "password forgot/change"); } } @@ -289,9 +300,9 @@ namespace Umbraco.Core.Auditing if (performingUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.PerformingUser}"); var affectedUser = _userServiceInstance.GetUserById(identityArgs.AffectedUser); if (affectedUser == null) throw new InvalidOperationException($"No user found with id {identityArgs.AffectedUser}"); - _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" <{performingUser.Email}>", identityArgs.IpAddress, + _auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", identityArgs.IpAddress, DateTime.Now, - affectedUser.Id, $"User \"{affectedUser.Name}\" <{affectedUser.Email}>", + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/password/forgot/request", "password forgot/request"); } } diff --git a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs index 3dd6a86bd9..c58bb409b0 100644 --- a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs @@ -46,12 +46,8 @@ namespace Umbraco.Core.Auditing /// public string Username { get; private set; } - /// - /// Sets the properties on the event being raised, all parameters are optional except for the action being performed - /// - /// An action based on the AuditEvent enum - /// The client's IP address. This is usually automatically set but could be overridden if necessary - /// The Id of the user performing the action (if different from the user affected by the action) + [Obsolete("Use the method that has the affectedUser parameter instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1) { DateTimeUtc = DateTime.UtcNow; @@ -65,7 +61,29 @@ namespace Umbraco.Core.Auditing } /// - /// Creates an instance without a performing user (the id will be set to -1) + /// Default constructor + /// + /// + /// + /// + /// + /// + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) + { + DateTimeUtc = DateTime.UtcNow; + Action = action; + + IpAddress = ipAddress; + Comment = comment; + AffectedUser = affectedUser; + + PerformingUser = performingUser == -1 + ? GetCurrentRequestBackofficeUserId() + : performingUser; + } + + /// + /// Creates an instance without a performing or affected user (the id will be set to -1) /// /// /// diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index ebbb3fa80e..dc6228d19d 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -544,6 +544,28 @@ namespace Umbraco.Core.Models return false; } + /// + /// Returns both instance dirty properties and property type properties + /// + /// + public override IEnumerable GetDirtyProperties() + { + var instanceProperties = base.GetDirtyProperties(); + var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + + /// + /// Returns both instance dirty properties and property type properties + /// + /// + internal override IEnumerable GetPreviouslyDirtyProperties() + { + var instanceProperties = base.GetPreviouslyDirtyProperties(); + var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index e43c9ffb66..2591a3c73d 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Models.EntityBase : _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); } - internal virtual IEnumerable GetWereDirtyProperties() + internal virtual IEnumerable GetPreviouslyDirtyProperties() { return _lastPropertyChangedInfo == null ? Enumerable.Empty() diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 1ed11af93e..bd54f9c6f8 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -170,7 +170,19 @@ namespace Umbraco.Core.Models public string RawPasswordValue { get { return _rawPasswordValue; } - set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } + set + { + if (value == null) + { + //special case, this is used to ensure that the password is not updated when persisting, in this case + //we don't want to track changes either + _rawPasswordValue = null; + } + else + { + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); + } + } } /// @@ -628,4 +640,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 892de4a7e1..cd9a84758e 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -706,5 +706,6 @@ namespace Umbraco.Core.Models.Membership return _user.GetHashCode(); } } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 7042c63ec1..85b497d511 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -152,4 +152,4 @@ namespace Umbraco.Core.Models.Membership public int UserCount { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index 4969fa5f2f..2cb026ad3e 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -420,6 +420,33 @@ namespace Umbraco.Core.Security return await base.CheckPasswordAsync(user, password); } + public override Task ResetPasswordAsync(int userId, string token, string newPassword) + { + var result = base.ResetPasswordAsync(userId, token, newPassword); + if (result.Result.Succeeded) + RaisePasswordResetEvent(userId); + return result; + } + + /// + /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event + /// + /// + /// + /// + /// + /// + /// We use this because in the back office the only way an admin can change another user's password without first knowing their password + /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset + /// + public Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + { + var result = base.ResetPasswordAsync(userId, token, newPassword); + if (result.Result.Succeeded) + RaisePasswordChangedEvent(userId); + return result; + } + public override Task ChangePasswordAsync(int userId, string currentPassword, string newPassword) { var result = base.ChangePasswordAsync(userId, currentPassword, newPassword); @@ -554,27 +581,27 @@ namespace Umbraco.Core.Security internal void RaiseAccountLockedEvent(int userId) { - OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), userId)); + OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseAccountUnlockedEvent(int userId) { - OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), userId)); + OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseForgotPasswordRequestedEvent(int userId) { - OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), userId)); + OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseForgotPasswordChangedSuccessEvent(int userId) { - OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), userId)); + OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseLoginFailedEvent(int userId) { - OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), userId)); + OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseInvalidLoginAttemptEvent(string username) @@ -584,31 +611,33 @@ namespace Umbraco.Core.Security internal void RaiseLoginRequiresVerificationEvent(int userId) { - OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), userId)); + OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseLoginSuccessEvent(int userId) { - OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), userId)); + OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaiseLogoutSuccessEvent(int userId) { - OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), userId)); + OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, GetCurrentRequestIpAddress(), affectedUser: userId)); } internal void RaisePasswordChangedEvent(int userId) { - OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), userId)); + OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, GetCurrentRequestIpAddress(), affectedUser: userId)); } + //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. internal void RaisePasswordResetEvent(int userId) { - OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), userId)); + OnPasswordReset(new IdentityAuditEventArgs(AuditEvent.PasswordReset, GetCurrentRequestIpAddress(), affectedUser: userId)); } + internal void RaiseResetAccessFailedCountEvent(int userId) { - OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), userId)); + OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, GetCurrentRequestIpAddress(), affectedUser: userId)); } public static event EventHandler AccountLocked; diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 3d5dc628b7..82cec0385b 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -120,6 +120,10 @@ namespace Umbraco.Core.Services if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType)); if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails)); + //we need to truncate the data else we'll get SQL errors + affectedDetails = affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, 1024)); + eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, 1024)); + //validate the eventType - must contain a forward slash, no spaces, no special chars var eventTypeParts = eventType.ToCharArray(); if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false) diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 9ed65888d4..6e4326ad80 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -144,15 +144,12 @@ namespace Umbraco.Web.Editors { var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager(); - //raise the appropriate event + //raise the reset event + //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. if (data.Reset.HasValue && data.Reset.Value) { userMgr.RaisePasswordResetEvent(Security.CurrentUser.Id); } - else - { - userMgr.RaisePasswordChangedEvent(Security.CurrentUser.Id); - } //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 7ddf361d23..52f96934a0 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -93,7 +93,7 @@ namespace Umbraco.Web.Editors ? userMgr.GeneratePassword() : passwordModel.NewPassword; - var resetResult = await userMgr.ResetPasswordAsync(savingUser.Id, resetToken, newPass); + var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, newPass); if (resetResult.Succeeded == false) { @@ -166,6 +166,7 @@ namespace Umbraco.Web.Editors } //Are we resetting the password?? + //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) { var canReset = membershipProvider.CanResetPassword(_userService); @@ -291,4 +292,4 @@ namespace Umbraco.Web.Editors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 2e6cd5dbbf..a9359e4baf 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -561,18 +561,10 @@ namespace Umbraco.Web.Editors { var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); + //this will change the password and raise appropriate events var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager); if (passwordChangeResult.Success) { - var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager(); - - //raise the event - NOTE that the ChangePassword.Reset value here doesn't mean it's been 'reset', it means - //it's been changed by a back office user - if (userSave.ChangePassword.Reset.HasValue && userSave.ChangePassword.Reset.Value) - { - userMgr.RaisePasswordChangedEvent(intId.Result); - } - //need to re-get the user found = Services.UserService.GetUserById(intId.Result); } @@ -722,4 +714,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs index 39f92af5d2..f485002985 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs @@ -39,4 +39,4 @@ namespace Umbraco.Web.Models.Mapping } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 446a63ee99..737a66b7ce 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -28,10 +28,13 @@ namespace Umbraco.Web.Models.Mapping .ConstructUsing((UserGroupSave save) => new UserGroup() { CreateDate = DateTime.Now }) .IgnoreDeletableEntityCommonProperties() .ForMember(dest => dest.Id, map => map.Condition(source => GetIntId(source.Id) > 0)) - .ForMember(dest => dest.Id, map => map.MapFrom(source => GetIntId(source.Id))) - .ForMember(dest => dest.Permissions, map => map.MapFrom(source => source.DefaultPermissions)) + .ForMember(dest => dest.Id, map => map.MapFrom(source => GetIntId(source.Id))) + //.ForMember(dest => dest.Permissions, map => map.MapFrom(source => source.DefaultPermissions)) + .ForMember(dest => dest.Permissions, map => map.Ignore()) .AfterMap((save, userGroup) => { + userGroup.Permissions = save.DefaultPermissions; + userGroup.ClearAllowedSections(); foreach (var section in save.Sections) { @@ -466,4 +469,4 @@ namespace Umbraco.Web.Models.Mapping } } -} \ No newline at end of file +}