2020-12-03 19:36:38 +11:00
using System ;
2020-05-14 22:21:19 +01:00
using System.Collections.Generic ;
2020-05-21 16:33:24 +10:00
using System.Security.Principal ;
2020-05-14 22:21:19 +01:00
using System.Threading ;
using System.Threading.Tasks ;
2020-12-01 17:24:23 +11:00
using Microsoft.AspNetCore.Http ;
2020-05-14 22:21:19 +01:00
using Microsoft.AspNetCore.Identity ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
2020-12-01 17:24:23 +11:00
using Umbraco.Core ;
using Umbraco.Core.BackOffice ;
2020-05-14 22:21:19 +01:00
using Umbraco.Core.Configuration ;
2020-08-20 22:18:50 +01:00
using Umbraco.Core.Configuration.Models ;
2020-10-23 14:18:53 +11:00
using Umbraco.Core.Models.Membership ;
2020-05-14 22:21:19 +01:00
using Umbraco.Core.Security ;
2020-05-21 16:33:24 +10:00
using Umbraco.Extensions ;
2020-05-14 22:21:19 +01:00
using Umbraco.Net ;
2020-10-23 14:18:53 +11:00
using Umbraco.Web.Models.ContentEditing ;
2020-05-14 22:21:19 +01:00
2020-12-01 17:24:23 +11:00
namespace Umbraco.Web.Common.Security
2020-05-14 22:21:19 +01:00
{
2020-12-01 17:51:52 +11:00
2020-09-22 14:44:41 +02:00
public class BackOfficeUserManager : BackOfficeUserManager < BackOfficeIdentityUser > , IBackOfficeUserManager
2020-05-14 22:21:19 +01:00
{
2020-05-21 15:43:33 +10:00
public BackOfficeUserManager (
IIpResolver ipResolver ,
IUserStore < BackOfficeIdentityUser > store ,
IOptions < BackOfficeIdentityOptions > optionsAccessor ,
IPasswordHasher < BackOfficeIdentityUser > passwordHasher ,
IEnumerable < IUserValidator < BackOfficeIdentityUser > > userValidators ,
IEnumerable < IPasswordValidator < BackOfficeIdentityUser > > passwordValidators ,
BackOfficeLookupNormalizer keyNormalizer ,
BackOfficeIdentityErrorDescriber errors ,
IServiceProvider services ,
2020-12-01 17:24:23 +11:00
IHttpContextAccessor httpContextAccessor ,
2020-06-22 10:08:08 +02:00
ILogger < UserManager < BackOfficeIdentityUser > > logger ,
2020-08-20 22:18:50 +01:00
IOptions < UserPasswordConfigurationSettings > passwordConfiguration )
2020-12-01 17:24:23 +11:00
: base ( ipResolver , store , optionsAccessor , passwordHasher , userValidators , passwordValidators , keyNormalizer , errors , services , httpContextAccessor , logger , passwordConfiguration )
2020-05-14 22:21:19 +01:00
{
}
}
public class BackOfficeUserManager < T > : UserManager < T >
where T : BackOfficeIdentityUser
{
private PasswordGenerator _passwordGenerator ;
2020-12-01 17:24:23 +11:00
private readonly IHttpContextAccessor _httpContextAccessor ;
2020-06-22 10:08:08 +02:00
2020-05-14 22:21:19 +01:00
public BackOfficeUserManager (
IIpResolver ipResolver ,
IUserStore < T > store ,
2020-05-21 15:43:33 +10:00
IOptions < BackOfficeIdentityOptions > optionsAccessor ,
2020-05-17 07:56:59 +01:00
IPasswordHasher < T > passwordHasher ,
2020-05-14 22:21:19 +01:00
IEnumerable < IUserValidator < T > > userValidators ,
IEnumerable < IPasswordValidator < T > > passwordValidators ,
2020-05-21 15:43:33 +10:00
BackOfficeLookupNormalizer keyNormalizer ,
BackOfficeIdentityErrorDescriber errors ,
2020-05-14 22:21:19 +01:00
IServiceProvider services ,
2020-12-01 17:24:23 +11:00
IHttpContextAccessor httpContextAccessor ,
2020-06-22 10:08:08 +02:00
ILogger < UserManager < T > > logger ,
2020-08-20 22:18:50 +01:00
IOptions < UserPasswordConfigurationSettings > passwordConfiguration )
2020-05-17 07:56:59 +01:00
: base ( store , optionsAccessor , passwordHasher , userValidators , passwordValidators , keyNormalizer , errors , services , logger )
2020-05-14 22:21:19 +01:00
{
IpResolver = ipResolver ? ? throw new ArgumentNullException ( nameof ( ipResolver ) ) ;
2020-12-01 17:24:23 +11:00
_httpContextAccessor = httpContextAccessor ;
2020-08-20 22:18:50 +01:00
PasswordConfiguration = passwordConfiguration . Value ? ? throw new ArgumentNullException ( nameof ( passwordConfiguration ) ) ;
2020-05-14 22:21:19 +01:00
}
2020-06-03 12:47:40 +10:00
// We don't support an IUserClaimStore and don't need to (at least currently)
2020-05-14 22:21:19 +01:00
public override bool SupportsUserClaim = > false ;
2020-06-22 10:08:08 +02:00
2020-06-03 12:47:40 +10:00
// It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
2020-05-14 22:21:19 +01:00
public override bool SupportsQueryableUsers = > false ;
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// </summary>
public override bool SupportsUserTwoFactor = > false ;
2020-06-03 12:47:40 +10:00
// We haven't needed to support this yet, though might be necessary for 2FA
2020-05-14 22:21:19 +01:00
public override bool SupportsUserPhoneNumber = > false ;
2020-06-03 12:47:40 +10:00
2020-05-21 15:43:33 +10:00
/// <summary>
/// Replace the underlying options property with our own strongly typed version
/// </summary>
public new BackOfficeIdentityOptions Options
{
get = > ( BackOfficeIdentityOptions ) base . Options ;
set = > base . Options = value ;
}
2020-05-14 22:21:19 +01:00
/// <summary>
/// Used to validate a user's session
/// </summary>
2020-12-03 20:30:35 +11:00
/// <param name="userId">The user id</param>
/// <param name="sessionId">The sesion id</param>
/// <returns>True if the sesion is valid, else false</returns>
2020-05-14 22:21:19 +01:00
public virtual async Task < bool > ValidateSessionIdAsync ( string userId , string sessionId )
{
var userSessionStore = Store as IUserSessionStore < T > ;
2020-12-03 20:30:35 +11:00
// if this is not set, for backwards compat (which would be super rare), we'll just approve it
if ( userSessionStore = = null )
{
return true ;
}
2020-05-14 22:21:19 +01:00
return await userSessionStore . ValidateSessionIdAsync ( userId , sessionId ) ;
}
/// <summary>
/// This will determine which password hasher to use based on what is defined in config
/// </summary>
2020-12-03 20:30:35 +11:00
/// <param name="passwordConfiguration">The <see cref="IPasswordConfiguration"/></param>
/// <returns>An <see cref="IPasswordHasher{T}"/></returns>
protected virtual IPasswordHasher < T > GetDefaultPasswordHasher ( IPasswordConfiguration passwordConfiguration ) = > new PasswordHasher < T > ( ) ;
2020-06-22 10:08:08 +02:00
2020-05-14 22:21:19 +01:00
/// <summary>
/// Gets/sets the default back office user password checker
/// </summary>
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get ; set ; }
2020-05-18 12:06:26 +01:00
public IPasswordConfiguration PasswordConfiguration { get ; protected set ; }
2020-05-14 22:21:19 +01:00
public IIpResolver IpResolver { get ; }
/// <summary>
/// Helper method to generate a password for a user based on the current password validator
/// </summary>
2020-12-03 20:30:35 +11:00
/// <returns>The generated password</returns>
2020-05-14 22:21:19 +01:00
public string GeneratePassword ( )
{
2020-12-03 20:30:35 +11:00
if ( _passwordGenerator = = null )
{
_passwordGenerator = new PasswordGenerator ( PasswordConfiguration ) ;
}
2020-05-14 22:21:19 +01:00
var password = _passwordGenerator . GeneratePassword ( ) ;
return password ;
}
/// <summary>
/// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date
/// </summary>
2020-12-03 19:36:38 +11:00
/// <param name="user">The user</param>
/// <returns>True if the user is locked out, else false</returns>
2020-05-14 22:21:19 +01:00
/// <remarks>
/// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values
/// </remarks>
public override async Task < bool > IsLockedOutAsync ( T user )
{
2020-12-03 19:36:38 +11:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
if ( user . IsApproved = = false )
{
return true ;
}
2020-05-14 22:21:19 +01:00
return await base . IsLockedOutAsync ( user ) ;
}
/// <summary>
/// Logic used to validate a username and password
/// </summary>
2020-12-03 20:30:35 +11:00
/// <inheritdoc />
2020-05-14 22:21:19 +01:00
/// <remarks>
/// By default this uses the standard ASP.Net Identity approach which is:
/// * Get password store
/// * Call VerifyPasswordAsync with the password store + user + password
/// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password
///
/// In some cases people want simple custom control over the username/password check, for simplicity
/// sake, developers would like the users to simply validate against an LDAP directory but the user
/// data remains stored inside of Umbraco.
/// See: http://issues.umbraco.org/issue/U4-7032 for the use cases.
///
/// We've allowed this check to be overridden with a simple callback so that developers don't actually
/// have to implement/override this class.
/// </remarks>
public override async Task < bool > CheckPasswordAsync ( T user , string password )
{
if ( BackOfficeUserPasswordChecker ! = null )
{
2020-12-03 20:30:35 +11:00
BackOfficeUserPasswordCheckerResult result = await BackOfficeUserPasswordChecker . CheckPasswordAsync ( user , password ) ;
2020-05-14 22:21:19 +01:00
if ( user . HasIdentity = = false )
{
return false ;
}
2020-12-03 20:30:35 +11:00
// if the result indicates to not fallback to the default, then return true if the credentials are valid
2020-05-14 22:21:19 +01:00
if ( result ! = BackOfficeUserPasswordCheckerResult . FallbackToDefaultChecker )
{
return result = = BackOfficeUserPasswordCheckerResult . ValidCredentials ;
}
}
2020-12-03 20:30:35 +11:00
// we cannot proceed if the user passed in does not have an identity
2020-05-14 22:21:19 +01:00
if ( user . HasIdentity = = false )
2020-12-03 20:30:35 +11:00
{
2020-05-14 22:21:19 +01:00
return false ;
2020-12-03 20:30:35 +11:00
}
2020-05-14 22:21:19 +01:00
2020-12-03 20:30:35 +11:00
// use the default behavior
2020-05-14 22:21:19 +01:00
return await base . CheckPasswordAsync ( user , password ) ;
}
2020-06-22 10:08:08 +02:00
2020-05-14 22:21:19 +01:00
/// <summary>
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
/// </summary>
2020-12-03 20:30:35 +11:00
/// <param name="userId">The userId</param>
/// <param name="token">The reset password token</param>
/// <param name="newPassword">The new password to set it to</param>
/// <returns>The <see cref="IdentityResult"/></returns>
2020-05-14 22:21:19 +01:00
/// <remarks>
/// 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
/// </remarks>
public async Task < IdentityResult > ChangePasswordWithResetAsync ( int userId , string token , string newPassword )
{
2020-12-03 20:30:35 +11:00
T user = await FindByIdAsync ( userId . ToString ( ) ) ;
if ( user = = null )
{
throw new InvalidOperationException ( "Could not find user" ) ;
}
2020-05-14 22:21:19 +01:00
2020-12-03 20:30:35 +11:00
IdentityResult result = await base . ResetPasswordAsync ( user , token , newPassword ) ;
2020-05-21 16:33:24 +10:00
if ( result . Succeeded )
2020-12-01 17:24:23 +11:00
{
RaisePasswordChangedEvent ( _httpContextAccessor . HttpContext ? . User , userId ) ;
}
2020-12-03 20:30:35 +11:00
2020-05-14 22:21:19 +01:00
return result ;
}
public override async Task < IdentityResult > ChangePasswordAsync ( T user , string currentPassword , string newPassword )
{
2020-12-03 20:30:35 +11:00
IdentityResult result = await base . ChangePasswordAsync ( user , currentPassword , newPassword ) ;
2020-05-21 16:33:24 +10:00
if ( result . Succeeded )
2020-12-01 17:24:23 +11:00
{
RaisePasswordChangedEvent ( _httpContextAccessor . HttpContext ? . User , user . Id ) ;
}
2020-05-14 22:21:19 +01:00
return result ;
}
2020-06-22 10:08:08 +02:00
2020-05-14 22:21:19 +01:00
/// <summary>
/// Override to determine how to hash the password
/// </summary>
2020-12-03 20:30:35 +11:00
/// <inheritdoc/>
2020-05-14 22:21:19 +01:00
protected override async Task < IdentityResult > UpdatePasswordHash ( T user , string newPassword , bool validatePassword )
{
user . LastPasswordChangeDateUtc = DateTime . UtcNow ;
if ( validatePassword )
{
2020-12-03 20:30:35 +11:00
IdentityResult validate = await ValidatePasswordAsync ( user , newPassword ) ;
2020-05-14 22:21:19 +01:00
if ( ! validate . Succeeded )
{
return validate ;
}
}
var passwordStore = Store as IUserPasswordStore < T > ;
2020-12-03 20:30:35 +11:00
if ( passwordStore = = null )
{
throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserPasswordStore < > ) ) ;
}
2020-05-14 22:21:19 +01:00
var hash = newPassword ! = null ? PasswordHasher . HashPassword ( user , newPassword ) : null ;
await passwordStore . SetPasswordHashAsync ( user , hash , CancellationToken ) ;
await UpdateSecurityStampInternal ( user ) ;
return IdentityResult . Success ;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
private async Task UpdateSecurityStampInternal ( T user )
{
2020-12-03 20:30:35 +11:00
if ( SupportsUserSecurityStamp = = false )
{
return ;
}
2020-05-14 22:21:19 +01:00
await GetSecurityStore ( ) . SetSecurityStampAsync ( user , NewSecurityStamp ( ) , CancellationToken . None ) ;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
private IUserSecurityStampStore < T > GetSecurityStore ( )
{
var store = Store as IUserSecurityStampStore < T > ;
2020-12-03 20:30:35 +11:00
if ( store = = null )
{
throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserSecurityStampStore < > ) ) ;
}
2020-05-14 22:21:19 +01:00
return store ;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
2020-12-03 20:30:35 +11:00
private static string NewSecurityStamp ( ) = > Guid . NewGuid ( ) . ToString ( ) ;
2020-05-14 22:21:19 +01:00
2020-12-03 20:30:35 +11:00
/// <inheritdoc/>
2020-05-14 22:21:19 +01:00
public override async Task < IdentityResult > SetLockoutEndDateAsync ( T user , DateTimeOffset ? lockoutEnd )
{
2020-12-03 20:30:35 +11:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-05-14 22:21:19 +01:00
2020-12-03 20:30:35 +11:00
IdentityResult result = await base . SetLockoutEndDateAsync ( user , lockoutEnd ) ;
2020-05-14 22:21:19 +01:00
// The way we unlock is by setting the lockoutEnd date to the current datetime
if ( result . Succeeded & & lockoutEnd > = DateTimeOffset . UtcNow )
{
2020-12-01 17:24:23 +11:00
RaiseAccountLockedEvent ( _httpContextAccessor . HttpContext ? . User , user . Id ) ;
2020-05-14 22:21:19 +01:00
}
else
{
2020-12-01 17:24:23 +11:00
RaiseAccountUnlockedEvent ( _httpContextAccessor . HttpContext ? . User , user . Id ) ;
2020-12-03 20:30:35 +11:00
// Resets the login attempt fails back to 0 when unlock is clicked
2020-05-14 22:21:19 +01:00
await ResetAccessFailedCountAsync ( user ) ;
}
return result ;
}
2020-12-03 20:30:35 +11:00
/// <inheritdoc/>
2020-05-14 22:21:19 +01:00
public override async Task < IdentityResult > ResetAccessFailedCountAsync ( T user )
{
2020-12-03 20:30:35 +11:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-05-14 22:21:19 +01:00
var lockoutStore = ( IUserLockoutStore < T > ) Store ;
var accessFailedCount = await GetAccessFailedCountAsync ( user ) ;
if ( accessFailedCount = = 0 )
2020-12-03 20:30:35 +11:00
{
2020-05-14 22:21:19 +01:00
return IdentityResult . Success ;
2020-12-03 20:30:35 +11:00
}
2020-05-14 22:21:19 +01:00
await lockoutStore . ResetAccessFailedCountAsync ( user , CancellationToken . None ) ;
2020-12-03 20:30:35 +11:00
// raise the event now that it's reset
2020-12-01 17:24:23 +11:00
RaiseResetAccessFailedCountEvent ( _httpContextAccessor . HttpContext ? . User , user . Id ) ;
2020-05-14 22:21:19 +01:00
return await UpdateAsync ( user ) ;
}
/// <summary>
/// Overrides the Microsoft ASP.NET user management method
/// </summary>
2020-12-03 20:30:35 +11:00
/// <inheritdoc/>
2020-05-14 22:21:19 +01:00
public override async Task < IdentityResult > AccessFailedAsync ( T user )
{
2020-12-03 20:30:35 +11:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-05-14 22:21:19 +01:00
var lockoutStore = Store as IUserLockoutStore < T > ;
2020-12-03 20:30:35 +11:00
if ( lockoutStore = = null )
{
throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserLockoutStore < > ) ) ;
}
2020-05-14 22:21:19 +01:00
var count = await lockoutStore . IncrementAccessFailedCountAsync ( user , CancellationToken . None ) ;
if ( count > = Options . Lockout . MaxFailedAccessAttempts )
{
2020-12-03 20:30:35 +11:00
await lockoutStore . SetLockoutEndDateAsync ( user , DateTimeOffset . UtcNow . Add ( Options . Lockout . DefaultLockoutTimeSpan ) , CancellationToken . None ) ;
// NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0
// here we are persisting the value for the back office
2020-05-14 22:21:19 +01:00
}
2020-12-03 20:30:35 +11:00
IdentityResult result = await UpdateAsync ( user ) ;
2020-05-14 22:21:19 +01:00
2020-12-03 20:30:35 +11:00
// Slightly confusing: this will return a Success if we successfully update the AccessFailed count
2020-05-21 16:33:24 +10:00
if ( result . Succeeded )
{
2020-12-01 17:24:23 +11:00
RaiseLoginFailedEvent ( _httpContextAccessor . HttpContext ? . User , user . Id ) ;
2020-05-21 16:33:24 +10:00
}
2020-05-14 22:21:19 +01:00
return result ;
}
2020-10-23 14:18:53 +11:00
private int GetCurrentUserId ( IPrincipal currentUser )
2020-05-14 22:21:19 +01:00
{
2020-12-03 20:30:35 +11:00
UmbracoBackOfficeIdentity umbIdentity = currentUser ? . GetUmbracoIdentity ( ) ;
2020-12-01 17:24:23 +11:00
var currentUserId = umbIdentity ? . GetUserId < int? > ( ) ? ? Core . Constants . Security . SuperUserId ;
2020-10-23 14:18:53 +11:00
return currentUserId ;
}
2020-12-03 20:30:35 +11:00
2020-10-23 14:18:53 +11:00
private IdentityAuditEventArgs CreateArgs ( AuditEvent auditEvent , IPrincipal currentUser , int affectedUserId , string affectedUsername )
{
var currentUserId = GetCurrentUserId ( currentUser ) ;
2020-05-21 16:33:24 +10:00
var ip = IpResolver . GetCurrentRequestIpAddress ( ) ;
return new IdentityAuditEventArgs ( auditEvent , ip , currentUserId , string . Empty , affectedUserId , affectedUsername ) ;
2020-05-27 18:27:49 +10:00
}
2020-12-03 20:30:35 +11:00
2020-05-27 18:27:49 +10:00
private IdentityAuditEventArgs CreateArgs ( AuditEvent auditEvent , BackOfficeIdentityUser currentUser , int affectedUserId , string affectedUsername )
{
var currentUserId = currentUser . Id ;
var ip = IpResolver . GetCurrentRequestIpAddress ( ) ;
return new IdentityAuditEventArgs ( auditEvent , ip , currentUserId , string . Empty , affectedUserId , affectedUsername ) ;
}
2020-05-14 22:21:19 +01:00
2020-12-01 17:24:23 +11:00
// TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager,
// lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring
public void RaiseAccountLockedEvent ( IPrincipal currentUser , int userId ) = > OnAccountLocked ( CreateArgs ( AuditEvent . AccountLocked , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-05-21 16:33:24 +10:00
public void RaiseAccountUnlockedEvent ( IPrincipal currentUser , int userId ) = > OnAccountUnlocked ( CreateArgs ( AuditEvent . AccountUnlocked , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-05-21 16:33:24 +10:00
public void RaiseForgotPasswordRequestedEvent ( IPrincipal currentUser , int userId ) = > OnForgotPasswordRequested ( CreateArgs ( AuditEvent . ForgotPasswordRequested , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-05-21 16:33:24 +10:00
public void RaiseForgotPasswordChangedSuccessEvent ( IPrincipal currentUser , int userId ) = > OnForgotPasswordChangedSuccess ( CreateArgs ( AuditEvent . ForgotPasswordChangedSuccess , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-05-21 16:33:24 +10:00
public void RaiseLoginFailedEvent ( IPrincipal currentUser , int userId ) = > OnLoginFailed ( CreateArgs ( AuditEvent . LoginFailed , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-05-21 16:33:24 +10:00
public void RaiseLoginRequiresVerificationEvent ( IPrincipal currentUser , int userId ) = > OnLoginRequiresVerification ( CreateArgs ( AuditEvent . LoginRequiresVerification , currentUser , userId , string . Empty ) ) ;
2020-10-23 14:18:53 +11:00
2020-12-01 17:24:23 +11:00
public void RaiseLoginSuccessEvent ( IPrincipal currentUser , int userId ) = > OnLoginSuccess ( CreateArgs ( AuditEvent . LoginSucces , currentUser , userId , string . Empty ) ) ;
2020-10-23 14:18:53 +11:00
public SignOutAuditEventArgs RaiseLogoutSuccessEvent ( IPrincipal currentUser , int userId )
2020-10-23 10:10:02 +11:00
{
2020-10-23 14:18:53 +11:00
var currentUserId = GetCurrentUserId ( currentUser ) ;
var args = new SignOutAuditEventArgs ( AuditEvent . LogoutSuccess , IpResolver . GetCurrentRequestIpAddress ( ) , performingUser : currentUserId , affectedUser : userId ) ;
2020-10-23 10:10:02 +11:00
OnLogoutSuccess ( args ) ;
return args ;
}
2020-12-01 17:24:23 +11:00
2020-05-21 16:33:24 +10:00
public void RaisePasswordChangedEvent ( IPrincipal currentUser , int userId ) = > OnPasswordChanged ( CreateArgs ( AuditEvent . LogoutSuccess , currentUser , userId , string . Empty ) ) ;
public void RaiseResetAccessFailedCountEvent ( IPrincipal currentUser , int userId ) = > OnResetAccessFailedCount ( CreateArgs ( AuditEvent . ResetAccessFailedCount , currentUser , userId , string . Empty ) ) ;
2020-05-14 22:21:19 +01:00
2020-10-23 14:18:53 +11:00
public UserInviteEventArgs RaiseSendingUserInvite ( IPrincipal currentUser , UserInvite invite , IUser createdUser )
{
var currentUserId = GetCurrentUserId ( currentUser ) ;
var ip = IpResolver . GetCurrentRequestIpAddress ( ) ;
var args = new UserInviteEventArgs ( ip , currentUserId , invite , createdUser ) ;
OnSendingUserInvite ( args ) ;
return args ;
}
public bool HasSendingUserInviteEventHandler = > SendingUserInvite ! = null ;
2020-12-01 18:14:37 +11:00
// TODO: These static events are problematic. Moving forward we don't want static events at all but we cannot
// have non-static events here because the user manager is a Scoped instance not a singleton
// so we'll have to deal with this a diff way i.e. refactoring how events are done entirely
public static event EventHandler < IdentityAuditEventArgs > AccountLocked ;
public static event EventHandler < IdentityAuditEventArgs > AccountUnlocked ;
public static event EventHandler < IdentityAuditEventArgs > ForgotPasswordRequested ;
public static event EventHandler < IdentityAuditEventArgs > ForgotPasswordChangedSuccess ;
public static event EventHandler < IdentityAuditEventArgs > LoginFailed ;
public static event EventHandler < IdentityAuditEventArgs > LoginRequiresVerification ;
public static event EventHandler < IdentityAuditEventArgs > LoginSuccess ;
public static event EventHandler < SignOutAuditEventArgs > LogoutSuccess ;
public static event EventHandler < IdentityAuditEventArgs > PasswordChanged ;
public static event EventHandler < IdentityAuditEventArgs > PasswordReset ;
public static event EventHandler < IdentityAuditEventArgs > ResetAccessFailedCount ;
2020-05-14 22:21:19 +01:00
2020-10-23 10:10:02 +11:00
/// <summary>
/// Raised when a user is invited
/// </summary>
2020-10-23 14:18:53 +11:00
public static event EventHandler < UserInviteEventArgs > SendingUserInvite ; // this event really has nothing to do with the user manager but was the most convenient place to put it
2020-05-25 23:15:32 +10:00
protected virtual void OnAccountLocked ( IdentityAuditEventArgs e ) = > AccountLocked ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-10-23 10:10:02 +11:00
protected virtual void OnSendingUserInvite ( UserInviteEventArgs e ) = > SendingUserInvite ? . Invoke ( this , e ) ;
2020-10-23 14:18:53 +11:00
2020-05-25 23:15:32 +10:00
protected virtual void OnAccountUnlocked ( IdentityAuditEventArgs e ) = > AccountUnlocked ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnForgotPasswordRequested ( IdentityAuditEventArgs e ) = > ForgotPasswordRequested ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnForgotPasswordChangedSuccess ( IdentityAuditEventArgs e ) = > ForgotPasswordChangedSuccess ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnLoginFailed ( IdentityAuditEventArgs e ) = > LoginFailed ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnLoginRequiresVerification ( IdentityAuditEventArgs e ) = > LoginRequiresVerification ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnLoginSuccess ( IdentityAuditEventArgs e ) = > LoginSuccess ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-10-23 14:18:53 +11:00
protected virtual void OnLogoutSuccess ( SignOutAuditEventArgs e ) = > LogoutSuccess ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnPasswordChanged ( IdentityAuditEventArgs e ) = > PasswordChanged ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnPasswordReset ( IdentityAuditEventArgs e ) = > PasswordReset ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
2020-05-25 23:15:32 +10:00
protected virtual void OnResetAccessFailedCount ( IdentityAuditEventArgs e ) = > ResetAccessFailedCount ? . Invoke ( this , e ) ;
2020-05-14 22:21:19 +01:00
}
}