From 39385be346a41c8959e57a456d541e78e0b368dd Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Thu, 24 Oct 2024 10:46:58 +0200 Subject: [PATCH] Reintroduce BackOfficeUserManagerAuditer --- .../AuditLogBuilderExtensions.cs | 9 ++ .../Security/BackOfficeUserManagerAuditer.cs | 150 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/Umbraco.Cms.Api.Management/Security/BackOfficeUserManagerAuditer.cs diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs index a962ba558eb0..edca89e56bd7 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Security; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; namespace Umbraco.Cms.Api.Management.DependencyInjection; @@ -9,6 +11,13 @@ internal static class AuditLogBuilderExtensions internal static IUmbracoBuilder AddAuditLogs(this IUmbracoBuilder builder) { builder.Services.AddTransient(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); return builder; } diff --git a/src/Umbraco.Cms.Api.Management/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Cms.Api.Management/Security/BackOfficeUserManagerAuditer.cs new file mode 100644 index 000000000000..6b1cd567ee6e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Security/BackOfficeUserManagerAuditer.cs @@ -0,0 +1,150 @@ +using System.Globalization; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Security; + +/// +/// Binds to notifications to write audit logs for the +/// +internal sealed class BackOfficeUserManagerAuditer : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler +{ + private readonly IAuditService _auditService; + private readonly IUserService _userService; + + public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService) + { + _auditService = auditService; + _userService = userService; + } + + public void Handle(UserForgotPasswordChangedNotification notification) => + WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/password/forgot/change", + "password forgot/change"); + + public void Handle(UserForgotPasswordRequestedNotification notification) => + WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/password/forgot/request", + "password forgot/request"); + + public void Handle(UserLoginFailedNotification notification) => + WriteAudit( + notification.PerformingUserId, + "0", + notification.IpAddress, + "umbraco/user/sign-in/failed", + "login failed", + string.Empty); + + public void Handle(UserLoginSuccessNotification notification) + => WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/sign-in/login", + "login success"); + + public void Handle(UserLogoutSuccessNotification notification) + => WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/sign-in/logout", + "logout success"); + + public void Handle(UserPasswordChangedNotification notification) => + WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/password/change", + "password change"); + + public void Handle(UserPasswordResetNotification notification) => + WriteAudit( + notification.PerformingUserId, + notification.AffectedUserId, + notification.IpAddress, + "umbraco/user/password/reset", + "password reset"); + + private static string FormatEmail(IMembershipUser? user) => + user is null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>"; + + private void WriteAudit( + string performingId, + string? affectedId, + string ipAddress, + string eventType, + string eventDetails, + string? affectedDetails = null) + { + IUser? performingUser = null; + if (int.TryParse(performingId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)) + { + performingUser = _userService.GetUserById(asInt); + } + + var performingDetails = performingUser == null + ? $"User UNKNOWN:{performingId}" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + if (!int.TryParse(performingId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var performingIdAsInt)) + { + performingIdAsInt = 0; + } + + if (!int.TryParse(affectedId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var affectedIdAsInt)) + { + affectedIdAsInt = 0; + } + + WriteAudit(performingIdAsInt, performingDetails, affectedIdAsInt, ipAddress, eventType, eventDetails, affectedDetails); + } + + private void WriteAudit( + int performingId, + string performingDetails, + int affectedId, + string ipAddress, + string eventType, + string eventDetails, + string? affectedDetails = null) + { + if (affectedDetails == null) + { + IUser? affectedUser = _userService.GetUserById(affectedId); + affectedDetails = affectedUser == null + ? $"User UNKNOWN:{affectedId}" + : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; + } + + _auditService.Write( + performingId, + performingDetails, + ipAddress, + DateTime.UtcNow, + affectedId, + affectedDetails, + eventType, + eventDetails); + } +}