From 4671d9d23b5217161abdb4bf5c76dc4caeecf231 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Dec 2020 17:51:52 +1100 Subject: [PATCH] moves the back office user auditing logic --- .../BackOffice/IBackOfficeUserManager.cs | 13 ++ .../BackOfficeApplicationBuilderExtensions.cs | 11 ++ .../BackOfficeServiceCollectionExtensions.cs | 1 + .../Security/BackOfficeUserManager.cs | 1 + .../Security/BackOfficeUserManagerAuditer.cs | 163 ++++++++++++++++++ .../BackOfficeUserAuditEventsComponent.cs | 163 ------------------ .../BackOfficeUserAuditEventsComposer.cs | 7 - .../IUmbracoBackOfficeTwoFactorOptions.cs | 12 -- src/Umbraco.Web/Umbraco.Web.csproj | 3 - 9 files changed, 189 insertions(+), 185 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs delete mode 100644 src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs delete mode 100644 src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs delete mode 100644 src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index 664c957f57..47de2e3956 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -320,5 +320,18 @@ namespace Umbraco.Core.BackOffice UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); bool HasSendingUserInviteEventHandler { get; } + + + event EventHandler AccountLocked; + event EventHandler AccountUnlocked; + event EventHandler ForgotPasswordRequested; + event EventHandler ForgotPasswordChangedSuccess; + event EventHandler LoginFailed; + event EventHandler LoginRequiresVerification; + event EventHandler LoginSuccess; + event EventHandler LogoutSuccess; + event EventHandler PasswordChanged; + event EventHandler PasswordReset; + event EventHandler ResetAccessFailedCount; } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index d32351fdc6..a097ead4a1 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -2,8 +2,10 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp.Web.DependencyInjection; +using Umbraco.Core.BackOffice; using Umbraco.Web.BackOffice.Middleware; using Umbraco.Web.BackOffice.Routing; +using Umbraco.Web.Common.Security; namespace Umbraco.Extensions { @@ -30,6 +32,8 @@ namespace Umbraco.Extensions { if (app == null) throw new ArgumentNullException(nameof(app)); + app.UseBackOfficeUserManagerAuditing(); + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? app.UseImageSharp(); @@ -64,5 +68,12 @@ namespace Umbraco.Extensions return app; } + + private static IApplicationBuilder UseBackOfficeUserManagerAuditing(this IApplicationBuilder app) + { + var auditer = app.ApplicationServices.GetRequiredService(); + auditer.Start(); + return app; + } } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 8d3223a79f..ac844b0340 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -64,6 +64,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 3501242eb5..30deb3d436 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Common.Security { + public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager { public BackOfficeUserManager( diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs new file mode 100644 index 0000000000..afa50ee8cd --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -0,0 +1,163 @@ +using System; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Compose; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Common.Security +{ + /// + /// Binds to events to write audit logs for the + /// + internal class BackOfficeUserManagerAuditer : IDisposable + { + private readonly IBackOfficeUserManager _backOfficeUserManager; + private readonly IAuditService _auditService; + private readonly IUserService _userService; + private readonly GlobalSettings _globalSettings; + private bool _disposedValue; + + public BackOfficeUserManagerAuditer(IBackOfficeUserManager backOfficeUserManager, IAuditService auditService, IUserService userService, GlobalSettings globalSettings) + { + _backOfficeUserManager = backOfficeUserManager; + _auditService = auditService; + _userService = userService; + _globalSettings = globalSettings; + } + + /// + /// Binds to events to start auditing + /// + public void Start() + { + // NOTE: This was migrated as-is from v8 including these missing entries + //_backOfficeUserManager.AccountLocked += ; + //_backOfficeUserManager.AccountUnlocked += ; + _backOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; + _backOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; + _backOfficeUserManager.LoginFailed += OnLoginFailed; + //_backOfficeUserManager.LoginRequiresVerification += ; + _backOfficeUserManager.LoginSuccess += OnLoginSuccess; + _backOfficeUserManager.LogoutSuccess += OnLogoutSuccess; + _backOfficeUserManager.PasswordChanged += OnPasswordChanged; + _backOfficeUserManager.PasswordReset += OnPasswordReset; + //_backOfficeUserManager.ResetAccessFailedCount += ; + } + + private IUser GetPerformingUser(int userId) + { + var found = userId >= 0 ? _userService.GetUserById(userId) : null; + return found ?? AuditEventsComponent.UnknownUser(_globalSettings); + } + + private static string FormatEmail(IMembershipUser user) + { + return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + } + + private void OnLoginSuccess(object sender, IdentityAuditEventArgs args) + { + var performingUser = GetPerformingUser(args.PerformingUser); + WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/login", "login success"); + } + + private void OnLogoutSuccess(object sender, IdentityAuditEventArgs args) + { + var performingUser = GetPerformingUser(args.PerformingUser); + WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/logout", "logout success"); + } + + private void OnPasswordReset(object sender, IdentityAuditEventArgs args) + { + WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); + } + + private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) + { + WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); + } + + private void OnLoginFailed(object sender, IdentityAuditEventArgs args) + { + WriteAudit(args.PerformingUser, 0, args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); + } + + private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) + { + WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); + } + + private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) + { + WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); + } + + private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + { + var performingUser = _userService.GetUserById(performingId); + + var performingDetails = performingUser == null + ? $"User UNKNOWN:{performingId}" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); + } + + private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) + { + var performingDetails = performingUser == null + ? $"User UNKNOWN" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); + } + + private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + { + if (affectedDetails == null) + { + var 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); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + //_backOfficeUserManager.AccountLocked -= ; + //_backOfficeUserManager.AccountUnlocked -= ; + _backOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + _backOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + _backOfficeUserManager.LoginFailed -= OnLoginFailed; + //_backOfficeUserManager.LoginRequiresVerification -= ; + _backOfficeUserManager.LoginSuccess -= OnLoginSuccess; + _backOfficeUserManager.LogoutSuccess -= OnLogoutSuccess; + _backOfficeUserManager.PasswordChanged -= OnPasswordChanged; + _backOfficeUserManager.PasswordReset -= OnPasswordReset; + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs deleted file mode 100644 index d8210556fd..0000000000 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Compose; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Security; - -namespace Umbraco.Web.Compose -{ - // TODO: Move to netcore - public sealed class BackOfficeUserAuditEventsComponent : IComponent - { - private readonly IAuditService _auditService; - private readonly IUserService _userService; - private readonly GlobalSettings _globalSettings; - - public BackOfficeUserAuditEventsComponent(IAuditService auditService, IUserService userService, GlobalSettings globalSettings) - { - _auditService = auditService; - _userService = userService; - _globalSettings = globalSettings; - } - - public void Initialize() - { - //BackOfficeUserManager.AccountLocked += ; - //BackOfficeUserManager.AccountUnlocked += ; - //BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - //BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification += ; - //BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; - //BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; - //BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; - //BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount += ; - } - - public void Terminate() - { - //BackOfficeUserManager.AccountLocked -= ; - //BackOfficeUserManager.AccountUnlocked -= ; - //BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; - //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; - //BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; - //BackOfficeUserManager.LoginRequiresVerification -= ; - //BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; - //BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; - //BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; - //BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; - //BackOfficeUserManager.ResetAccessFailedCount -= ; - } - - private IUser GetPerformingUser(int userId) - { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? AuditEventsComponent.UnknownUser(_globalSettings); - } - - private static string FormatEmail(IMembershipUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } - - private void OnLoginSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success"); - } - } - - private void OnLogoutSuccess(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs) - { - var performingUser = GetPerformingUser(identityArgs.PerformingUser); - WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success"); - } - } - - private void OnPasswordReset(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset"); - } - } - - private void OnPasswordChanged(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change"); - } - } - - private void OnLoginFailed(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - } - } - - private void OnForgotPasswordChange(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - } - } - - private void OnForgotPasswordRequest(object sender, EventArgs args) - { - if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) - { - WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - } - } - - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - var performingUser = _userService.GetUserById(performingId); - - var performingDetails = performingUser == null - ? $"User UNKNOWN:{performingId}" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); - } - - private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) - { - var performingDetails = performingUser == null - ? $"User UNKNOWN" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); - } - - private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - if (affectedDetails == null) - { - var 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); - } - } -} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs deleted file mode 100644 index 38065f98ce..0000000000 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Compose -{ - public sealed class BackOfficeUserAuditEventsComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs deleted file mode 100644 index 0b43342594..0000000000 --- a/src/Umbraco.Web/Security/IUmbracoBackOfficeTwoFactorOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Owin; - -namespace Umbraco.Web.Security -{ - /// - /// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication - /// - public interface IUmbracoBackOfficeTwoFactorOptions - { - string GetTwoFactorView(IOwinContext owinContext, IUmbracoContext umbracoContext, string username); - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7a2ec6e0f7..8316c20418 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -135,8 +135,6 @@ - - @@ -212,7 +210,6 @@ -