diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index d4b0ae2721..67c37d9f04 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -21,7 +21,7 @@ using Umbraco.Core.Security; namespace Umbraco.Web.Security.Providers { - + /// /// Abstract Membership Provider that users any implementation of IMembershipMemberService{TEntity} service @@ -32,7 +32,7 @@ namespace Umbraco.Web.Security.Providers { protected IMembershipMemberService MemberService { get; private set; } - + protected UmbracoMembershipProvider(IMembershipMemberService memberService) { MemberService = memberService; @@ -64,7 +64,7 @@ namespace Umbraco.Web.Security.Providers /// The name of the provider has a length of zero. public override void Initialize(string name, NameValueCollection config) { - if (config == null) {throw new ArgumentNullException("config");} + if (config == null) { throw new ArgumentNullException("config"); } if (string.IsNullOrEmpty(name)) name = ProviderName; @@ -92,7 +92,7 @@ namespace Umbraco.Web.Security.Providers // in order to support updating passwords from the umbraco core, we can't validate the old password var m = MemberService.GetByUsername(username); if (m == null) return false; - + string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); @@ -145,7 +145,7 @@ namespace Umbraco.Web.Security.Providers /// /// A object populated with the information for the newly created user. /// - protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, + protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { // See if the user already exists @@ -170,11 +170,11 @@ namespace Umbraco.Web.Security.Providers var member = MemberService.CreateWithIdentity( username, - email, - FormatPasswordForStorage(encodedPassword, salt), + email, + FormatPasswordForStorage(encodedPassword, salt), memberTypeAlias, isApproved); - + member.PasswordQuestion = passwordQuestion; member.RawPasswordAnswerValue = EncryptString(passwordAnswer); member.LastLoginDate = DateTime.Now; @@ -218,7 +218,7 @@ namespace Umbraco.Web.Security.Providers public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - + var collection = new MembershipUserCollection(); foreach (var m in byEmail) { @@ -263,7 +263,7 @@ namespace Umbraco.Web.Security.Providers var membersList = new MembershipUserCollection(); var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords); - + foreach (var m in pagedMembers) { membersList.Add(ConvertToMembershipUser(m)); @@ -296,7 +296,7 @@ namespace Umbraco.Web.Security.Providers /// The password for the specified user name. /// protected override string PerformGetPassword(string username, string answer) - { + { var m = MemberService.GetByUsername(username); if (m == null) { @@ -419,7 +419,7 @@ namespace Umbraco.Web.Security.Providers // throw new ProviderException("Password answer required for password reset."); //} - + var m = MemberService.GetByUsername(username); if (m == null) { @@ -443,7 +443,7 @@ namespace Umbraco.Web.Security.Providers m.RawPasswordValue = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); - + return generatedPassword; } @@ -456,15 +456,21 @@ namespace Umbraco.Web.Security.Providers /// public override bool UnlockUser(string username) { - var userManager = GetBackofficeUserManager(); - return userManager != null && userManager.UnlockUser(username); - } + var member = MemberService.GetByUsername(username); + if (member == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", username)); + } - internal BackOfficeUserManager GetBackofficeUserManager() - { - return HttpContext.Current == null - ? null - : HttpContext.Current.GetOwinContext().GetBackOfficeUserManager(); + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + member.FailedPasswordAttempts = 0; + + MemberService.Save(member); + + return true; } /// @@ -490,7 +496,7 @@ namespace Umbraco.Web.Security.Providers } } m.Email = user.Email; - + m.IsApproved = user.IsApproved; m.IsLockedOut = user.IsLockedOut; if (user.IsLockedOut) @@ -502,15 +508,9 @@ namespace Umbraco.Web.Security.Providers MemberService.Save(m); } - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) + + + internal virtual ValidateUserResult PerformValidateUser(string username, string password) { var member = MemberService.GetByUsername(username); @@ -522,7 +522,10 @@ namespace Umbraco.Web.Security.Providers username, GetCurrentRequestIpAddress())); - return false; + return new ValidateUserResult + { + Authenticated = false + }; } if (member.IsApproved == false) @@ -533,7 +536,11 @@ namespace Umbraco.Web.Security.Providers username, GetCurrentRequestIpAddress())); - return false; + return new ValidateUserResult + { + Member = member, + Authenticated = false + }; } if (member.IsLockedOut) { @@ -543,11 +550,14 @@ namespace Umbraco.Web.Security.Providers username, GetCurrentRequestIpAddress())); - return false; + return new ValidateUserResult + { + Member = member, + Authenticated = false + }; } var authenticated = CheckPassword(password, member.RawPasswordValue); - var backofficeUserManager = GetBackofficeUserManager(); if (authenticated == false) { @@ -566,10 +576,7 @@ namespace Umbraco.Web.Security.Providers string.Format( "Login attempt failed for username {0} from IP address {1}, the user is now locked out, max invalid password attempts exceeded", username, - GetCurrentRequestIpAddress())); - - if(backofficeUserManager != null) - backofficeUserManager.RaiseAccountLockedEvent(member.Id); + GetCurrentRequestIpAddress())); } else { @@ -586,8 +593,6 @@ namespace Umbraco.Web.Security.Providers { //we have successfully logged in, reset the AccessFailedCount member.FailedPasswordAttempts = 0; - if (backofficeUserManager != null) - backofficeUserManager.RaiseResetAccessFailedCountEvent(member.Id); } member.LastLoginDate = DateTime.Now; @@ -596,10 +601,7 @@ namespace Umbraco.Web.Security.Providers string.Format( "Login attempt succeeded for username {0} from IP address {1}", username, - GetCurrentRequestIpAddress())); - - if (backofficeUserManager != null) - backofficeUserManager.RaiseLoginSuccessEvent(member.Id); + GetCurrentRequestIpAddress())); } //don't raise events for this! It just sets the member dates, if we do raise events this will @@ -612,7 +614,31 @@ namespace Umbraco.Web.Security.Providers if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) MemberService.Save(member, false); - return authenticated; + return new ValidateUserResult + { + Authenticated = authenticated, + Member = member + }; + } + + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var result = PerformValidateUser(username, password); + return result.Authenticated; + } + + internal class ValidateUserResult + { + public TEntity Member { get; set; } + public bool Authenticated { get; set; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 267bc0b433..6cbde80da3 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -2,9 +2,11 @@ using System.Configuration.Provider; using System.Security.Cryptography; using System.Text; +using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -80,5 +82,65 @@ namespace Umbraco.Web.Security.Providers return _defaultMemberTypeAlias; } } + + /// + /// Overridden in order to call the BackOfficeUserManager.UnlockUser method in order to raise the user audit events + /// + /// + /// + public override bool UnlockUser(string username) + { + var userManager = GetBackofficeUserManager(); + + //if this is null it means it's not in a web context so we'll revert to calling the base class + if (userManager == null) + return base.UnlockUser(username); + + return userManager.UnlockUser(username); + } + + /// + /// Override in order to raise appropriate events via the + /// + /// + /// + /// + internal override ValidateUserResult PerformValidateUser(string username, string password) + { + var result = base.PerformValidateUser(username, password); + + var userManager = GetBackofficeUserManager(); + + if (userManager == null) return result; + + if (result.Authenticated == false) + { + var count = result.Member.FailedPasswordAttempts; + if (count >= MaxInvalidPasswordAttempts) + { + userManager.RaiseAccountLockedEvent(result.Member.Id); + } + } + else + { + if (result.Member.FailedPasswordAttempts > 0) + { + //we have successfully logged in, if the failed password attempts was modified it means it was reset + if (result.Member.WasPropertyDirty("FailedPasswordAttempts")) + { + userManager.RaiseResetAccessFailedCountEvent(result.Member.Id); + } + } + } + + return result; + } + + internal BackOfficeUserManager GetBackofficeUserManager() + { + return HttpContext.Current == null + ? null + : HttpContext.Current.GetOwinContext().GetBackOfficeUserManager(); + } } } \ No newline at end of file