2020-03-13 15:15:14 +00:00
using System ;
using System.Collections.Generic ;
2020-04-04 19:58:09 +01:00
using System.Security.Claims ;
2020-03-13 15:15:14 +00:00
using System.Threading ;
using System.Threading.Tasks ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
2020-04-02 15:55:00 +01:00
using Microsoft.Owin.Security.DataProtection ;
2020-04-04 12:36:04 +01:00
using Umbraco.Core ;
2020-03-13 15:15:14 +00:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.Mapping ;
using Umbraco.Core.Security ;
using Umbraco.Core.Services ;
2020-04-16 11:16:35 +01:00
using Umbraco.Net ;
2020-03-13 15:15:14 +00:00
using Umbraco.Web.Models.Identity ;
namespace Umbraco.Web.Security
{
2020-04-04 12:59:29 +01:00
public class BackOfficeUserManager : BackOfficeUserManager < BackOfficeIdentityUser >
2020-03-13 15:15:14 +00:00
{
public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker" ;
2020-04-04 12:59:29 +01:00
public BackOfficeUserManager (
2020-03-13 15:15:14 +00:00
IPasswordConfiguration passwordConfiguration ,
IIpResolver ipResolver ,
IUserStore < BackOfficeIdentityUser > store ,
IOptions < IdentityOptions > optionsAccessor ,
IEnumerable < IUserValidator < BackOfficeIdentityUser > > userValidators ,
IEnumerable < IPasswordValidator < BackOfficeIdentityUser > > passwordValidators ,
ILookupNormalizer keyNormalizer ,
IdentityErrorDescriber errors ,
2020-04-02 15:55:00 +01:00
IDataProtectionProvider dataProtectionProvider ,
2020-03-13 15:15:14 +00:00
ILogger < UserManager < BackOfficeIdentityUser > > logger )
2020-04-02 15:55:00 +01:00
: base ( passwordConfiguration , ipResolver , store , optionsAccessor , userValidators , passwordValidators , keyNormalizer , errors , null , logger )
2020-03-13 15:15:14 +00:00
{
2020-04-02 15:55:00 +01:00
InitUserManager ( this , dataProtectionProvider ) ;
2020-03-13 15:15:14 +00:00
}
#region Static Create methods
2020-04-02 08:08:09 +01:00
2020-03-13 15:15:14 +00:00
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
2020-04-04 12:59:29 +01:00
public static BackOfficeUserManager Create (
2020-03-13 15:15:14 +00:00
IUserService userService ,
IEntityService entityService ,
IExternalLoginService externalLoginService ,
IGlobalSettings globalSettings ,
UmbracoMapper mapper ,
IPasswordConfiguration passwordConfiguration ,
IIpResolver ipResolver ,
IdentityErrorDescriber errors ,
2020-04-02 15:55:00 +01:00
IDataProtectionProvider dataProtectionProvider ,
2020-03-13 15:15:14 +00:00
ILogger < UserManager < BackOfficeIdentityUser > > logger )
{
2020-04-04 12:59:29 +01:00
var store = new BackOfficeUserStore ( userService , entityService , externalLoginService , globalSettings , mapper ) ;
2020-04-02 08:08:09 +01:00
return Create (
2020-03-13 15:15:14 +00:00
passwordConfiguration ,
ipResolver ,
store ,
errors ,
2020-04-02 15:55:00 +01:00
dataProtectionProvider ,
2020-03-13 15:15:14 +00:00
logger ) ;
}
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
2020-04-04 12:59:29 +01:00
public static BackOfficeUserManager Create (
2020-03-13 15:15:14 +00:00
IPasswordConfiguration passwordConfiguration ,
IIpResolver ipResolver ,
2020-03-16 13:53:03 +00:00
IUserStore < BackOfficeIdentityUser > customUserStore ,
2020-03-13 15:15:14 +00:00
IdentityErrorDescriber errors ,
2020-04-02 15:55:00 +01:00
IDataProtectionProvider dataProtectionProvider ,
2020-03-13 15:15:14 +00:00
ILogger < UserManager < BackOfficeIdentityUser > > logger )
{
2020-04-02 08:08:09 +01:00
var options = new IdentityOptions ( ) ;
// Configure validation logic for usernames
2020-04-04 12:59:29 +01:00
var userValidators = new List < UserValidator < BackOfficeIdentityUser > > { new BackOfficeUserValidator < BackOfficeIdentityUser > ( ) } ;
2020-04-02 08:08:09 +01:00
options . User . RequireUniqueEmail = true ;
// Configure validation logic for passwords
var passwordValidators = new List < IPasswordValidator < BackOfficeIdentityUser > > { new PasswordValidator < BackOfficeIdentityUser > ( ) } ;
options . Password . RequiredLength = passwordConfiguration . RequiredLength ;
options . Password . RequireNonAlphanumeric = passwordConfiguration . RequireNonLetterOrDigit ;
options . Password . RequireDigit = passwordConfiguration . RequireDigit ;
options . Password . RequireLowercase = passwordConfiguration . RequireLowercase ;
options . Password . RequireUppercase = passwordConfiguration . RequireUppercase ;
2020-04-06 17:23:04 +01:00
2020-04-04 19:58:09 +01:00
// Ensure Umbraco security stamp claim type is used
options . ClaimsIdentity . UserIdClaimType = ClaimTypes . NameIdentifier ;
options . ClaimsIdentity . UserNameClaimType = ClaimTypes . Name ;
options . ClaimsIdentity . RoleClaimType = ClaimTypes . Role ;
options . ClaimsIdentity . SecurityStampClaimType = Constants . Web . SecurityStampClaimType ;
2020-04-02 08:08:09 +01:00
options . Lockout . AllowedForNewUsers = true ;
options . Lockout . MaxFailedAccessAttempts = passwordConfiguration . MaxFailedAccessAttemptsBeforeLockout ;
//NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked
// or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are
// locked out or not.
options . Lockout . DefaultLockoutTimeSpan = TimeSpan . FromDays ( 30 ) ;
2020-04-04 12:59:29 +01:00
return new BackOfficeUserManager (
2020-03-13 15:15:14 +00:00
passwordConfiguration ,
ipResolver ,
2020-03-16 13:53:03 +00:00
customUserStore ,
2020-04-02 08:08:09 +01:00
new OptionsWrapper < IdentityOptions > ( options ) ,
2020-03-13 15:15:14 +00:00
userValidators ,
passwordValidators ,
2020-04-02 08:08:09 +01:00
new NopLookupNormalizer ( ) ,
2020-03-13 15:15:14 +00:00
errors ,
2020-04-02 15:55:00 +01:00
dataProtectionProvider ,
2020-03-13 15:15:14 +00:00
logger ) ;
}
#endregion
}
2020-04-04 12:59:29 +01:00
public class BackOfficeUserManager < T > : UserManager < T >
2020-03-13 15:15:14 +00:00
where T : BackOfficeIdentityUser
{
private PasswordGenerator _passwordGenerator ;
2020-04-04 12:59:29 +01:00
public BackOfficeUserManager (
2020-03-13 15:15:14 +00:00
IPasswordConfiguration passwordConfiguration ,
IIpResolver ipResolver ,
IUserStore < T > store ,
IOptions < IdentityOptions > optionsAccessor ,
IEnumerable < IUserValidator < T > > userValidators ,
IEnumerable < IPasswordValidator < T > > passwordValidators ,
ILookupNormalizer keyNormalizer ,
IdentityErrorDescriber errors ,
IServiceProvider services ,
ILogger < UserManager < T > > logger )
2020-04-02 08:08:09 +01:00
: base ( store , optionsAccessor , null , userValidators , passwordValidators , keyNormalizer , errors , services , logger )
2020-03-13 15:15:14 +00:00
{
PasswordConfiguration = passwordConfiguration ? ? throw new ArgumentNullException ( nameof ( passwordConfiguration ) ) ;
IpResolver = ipResolver ? ? throw new ArgumentNullException ( nameof ( ipResolver ) ) ;
}
#region What we do not currently support
// TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
public override bool SupportsUserClaim = > false ;
// TODO: Support this
public override bool SupportsQueryableUsers = > false ;
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// </summary>
public override bool SupportsUserTwoFactor = > false ;
// TODO: Support this
public override bool SupportsUserPhoneNumber = > false ;
#endregion
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
protected void InitUserManager (
2020-04-04 12:59:29 +01:00
BackOfficeUserManager < T > manager ,
2020-04-02 15:55:00 +01:00
IDataProtectionProvider dataProtectionProvider )
2020-03-13 15:15:14 +00:00
{
2020-04-22 20:28:49 +01:00
// use a custom hasher based on our membership provider
2020-04-02 08:08:09 +01:00
PasswordHasher = GetDefaultPasswordHasher ( PasswordConfiguration ) ;
2020-03-13 15:15:14 +00:00
2020-04-22 20:28:49 +01:00
// set OWIN data protection token provider as default
2020-04-02 15:55:00 +01:00
if ( dataProtectionProvider ! = null )
2020-03-13 15:15:14 +00:00
{
2020-04-02 15:55:00 +01:00
manager . RegisterTokenProvider (
2020-04-22 20:28:49 +01:00
TokenOptions . DefaultProvider ,
2020-04-02 15:55:00 +01:00
new OwinDataProtectorTokenProvider < T > ( dataProtectionProvider . Create ( "ASP.NET Identity" ) )
{
TokenLifespan = TimeSpan . FromDays ( 3 )
} ) ;
}
2020-04-22 20:28:49 +01:00
// register ASP.NET Core Identity token providers
manager . RegisterTokenProvider ( TokenOptions . DefaultEmailProvider , new EmailTokenProvider < T > ( ) ) ;
manager . RegisterTokenProvider ( TokenOptions . DefaultPhoneProvider , new PhoneNumberTokenProvider < T > ( ) ) ;
manager . RegisterTokenProvider ( TokenOptions . DefaultAuthenticatorProvider , new AuthenticatorTokenProvider < T > ( ) ) ;
2020-03-13 15:15:14 +00:00
}
/// <summary>
/// Used to validate a user's session
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
2020-03-16 13:53:03 +00:00
public virtual async Task < bool > ValidateSessionIdAsync ( string userId , string sessionId )
2020-03-13 15:15:14 +00:00
{
2020-04-04 12:59:29 +01:00
var userSessionStore = Store as IUserSessionStore < T > ;
2020-03-13 15:15:14 +00:00
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
if ( userSessionStore = = null ) return true ;
return await userSessionStore . ValidateSessionIdAsync ( userId , sessionId ) ;
}
/// <summary>
/// This will determine which password hasher to use based on what is defined in config
/// </summary>
/// <returns></returns>
protected virtual IPasswordHasher < T > GetDefaultPasswordHasher ( IPasswordConfiguration passwordConfiguration )
{
//we can use the user aware password hasher (which will be the default and preferred way)
2020-04-06 17:23:04 +01:00
return new UserAwarePasswordHasher < T > ( new PasswordSecurity ( passwordConfiguration ) ) ;
2020-03-13 15:15:14 +00:00
}
/// <summary>
/// Gets/sets the default back office user password checker
/// </summary>
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get ; set ; }
public IPasswordConfiguration PasswordConfiguration { get ; }
public IIpResolver IpResolver { get ; }
/// <summary>
/// Helper method to generate a password for a user based on the current password validator
/// </summary>
/// <returns></returns>
public string GeneratePassword ( )
{
if ( _passwordGenerator = = null ) _passwordGenerator = new PasswordGenerator ( PasswordConfiguration ) ;
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>
/// <param name="user"></param>
/// <returns></returns>
/// <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 )
{
if ( user = = null ) throw new ArgumentNullException ( nameof ( user ) ) ;
if ( user . IsApproved = = false ) return true ;
return await base . IsLockedOutAsync ( user ) ;
}
#region Overrides for password logic
/// <summary>
/// Logic used to validate a username and password
/// </summary>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
/// <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 )
{
var result = await BackOfficeUserPasswordChecker . CheckPasswordAsync ( user , password ) ;
if ( user . HasIdentity = = false )
{
return false ;
}
//if the result indicates to not fallback to the default, then return true if the credentials are valid
if ( result ! = BackOfficeUserPasswordCheckerResult . FallbackToDefaultChecker )
{
return result = = BackOfficeUserPasswordCheckerResult . ValidCredentials ;
}
}
//we cannot proceed if the user passed in does not have an identity
if ( user . HasIdentity = = false )
return false ;
//use the default behavior
return await base . CheckPasswordAsync ( user , password ) ;
}
/// <summary>
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <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-04-28 12:20:04 +01:00
var user = await base . FindByIdAsync ( userId . ToString ( ) ) ;
2020-03-13 15:15:14 +00:00
var result = await base . ResetPasswordAsync ( user , token , newPassword ) ;
if ( result . Succeeded ) RaisePasswordChangedEvent ( userId ) ;
return result ;
}
public override async Task < IdentityResult > ChangePasswordAsync ( T user , string currentPassword , string newPassword )
{
var result = await base . ChangePasswordAsync ( user , currentPassword , newPassword ) ;
if ( result . Succeeded ) RaisePasswordChangedEvent ( user . Id ) ;
return result ;
}
/// <summary>
/// Override to determine how to hash the password
/// </summary>
/// <param name="store"></param>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
protected override async Task < PasswordVerificationResult > VerifyPasswordAsync ( IUserPasswordStore < T > store , T user , string password )
{
var userAwarePasswordHasher = PasswordHasher ;
if ( userAwarePasswordHasher = = null )
return await base . VerifyPasswordAsync ( store , user , password ) ;
var hash = await store . GetPasswordHashAsync ( user , CancellationToken . None ) ;
return userAwarePasswordHasher . VerifyHashedPassword ( user , hash , password ) ;
}
/// <summary>
/// Override to determine how to hash the password
/// </summary>
/// <param name="user"></param>
/// <param name="newPassword"></param>
/// <param name="validatePassword"></param>
/// <returns></returns>
/// <remarks>
/// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used)
/// </remarks>
protected override async Task < IdentityResult > UpdatePasswordHash ( T user , string newPassword , bool validatePassword )
{
user . LastPasswordChangeDateUtc = DateTime . UtcNow ;
if ( validatePassword )
{
var validate = await ValidatePasswordAsync ( user , newPassword ) ;
if ( ! validate . Succeeded )
{
return validate ;
}
}
var passwordStore = Store as IUserPasswordStore < T > ;
if ( passwordStore = = null ) throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserPasswordStore < > ) ) ;
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>
/// <param name="user"></param>
/// <returns></returns>
private async Task UpdateSecurityStampInternal ( T user )
{
if ( SupportsUserSecurityStamp = = false ) return ;
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>
/// <returns></returns>
private IUserSecurityStampStore < T > GetSecurityStore ( )
{
var store = Store as IUserSecurityStampStore < T > ;
if ( store = = null ) throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserSecurityStampStore < > ) ) ;
return store ;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
/// <returns></returns>
private static string NewSecurityStamp ( )
{
return Guid . NewGuid ( ) . ToString ( ) ;
}
#endregion
public override async Task < IdentityResult > SetLockoutEndDateAsync ( T user , DateTimeOffset ? lockoutEnd )
{
if ( user = = null ) throw new ArgumentNullException ( nameof ( user ) ) ;
var result = await base . SetLockoutEndDateAsync ( user , lockoutEnd ) ;
// The way we unlock is by setting the lockoutEnd date to the current datetime
if ( result . Succeeded & & lockoutEnd > = DateTimeOffset . UtcNow )
{
RaiseAccountLockedEvent ( user . Id ) ;
}
else
{
RaiseAccountUnlockedEvent ( user . Id ) ;
//Resets the login attempt fails back to 0 when unlock is clicked
await ResetAccessFailedCountAsync ( user ) ;
}
return result ;
}
public override async Task < IdentityResult > ResetAccessFailedCountAsync ( T user )
{
if ( user = = null ) throw new ArgumentNullException ( nameof ( user ) ) ;
var lockoutStore = ( IUserLockoutStore < T > ) Store ;
var accessFailedCount = await GetAccessFailedCountAsync ( user ) ;
if ( accessFailedCount = = 0 )
return IdentityResult . Success ;
await lockoutStore . ResetAccessFailedCountAsync ( user , CancellationToken . None ) ;
//raise the event now that it's reset
RaiseResetAccessFailedCountEvent ( user . Id ) ;
return await UpdateAsync ( user ) ;
}
/// <summary>
/// Overrides the Microsoft ASP.NET user management method
/// </summary>
/// <param name="user"></param>
/// <returns>
/// returns a Async Task<IdentityResult />
/// </returns>
/// <remarks>
/// Doesn't set fail attempts back to 0
/// </remarks>
public override async Task < IdentityResult > AccessFailedAsync ( T user )
{
if ( user = = null ) throw new ArgumentNullException ( nameof ( user ) ) ;
var lockoutStore = Store as IUserLockoutStore < T > ;
if ( lockoutStore = = null ) throw new NotSupportedException ( "The current user store does not implement " + typeof ( IUserLockoutStore < > ) ) ;
var count = await lockoutStore . IncrementAccessFailedCountAsync ( user , CancellationToken . None ) ;
if ( count > = Options . Lockout . MaxFailedAccessAttempts )
{
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
}
var result = await UpdateAsync ( user ) ;
//Slightly confusing: this will return a Success if we successfully update the AccessFailed count
if ( result . Succeeded ) RaiseLoginFailedEvent ( user . Id ) ;
return result ;
}
internal void RaiseAccountLockedEvent ( int userId )
{
OnAccountLocked ( new IdentityAuditEventArgs ( AuditEvent . AccountLocked , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseAccountUnlockedEvent ( int userId )
{
OnAccountUnlocked ( new IdentityAuditEventArgs ( AuditEvent . AccountUnlocked , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseForgotPasswordRequestedEvent ( int userId )
{
OnForgotPasswordRequested ( new IdentityAuditEventArgs ( AuditEvent . ForgotPasswordRequested , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseForgotPasswordChangedSuccessEvent ( int userId )
{
OnForgotPasswordChangedSuccess ( new IdentityAuditEventArgs ( AuditEvent . ForgotPasswordChangedSuccess , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseLoginFailedEvent ( int userId )
{
OnLoginFailed ( new IdentityAuditEventArgs ( AuditEvent . LoginFailed , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseInvalidLoginAttemptEvent ( string username )
{
OnLoginFailed ( new IdentityAuditEventArgs ( AuditEvent . LoginFailed , IpResolver . GetCurrentRequestIpAddress ( ) , username , string . Format ( "Attempted login for username '{0}' failed" , username ) ) ) ;
}
internal void RaiseLoginRequiresVerificationEvent ( int userId )
{
OnLoginRequiresVerification ( new IdentityAuditEventArgs ( AuditEvent . LoginRequiresVerification , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseLoginSuccessEvent ( int userId )
{
OnLoginSuccess ( new IdentityAuditEventArgs ( AuditEvent . LoginSucces , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseLogoutSuccessEvent ( int userId )
{
OnLogoutSuccess ( new IdentityAuditEventArgs ( AuditEvent . LogoutSuccess , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaisePasswordChangedEvent ( int userId )
{
OnPasswordChanged ( new IdentityAuditEventArgs ( AuditEvent . PasswordChanged , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
internal void RaiseResetAccessFailedCountEvent ( int userId )
{
OnResetAccessFailedCount ( new IdentityAuditEventArgs ( AuditEvent . ResetAccessFailedCount , IpResolver . GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
}
public static event EventHandler AccountLocked ;
public static event EventHandler AccountUnlocked ;
public static event EventHandler ForgotPasswordRequested ;
public static event EventHandler ForgotPasswordChangedSuccess ;
public static event EventHandler LoginFailed ;
public static event EventHandler LoginRequiresVerification ;
public static event EventHandler LoginSuccess ;
public static event EventHandler LogoutSuccess ;
public static event EventHandler PasswordChanged ;
public static event EventHandler PasswordReset ;
public static event EventHandler ResetAccessFailedCount ;
protected virtual void OnAccountLocked ( IdentityAuditEventArgs e )
{
if ( AccountLocked ! = null ) AccountLocked ( this , e ) ;
}
protected virtual void OnAccountUnlocked ( IdentityAuditEventArgs e )
{
if ( AccountUnlocked ! = null ) AccountUnlocked ( this , e ) ;
}
protected virtual void OnForgotPasswordRequested ( IdentityAuditEventArgs e )
{
if ( ForgotPasswordRequested ! = null ) ForgotPasswordRequested ( this , e ) ;
}
protected virtual void OnForgotPasswordChangedSuccess ( IdentityAuditEventArgs e )
{
if ( ForgotPasswordChangedSuccess ! = null ) ForgotPasswordChangedSuccess ( this , e ) ;
}
protected virtual void OnLoginFailed ( IdentityAuditEventArgs e )
{
if ( LoginFailed ! = null ) LoginFailed ( this , e ) ;
}
protected virtual void OnLoginRequiresVerification ( IdentityAuditEventArgs e )
{
if ( LoginRequiresVerification ! = null ) LoginRequiresVerification ( this , e ) ;
}
protected virtual void OnLoginSuccess ( IdentityAuditEventArgs e )
{
if ( LoginSuccess ! = null ) LoginSuccess ( this , e ) ;
}
protected virtual void OnLogoutSuccess ( IdentityAuditEventArgs e )
{
if ( LogoutSuccess ! = null ) LogoutSuccess ( this , e ) ;
}
protected virtual void OnPasswordChanged ( IdentityAuditEventArgs e )
{
if ( PasswordChanged ! = null ) PasswordChanged ( this , e ) ;
}
protected virtual void OnPasswordReset ( IdentityAuditEventArgs e )
{
if ( PasswordReset ! = null ) PasswordReset ( this , e ) ;
}
protected virtual void OnResetAccessFailedCount ( IdentityAuditEventArgs e )
{
if ( ResetAccessFailedCount ! = null ) ResetAccessFailedCount ( this , e ) ;
}
}
}