V14: Reintroduce BackOfficeUserManagerAuditer (#17349)

* Reintroduce BackOfficeUserManagerAuditer

* Cleanup legacy code

* Notify user logout on logout
This commit is contained in:
Nikolaj Geisle
2025-01-09 21:14:24 +01:00
committed by GitHub
parent 53a5813189
commit 58d6404ec8
3 changed files with 155 additions and 1 deletions

View File

@@ -185,9 +185,12 @@ public class BackOfficeController : SecurityControllerBase
[MapToApiVersion("1.0")]
public async Task<IActionResult> Signout(CancellationToken cancellationToken)
{
var userName = await GetUserNameFromAuthCookie();
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType);
var userName = cookieAuthResult.Principal?.Identity?.Name;
var userId = cookieAuthResult.Principal?.Identity?.GetUserId();
await _backOfficeSignInManager.SignOutAsync();
_backOfficeUserManager.NotifyLogoutSuccess(cookieAuthResult.Principal ?? User, userId);
_logger.LogInformation(
"User {UserName} from IP address {RemoteIpAddress} has logged out",

View File

@@ -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<IAuditLogPresentationFactory, AuditLogPresentationFactory>();
builder.AddNotificationHandler<UserLoginSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserLogoutSuccessNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserLoginFailedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserForgotPasswordRequestedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserForgotPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserPasswordChangedNotification, BackOfficeUserManagerAuditer>();
builder.AddNotificationHandler<UserPasswordResetNotification, BackOfficeUserManagerAuditer>();
return builder;
}

View File

@@ -0,0 +1,142 @@
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;
/// <summary>
/// Binds to notifications to write audit logs for the <see cref="BackOfficeUserManager" />
/// </summary>
internal sealed class BackOfficeUserManagerAuditer :
INotificationHandler<UserLoginSuccessNotification>,
INotificationHandler<UserLogoutSuccessNotification>,
INotificationHandler<UserLoginFailedNotification>,
INotificationHandler<UserForgotPasswordRequestedNotification>,
INotificationHandler<UserForgotPasswordChangedNotification>,
INotificationHandler<UserPasswordChangedNotification>,
INotificationHandler<UserPasswordResetNotification>
{
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,
null,
notification.IpAddress,
"umbraco/user/sign-in/failed",
"login failed");
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)
{
int? performingIdAsInt = ParseUserId(performingId);
int? affectedIdAsInt = ParseUserId(affectedId);
WriteAudit(performingIdAsInt, affectedIdAsInt, ipAddress, eventType, eventDetails);
}
private static int? ParseUserId(string? id)
=> int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var isAsInt) ? isAsInt : null;
private void WriteAudit(
int? performingId,
int? affectedId,
string ipAddress,
string eventType,
string eventDetails)
{
var performingDetails = "User UNKNOWN:0";
if (performingId.HasValue)
{
IUser? performingUser = _userService.GetUserById(performingId.Value);
performingDetails = performingUser is null
? $"User UNKNOWN:{performingId.Value}"
: $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}";
}
var affectedDetails = "User UNKNOWN:0";
if (affectedId.HasValue)
{
IUser? affectedUser = _userService.GetUserById(affectedId.Value);
affectedDetails = affectedUser is null
? $"User UNKNOWN:{affectedId.Value}"
: $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}";
}
_auditService.Write(
performingId ?? 0,
performingDetails,
ipAddress,
DateTime.UtcNow,
affectedId ?? 0,
affectedDetails,
eventType,
eventDetails);
}
}