moves the back office user auditing logic
This commit is contained in:
@@ -320,5 +320,18 @@ namespace Umbraco.Core.BackOffice
|
||||
UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser);
|
||||
|
||||
bool HasSendingUserInviteEventHandler { get; }
|
||||
|
||||
|
||||
event EventHandler<IdentityAuditEventArgs> AccountLocked;
|
||||
event EventHandler<IdentityAuditEventArgs> AccountUnlocked;
|
||||
event EventHandler<IdentityAuditEventArgs> ForgotPasswordRequested;
|
||||
event EventHandler<IdentityAuditEventArgs> ForgotPasswordChangedSuccess;
|
||||
event EventHandler<IdentityAuditEventArgs> LoginFailed;
|
||||
event EventHandler<IdentityAuditEventArgs> LoginRequiresVerification;
|
||||
event EventHandler<IdentityAuditEventArgs> LoginSuccess;
|
||||
event EventHandler<SignOutAuditEventArgs> LogoutSuccess;
|
||||
event EventHandler<IdentityAuditEventArgs> PasswordChanged;
|
||||
event EventHandler<IdentityAuditEventArgs> PasswordReset;
|
||||
event EventHandler<IdentityAuditEventArgs> ResetAccessFailedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BackOfficeUserManagerAuditer>();
|
||||
auditer.Start();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Umbraco.Extensions
|
||||
services.TryAddScoped<IIpResolver, AspNetCoreIpResolver>();
|
||||
services.TryAddSingleton<IBackOfficeExternalLoginProviders, BackOfficeExternalLoginProviders>();
|
||||
services.TryAddSingleton<IBackOfficeTwoFactorOptions, NoopBackOfficeTwoFactorOptions>();
|
||||
services.TryAddSingleton<BackOfficeUserManagerAuditer>();
|
||||
|
||||
/*
|
||||
* IdentityBuilderExtensions.AddUserManager adds UserManager<BackOfficeIdentityUser> to service collection
|
||||
|
||||
@@ -20,6 +20,7 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
|
||||
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>, IBackOfficeUserManager
|
||||
{
|
||||
public BackOfficeUserManager(
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds to events to write audit logs for the <see cref="IBackOfficeUserManager"/>
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to events to start auditing
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
public sealed class BackOfficeUserAuditEventsComposer : ComponentComposer<BackOfficeUserAuditEventsComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Microsoft.Owin;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to display a custom view in the back office if developers choose to implement their own custom 2 factor authentication
|
||||
/// </summary>
|
||||
public interface IUmbracoBackOfficeTwoFactorOptions
|
||||
{
|
||||
string GetTwoFactorView(IOwinContext owinContext, IUmbracoContext umbracoContext, string username);
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,6 @@
|
||||
<Compile Include="AspNet\AspNetUserAgentProvider.cs" />
|
||||
<Compile Include="AspNet\FrameworkMarchal.cs" />
|
||||
<Compile Include="AspNet\AspNetUmbracoApplicationLifetime.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
|
||||
<Compile Include="HttpContextExtensions.cs" />
|
||||
<Compile Include="Macros\MemberUserKeyProvider.cs" />
|
||||
<Compile Include="Mvc\IRenderController.cs" />
|
||||
@@ -212,7 +210,6 @@
|
||||
<Compile Include="Security\ForceRenewalCookieAuthenticationHandler.cs" />
|
||||
<Compile Include="Security\ForceRenewalCookieAuthenticationMiddleware.cs" />
|
||||
<Compile Include="Security\GetUserSecondsMiddleWare.cs" />
|
||||
<Compile Include="Security\IUmbracoBackOfficeTwoFactorOptions.cs" />
|
||||
<Compile Include="Security\WebAuthExtensions.cs" />
|
||||
<Compile Include="WebAssets\CDF\UmbracoClientDependencyLoader.cs" />
|
||||
<Compile Include="UmbracoDefaultOwinStartup.cs" />
|
||||
|
||||
Reference in New Issue
Block a user