2015-02-09 17:37:21 +11:00
using System ;
using System.Linq ;
2018-08-29 01:15:46 +10:00
using System.Security.Claims ;
2015-10-22 18:40:20 +02:00
using System.Threading.Tasks ;
2017-09-19 16:46:49 +02:00
using System.Web ;
2018-08-29 01:15:46 +10:00
using System.Web.Security ;
2015-02-09 17:37:21 +11:00
using Microsoft.AspNet.Identity ;
using Microsoft.AspNet.Identity.Owin ;
2016-08-12 16:17:14 +02:00
using Microsoft.Owin.Security.DataProtection ;
2018-08-29 01:15:46 +10:00
using Umbraco.Core ;
2017-09-18 15:33:13 +02:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.Configuration.UmbracoSettings ;
2015-02-09 17:37:21 +11:00
using Umbraco.Core.Models.Identity ;
2018-08-29 01:15:46 +10:00
using Umbraco.Core.Security ;
2015-02-09 17:37:21 +11:00
using Umbraco.Core.Services ;
2018-08-29 01:15:46 +10:00
namespace Umbraco.Web.Security
2015-02-09 17:37:21 +11:00
{
/// <summary>
2015-03-26 17:43:22 +11:00
/// Default back office user manager
2015-02-09 17:37:21 +11:00
/// </summary>
2015-03-26 17:43:22 +11:00
public class BackOfficeUserManager : BackOfficeUserManager < BackOfficeIdentityUser >
2015-02-09 17:37:21 +11:00
{
2016-08-12 12:20:00 +02:00
public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker" ;
2015-02-09 17:37:21 +11:00
public BackOfficeUserManager ( IUserStore < BackOfficeIdentityUser , int > store )
: base ( store )
{
}
2017-09-18 15:33:13 +02:00
public BackOfficeUserManager (
IUserStore < BackOfficeIdentityUser , int > store ,
IdentityFactoryOptions < BackOfficeUserManager > options ,
MembershipProviderBase membershipProvider ,
IContentSection contentSectionConfig )
2015-10-22 18:40:20 +02:00
: base ( store )
{
2017-09-19 16:46:49 +02:00
if ( options = = null ) throw new ArgumentNullException ( "options" ) ;
2017-09-18 15:33:13 +02:00
InitUserManager ( this , membershipProvider , contentSectionConfig , options ) ;
2015-10-22 18:40:20 +02:00
}
#region Static Create methods
2016-05-30 17:56:25 +02:00
2015-03-24 13:36:52 +11:00
/// <summary>
2017-07-20 11:21:28 +02:00
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
2015-03-24 13:36:52 +11:00
/// </summary>
/// <param name="options"></param>
/// <param name="userService"></param>
2016-05-30 17:56:25 +02:00
/// <param name="memberTypeService"></param>
2017-08-25 17:55:26 +02:00
/// <param name="entityService"></param>
2015-03-24 13:36:52 +11:00
/// <param name="externalLoginService"></param>
/// <param name="membershipProvider"></param>
2017-09-18 15:33:13 +02:00
/// <param name="contentSectionConfig"></param>
2018-04-06 13:51:54 +10:00
/// <param name="globalSettings"></param>
2015-03-24 13:36:52 +11:00
/// <returns></returns>
2015-02-09 17:37:21 +11:00
public static BackOfficeUserManager Create (
IdentityFactoryOptions < BackOfficeUserManager > options ,
IUserService userService ,
2016-05-30 17:56:25 +02:00
IMemberTypeService memberTypeService ,
2017-08-25 17:55:26 +02:00
IEntityService entityService ,
2015-02-09 17:37:21 +11:00
IExternalLoginService externalLoginService ,
2017-09-18 15:33:13 +02:00
MembershipProviderBase membershipProvider ,
2018-04-06 13:51:54 +10:00
IContentSection contentSectionConfig ,
IGlobalSettings globalSettings )
2015-02-09 17:37:21 +11:00
{
if ( options = = null ) throw new ArgumentNullException ( "options" ) ;
if ( userService = = null ) throw new ArgumentNullException ( "userService" ) ;
2016-05-30 17:56:25 +02:00
if ( memberTypeService = = null ) throw new ArgumentNullException ( "memberTypeService" ) ;
2015-02-09 17:37:21 +11:00
if ( externalLoginService = = null ) throw new ArgumentNullException ( "externalLoginService" ) ;
2018-04-06 13:51:54 +10:00
var manager = new BackOfficeUserManager (
new BackOfficeUserStore ( userService , memberTypeService , entityService , externalLoginService , globalSettings , membershipProvider ) ) ;
2017-09-18 15:33:13 +02:00
manager . InitUserManager ( manager , membershipProvider , contentSectionConfig , options ) ;
return manager ;
}
2017-09-23 10:08:18 +02:00
2015-03-24 13:36:52 +11:00
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
/// <param name="options"></param>
/// <param name="customUserStore"></param>
/// <param name="membershipProvider"></param>
2017-09-18 15:33:13 +02:00
/// <param name="contentSectionConfig"></param>
2015-03-24 13:36:52 +11:00
/// <returns></returns>
public static BackOfficeUserManager Create (
2017-09-18 15:33:13 +02:00
IdentityFactoryOptions < BackOfficeUserManager > options ,
BackOfficeUserStore customUserStore ,
MembershipProviderBase membershipProvider ,
IContentSection contentSectionConfig )
2015-03-24 13:36:52 +11:00
{
2017-09-18 15:33:13 +02:00
var manager = new BackOfficeUserManager ( customUserStore , options , membershipProvider , contentSectionConfig ) ;
2015-10-22 18:40:20 +02:00
return manager ;
2016-08-12 16:17:14 +02:00
}
2015-10-22 18:40:20 +02:00
#endregion
2015-03-24 13:36:52 +11:00
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
/// <param name="manager"></param>
/// <param name="membershipProvider"></param>
2017-09-18 15:33:13 +02:00
/// <param name="contentSectionConfig"></param>
2015-03-24 13:36:52 +11:00
/// <param name="options"></param>
/// <returns></returns>
2016-08-12 16:17:14 +02:00
protected void InitUserManager (
BackOfficeUserManager manager ,
MembershipProviderBase membershipProvider ,
2017-09-18 15:33:13 +02:00
IContentSection contentSectionConfig ,
2016-08-12 16:17:14 +02:00
IdentityFactoryOptions < BackOfficeUserManager > options )
{
//NOTE: This method is mostly here for backwards compat
2017-09-18 15:33:13 +02:00
base . InitUserManager ( manager , membershipProvider , options . DataProtectionProvider , contentSectionConfig ) ;
2016-08-12 16:17:14 +02:00
}
}
/// <summary>
/// Generic Back office user manager
/// </summary>
public class BackOfficeUserManager < T > : UserManager < T , int >
where T : BackOfficeIdentityUser
{
public BackOfficeUserManager ( IUserStore < T , int > store )
: base ( store )
{
}
#region What we support do not currently
2018-03-21 09:06:32 +01:00
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
2016-08-12 16:17:14 +02:00
public override bool SupportsUserClaim
{
get { return false ; }
}
//TODO: Support this
public override bool SupportsQueryableUsers
{
get { return false ; }
}
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// </summary>
public override bool SupportsUserTwoFactor
{
get { return false ; }
}
//TODO: Support this
public override bool SupportsUserPhoneNumber
{
get { return false ; }
}
#endregion
2018-08-29 01:15:46 +10:00
public virtual async Task < ClaimsIdentity > GenerateUserIdentityAsync ( T user )
{
// NOTE the authenticationType must match the umbraco one
// defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await CreateIdentityAsync ( user , Core . Constants . Security . BackOfficeAuthenticationType ) ;
return userIdentity ;
}
2016-08-12 16:17:14 +02:00
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
/// <param name="manager"></param>
2017-08-25 17:55:26 +02:00
/// <param name="membershipProvider">
/// The <see cref="MembershipProviderBase"/> for the users called UsersMembershipProvider
/// </param>
2016-08-12 16:17:14 +02:00
/// <param name="dataProtectionProvider"></param>
2017-09-18 15:33:13 +02:00
/// <param name="contentSectionConfig"></param>
2016-08-12 16:17:14 +02:00
/// <returns></returns>
protected void InitUserManager (
2017-07-20 11:21:28 +02:00
BackOfficeUserManager < T > manager ,
MembershipProviderBase membershipProvider ,
2017-09-18 15:33:13 +02:00
IDataProtectionProvider dataProtectionProvider ,
IContentSection contentSectionConfig )
2015-03-24 13:36:52 +11:00
{
2015-02-09 17:37:21 +11:00
// Configure validation logic for usernames
2017-08-25 17:55:26 +02:00
manager . UserValidator = new BackOfficeUserValidator < T > ( manager )
2015-02-09 17:37:21 +11:00
{
AllowOnlyAlphanumericUserNames = false ,
RequireUniqueEmail = true
} ;
// Configure validation logic for passwords
2017-08-25 17:55:26 +02:00
manager . PasswordValidator = new MembershipProviderPasswordValidator ( membershipProvider ) ;
2015-02-09 17:37:21 +11:00
//use a custom hasher based on our membership provider
2017-08-25 17:55:26 +02:00
manager . PasswordHasher = GetDefaultPasswordHasher ( membershipProvider ) ;
2017-07-20 11:21:28 +02:00
2015-02-09 17:37:21 +11:00
if ( dataProtectionProvider ! = null )
{
2018-10-01 02:03:52 +13:00
manager . UserTokenProvider = new DataProtectorTokenProvider < T , int > ( dataProtectionProvider . Create ( "ASP.NET Identity" ) )
{
TokenLifespan = TimeSpan . FromDays ( 3 )
} ;
2015-02-09 17:37:21 +11:00
}
2015-07-01 17:07:29 +02:00
manager . UserLockoutEnabledByDefault = true ;
manager . MaxFailedAccessAttemptsBeforeLockout = membershipProvider . MaxInvalidPasswordAttempts ;
2015-07-01 18:02:58 +02:00
//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.
manager . DefaultAccountLockoutTimeSpan = TimeSpan . FromDays ( 30 ) ;
2015-07-01 17:07:29 +02:00
2015-02-09 17:37:21 +11:00
//custom identity factory for creating the identity object for which we auth against in the back office
2016-08-12 16:17:14 +02:00
manager . ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory < T > ( ) ;
2015-02-09 17:37:21 +11:00
2017-09-18 15:33:13 +02:00
manager . EmailService = new EmailService (
contentSectionConfig . NotificationEmailAddress ,
new EmailSender ( ) ) ;
2016-08-12 16:17:14 +02:00
2015-03-24 13:13:06 +11:00
//NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it
2015-02-09 17:37:21 +11:00
//// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
//// You can write your own provider and plug in here.
//manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
//{
// MessageFormat = "Your security code is: {0}"
//});
//manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
//{
// Subject = "Security Code",
// BodyFormat = "Your security code is: {0}"
//});
2017-07-20 11:21:28 +02:00
//manager.SmsService = new SmsService();
2015-02-09 17:37:21 +11:00
}
2015-10-22 18:40:20 +02:00
2018-03-21 09:06:32 +01:00
/// <summary>
/// Used to validate a user's session
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public virtual async Task < bool > ValidateSessionIdAsync ( int userId , string sessionId )
{
var userSessionStore = Store as IUserSessionStore < BackOfficeIdentityUser , int > ;
//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 ) ;
}
2017-08-25 17:55:26 +02:00
/// <summary>
/// This will determine which password hasher to use based on what is defined in config
/// </summary>
/// <returns></returns>
protected virtual IPasswordHasher GetDefaultPasswordHasher ( MembershipProviderBase provider )
{
//if the current user membership provider is unkown (this would be rare), then return the default password hasher
if ( provider . IsUmbracoUsersProvider ( ) = = false )
return new PasswordHasher ( ) ;
//if the configured provider has legacy features enabled, then return the membership provider password hasher
if ( provider . AllowManuallyChangingPassword | | provider . DefaultUseLegacyEncoding )
return new MembershipProviderPasswordHasher ( provider ) ;
//we can use the user aware password hasher (which will be the default and preferred way)
return new UserAwareMembershipProviderPasswordHasher ( provider ) ;
}
/// <summary>
/// Gets/sets the default back office user password checker
/// </summary>
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get ; set ; }
/// <summary>
/// Helper method to generate a password for a user based on the current password validator
/// </summary>
/// <returns></returns>
public string GeneratePassword ( )
{
var passwordValidator = PasswordValidator as PasswordValidator ;
if ( passwordValidator = = null )
{
var membershipPasswordHasher = PasswordHasher as IMembershipProviderPasswordHasher ;
//get the real password validator, this should not be null but in some very rare cases it could be, in which case
//we need to create a default password validator to use since we have no idea what it actually is or what it's rules are
//this is an Edge Case!
passwordValidator = PasswordValidator as PasswordValidator
? ? ( membershipPasswordHasher ! = null
? new MembershipProviderPasswordValidator ( membershipPasswordHasher . MembershipProvider )
: new PasswordValidator ( ) ) ;
}
var password = Membership . GeneratePassword (
passwordValidator . RequiredLength ,
passwordValidator . RequireNonLetterOrDigit ? 2 : 0 ) ;
var random = new Random ( ) ;
var passwordChars = password . ToCharArray ( ) ;
if ( passwordValidator . RequireDigit & & passwordChars . ContainsAny ( Enumerable . Range ( 48 , 58 ) . Select ( x = > ( char ) x ) ) )
password + = Convert . ToChar ( random . Next ( 48 , 58 ) ) ; // 0-9
if ( passwordValidator . RequireLowercase & & passwordChars . ContainsAny ( Enumerable . Range ( 97 , 123 ) . Select ( x = > ( char ) x ) ) )
password + = Convert . ToChar ( random . Next ( 97 , 123 ) ) ; // a-z
if ( passwordValidator . RequireUppercase & & passwordChars . ContainsAny ( Enumerable . Range ( 65 , 91 ) . Select ( x = > ( char ) x ) ) )
password + = Convert . ToChar ( random . Next ( 65 , 91 ) ) ; // A-Z
if ( passwordValidator . RequireNonLetterOrDigit & & passwordChars . ContainsAny ( Enumerable . Range ( 33 , 48 ) . Select ( x = > ( char ) x ) ) )
password + = Convert . ToChar ( random . Next ( 33 , 48 ) ) ; // symbols !"#$%&'()*+,-./
return password ;
}
2017-09-18 15:33:13 +02:00
/// <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="userId"></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 ( int userId )
{
var user = await FindByIdAsync ( userId ) ;
if ( user = = null )
throw new InvalidOperationException ( "No user found by id " + userId ) ;
if ( user . IsApproved = = false )
return true ;
return await base . IsLockedOutAsync ( userId ) ;
}
2017-08-25 17:55:26 +02:00
#region Overrides for password logic
2017-09-23 10:08:18 +02:00
2015-10-22 18:40:20 +02:00
/// <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
2017-07-20 11:21:28 +02:00
///
2015-10-22 18:40:20 +02:00
/// 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
2017-07-20 11:21:28 +02:00
/// data remains stored inside of Umbraco.
2015-10-22 18:40:20 +02:00
/// See: http://issues.umbraco.org/issue/U4-7032 for the use cases.
2017-07-20 11:21:28 +02:00
///
2015-10-22 18:40:20 +02:00
/// 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>
2016-07-12 13:36:08 +02:00
public override async Task < bool > CheckPasswordAsync ( T user , string password )
2015-10-22 18:40:20 +02:00
{
2015-10-26 14:51:19 +01:00
if ( BackOfficeUserPasswordChecker ! = null )
2015-10-22 18:40:20 +02:00
{
2015-10-26 14:51:19 +01:00
var result = await BackOfficeUserPasswordChecker . CheckPasswordAsync ( user , password ) ;
2017-08-25 17:55:26 +02:00
if ( user . HasIdentity = = false )
{
return false ;
}
2015-10-26 14:51:19 +01:00
//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 ;
}
2015-10-22 18:40:20 +02:00
}
2017-08-25 17:55:26 +02:00
//we cannot proceed if the user passed in does not have an identity
if ( user . HasIdentity = = false )
return false ;
2015-10-26 14:51:19 +01:00
//use the default behavior
return await base . CheckPasswordAsync ( user , password ) ;
2015-10-22 18:40:20 +02:00
}
2018-03-21 09:06:32 +01:00
public override Task < IdentityResult > ResetPasswordAsync ( int userId , string token , string newPassword )
{
var result = base . ResetPasswordAsync ( userId , token , newPassword ) ;
if ( result . Result . Succeeded )
RaisePasswordResetEvent ( userId ) ;
return result ;
}
/// <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 Task < IdentityResult > ChangePasswordWithResetAsync ( int userId , string token , string newPassword )
{
var result = base . ResetPasswordAsync ( userId , token , newPassword ) ;
if ( result . Result . Succeeded )
RaisePasswordChangedEvent ( userId ) ;
return result ;
}
2017-08-25 17:55:26 +02:00
public override Task < IdentityResult > ChangePasswordAsync ( int userId , string currentPassword , string newPassword )
{
2017-09-19 16:46:49 +02:00
var result = base . ChangePasswordAsync ( userId , currentPassword , newPassword ) ;
if ( result . Result . Succeeded )
RaisePasswordChangedEvent ( userId ) ;
return result ;
2017-08-25 17:55:26 +02:00
}
2015-10-22 18:40:20 +02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Override to determine how to hash the password
2015-10-22 18:40:20 +02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="store"></param>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
protected override async Task < bool > VerifyPasswordAsync ( IUserPasswordStore < T , int > store , T user , string password )
{
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher < BackOfficeIdentityUser , int > ;
if ( userAwarePasswordHasher = = null )
return await base . VerifyPasswordAsync ( store , user , password ) ;
var hash = await store . GetPasswordHashAsync ( user ) ;
return userAwarePasswordHasher . VerifyHashedPassword ( user , hash , password ) ! = PasswordVerificationResult . Failed ;
}
/// <summary>
/// Override to determine how to hash the password
/// </summary>
/// <param name="passwordStore"></param>
/// <param name="user"></param>
/// <param name="newPassword"></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 > UpdatePassword ( IUserPasswordStore < T , int > passwordStore , T user , string newPassword )
{
2018-02-22 14:06:26 +11:00
user . LastPasswordChangeDateUtc = DateTime . UtcNow ;
2017-08-25 17:55:26 +02:00
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher < BackOfficeIdentityUser , int > ;
if ( userAwarePasswordHasher = = null )
return await base . UpdatePassword ( passwordStore , user , newPassword ) ;
var result = await PasswordValidator . ValidateAsync ( newPassword ) ;
if ( result . Succeeded = = false )
return result ;
await passwordStore . SetPasswordHashAsync ( user , userAwarePasswordHasher . HashPassword ( user , newPassword ) ) ;
await UpdateSecurityStampInternal ( user ) ;
return IdentityResult . Success ;
2017-09-23 10:08:18 +02:00
2017-08-25 17:55:26 +02:00
}
/// <summary>
/// This is copied from the underlying .NET base class since they decied to not expose it
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
private async Task UpdateSecurityStampInternal ( BackOfficeIdentityUser user )
{
if ( SupportsUserSecurityStamp = = false )
return ;
await GetSecurityStore ( ) . SetSecurityStampAsync ( user , NewSecurityStamp ( ) ) ;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decied to not expose it
/// </summary>
/// <returns></returns>
private IUserSecurityStampStore < BackOfficeIdentityUser , int > GetSecurityStore ( )
{
var store = Store as IUserSecurityStampStore < BackOfficeIdentityUser , int > ;
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 decied to not expose it
/// </summary>
/// <returns></returns>
private static string NewSecurityStamp ( )
{
return Guid . NewGuid ( ) . ToString ( ) ;
}
#endregion
2017-09-19 16:46:49 +02:00
2018-02-22 13:02:34 +10:00
public override async Task < IdentityResult > SetLockoutEndDateAsync ( int userId , DateTimeOffset lockoutEnd )
2017-09-19 16:46:49 +02:00
{
2018-02-22 13:02:34 +10:00
var result = await base . SetLockoutEndDateAsync ( userId , lockoutEnd ) ;
2017-09-19 16:46:49 +02:00
// The way we unlock is by setting the lockoutEnd date to the current datetime
2018-02-22 13:02:34 +10:00
if ( result . Succeeded & & lockoutEnd > = DateTimeOffset . UtcNow )
{
2017-09-19 16:46:49 +02:00
RaiseAccountLockedEvent ( userId ) ;
2018-02-22 13:02:34 +10:00
}
2017-09-19 16:46:49 +02:00
else
2018-02-22 13:02:34 +10:00
{
2017-09-19 16:46:49 +02:00
RaiseAccountUnlockedEvent ( userId ) ;
2018-02-22 13:02:34 +10:00
//Resets the login attempt fails back to 0 when unlock is clicked
await ResetAccessFailedCountAsync ( userId ) ;
}
2017-09-19 16:46:49 +02:00
return result ;
}
public override async Task < IdentityResult > ResetAccessFailedCountAsync ( int userId )
{
var lockoutStore = ( IUserLockoutStore < BackOfficeIdentityUser , int > ) Store ;
var user = await FindByIdAsync ( userId ) ;
if ( user = = null )
throw new InvalidOperationException ( "No user found by user id " + userId ) ;
var accessFailedCount = await GetAccessFailedCountAsync ( user . Id ) ;
if ( accessFailedCount = = 0 )
return IdentityResult . Success ;
await lockoutStore . ResetAccessFailedCountAsync ( user ) ;
//raise the event now that it's reset
RaiseResetAccessFailedCountEvent ( userId ) ;
return await UpdateAsync ( user ) ;
}
2017-09-23 10:08:18 +02:00
2018-02-22 13:02:34 +10:00
/// <summary>
/// Overides the microsoft ASP.NET user managment method
/// </summary>
/// <param name="userId"></param>
/// <returns>
/// returns a Async Task<IdentityResult>
/// </returns>
/// <remarks>
/// Doesnt set fail attempts back to 0
/// </remarks>
public override async Task < IdentityResult > AccessFailedAsync ( int userId )
2017-09-19 16:46:49 +02:00
{
2018-02-22 13:02:34 +10:00
var lockoutStore = ( IUserLockoutStore < BackOfficeIdentityUser , int > ) Store ;
var user = await FindByIdAsync ( userId ) ;
if ( user = = null )
throw new InvalidOperationException ( "No user found by user id " + userId ) ;
var count = await lockoutStore . IncrementAccessFailedCountAsync ( user ) ;
if ( count > = MaxFailedAccessAttemptsBeforeLockout )
{
await lockoutStore . SetLockoutEndDateAsync ( user , DateTimeOffset . UtcNow . Add ( DefaultAccountLockoutTimeSpan ) ) ;
//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 ) ;
2017-09-19 16:46:49 +02:00
//Slightly confusing: this will return a Success if we successfully update the AccessFailed count
2018-02-22 13:02:34 +10:00
if ( result . Succeeded )
2017-09-19 16:46:49 +02:00
RaiseLoginFailedEvent ( userId ) ;
return result ;
}
internal void RaiseAccountLockedEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnAccountLocked ( new IdentityAuditEventArgs ( AuditEvent . AccountLocked , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseAccountUnlockedEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnAccountUnlocked ( new IdentityAuditEventArgs ( AuditEvent . AccountUnlocked , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseForgotPasswordRequestedEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnForgotPasswordRequested ( new IdentityAuditEventArgs ( AuditEvent . ForgotPasswordRequested , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseForgotPasswordChangedSuccessEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnForgotPasswordChangedSuccess ( new IdentityAuditEventArgs ( AuditEvent . ForgotPasswordChangedSuccess , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseLoginFailedEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnLoginFailed ( new IdentityAuditEventArgs ( AuditEvent . LoginFailed , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseInvalidLoginAttemptEvent ( string username )
{
OnLoginFailed ( new IdentityAuditEventArgs ( AuditEvent . LoginFailed , GetCurrentRequestIpAddress ( ) , username , string . Format ( "Attempted login for username '{0}' failed" , username ) ) ) ;
}
internal void RaiseLoginRequiresVerificationEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnLoginRequiresVerification ( new IdentityAuditEventArgs ( AuditEvent . LoginRequiresVerification , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseLoginSuccessEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnLoginSuccess ( new IdentityAuditEventArgs ( AuditEvent . LoginSucces , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaiseLogoutSuccessEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnLogoutSuccess ( new IdentityAuditEventArgs ( AuditEvent . LogoutSuccess , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
internal void RaisePasswordChangedEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnPasswordChanged ( new IdentityAuditEventArgs ( AuditEvent . PasswordChanged , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
2018-03-21 09:06:32 +01:00
//TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense.
2017-09-19 16:46:49 +02:00
internal void RaisePasswordResetEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnPasswordReset ( new IdentityAuditEventArgs ( AuditEvent . PasswordReset , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
2018-03-21 09:06:32 +01:00
2017-09-19 16:46:49 +02:00
internal void RaiseResetAccessFailedCountEvent ( int userId )
{
2018-03-21 09:06:32 +01:00
OnResetAccessFailedCount ( new IdentityAuditEventArgs ( AuditEvent . ResetAccessFailedCount , GetCurrentRequestIpAddress ( ) , affectedUser : userId ) ) ;
2017-09-19 16:46:49 +02:00
}
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 ) ;
}
/// <summary>
/// Returns the current request IP address for logging if there is one
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentRequestIpAddress ( )
{
//TODO: inject a service to get this value, we should not be relying on the old HttpContext.Current especially in the ASP.NET Identity world.
var httpContext = HttpContext . Current = = null ? ( HttpContextBase ) null : new HttpContextWrapper ( HttpContext . Current ) ;
return httpContext . GetCurrentRequestIpAddress ( ) ;
}
2018-10-01 02:03:52 +13:00
2015-02-09 17:37:21 +11:00
}
2018-03-21 09:06:32 +01:00
2015-02-09 17:37:21 +11:00
}