From 8e21bc3c74bf242bd7108e84ed5fa7e17bf7f5e3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 27 Jun 2017 15:30:32 +1000 Subject: [PATCH] Create user now generates a password --- .../Security/BackOfficeUserManager.cs | 46 +++++++++++++++---- .../MembershipProviderPasswordValidator.cs | 38 +++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/Editors/MemberController.cs | 4 +- src/Umbraco.Web/Editors/UsersController.cs | 28 +++++++++-- 5 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index 2b68fd07eb..ad22c1426e 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -90,6 +90,7 @@ namespace Umbraco.Core.Security base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider); } + } /// @@ -152,15 +153,8 @@ namespace Umbraco.Core.Security }; // Configure validation logic for passwords - manager.PasswordValidator = new PasswordValidator - { - RequiredLength = membershipProvider.MinRequiredPasswordLength, - RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0, - RequireDigit = false, - RequireLowercase = false, - RequireUppercase = false - //TODO: Do we support the old regex match thing that membership providers used? - }; + var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); + manager.PasswordValidator = new MembershipProviderPasswordValidator(provider); //use a custom hasher based on our membership provider manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); @@ -238,5 +232,39 @@ namespace Umbraco.Core.Security /// Gets/sets the default back office user password checker /// public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; } + + /// + /// Helper method to generate a password for a user based on the current password validator + /// + /// + public string GeneratePassword() + { + var passwordValidator = PasswordValidator as PasswordValidator; + if (passwordValidator != null) + { + 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; + } + throw new NotSupportedException("Cannot generate a password since the type of the password validator (" + PasswordValidator.GetType() + ") is not known"); + } } } diff --git a/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs b/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs new file mode 100644 index 0000000000..3331116b4e --- /dev/null +++ b/src/Umbraco.Core/Security/MembershipProviderPasswordValidator.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using System.Web.Security; +using Microsoft.AspNet.Identity; + +namespace Umbraco.Core.Security +{ + /// + /// Ensure that both the normal password validator rules are processed along with the underlying memberhsip provider rules + /// + public class MembershipProviderPasswordValidator : PasswordValidator + { + public MembershipProvider Provider { get; private set; } + + public MembershipProviderPasswordValidator(MembershipProvider provider) + { + Provider = provider; + + RequiredLength = Provider.MinRequiredPasswordLength; + RequireNonLetterOrDigit = Provider.MinRequiredNonAlphanumericCharacters > 0; + RequireDigit = false; + RequireLowercase = false; + RequireUppercase = false; + } + + public override async Task ValidateAsync(string item) + { + var result = await base.ValidateAsync(item); + if (result.Succeeded == false) + return result; + var providerValidate = MembershipProviderBase.IsPasswordValid(item, Provider.MinRequiredNonAlphanumericCharacters, Provider.PasswordStrengthRegularExpression, Provider.MinRequiredPasswordLength); + if (providerValidate.Success == false) + { + return IdentityResult.Failed("Could not set password, password rules violated: " + providerValidate.Result); + } + return IdentityResult.Success; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1668d19e70..f066074a94 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -654,6 +654,7 @@ + diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index ec28a9408c..888d097bf4 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -234,8 +234,10 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + emptyContent = new Member(contentType); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); return Mapper.Map(emptyContent); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 3500982b7e..d42bd4f7e9 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -10,6 +10,7 @@ using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; +using System.Web.Security; using System.Web.WebPages; using AutoMapper; using ClientDependency.Core; @@ -203,6 +204,7 @@ namespace Umbraco.Web.Editors string orderBy = "username", Direction orderDirection = Direction.Ascending, [FromUri]string[] userGroups = null, + //TODO: Add User state filtering string filter = "") { long pageIndex = pageNumber - 1; @@ -246,18 +248,36 @@ namespace Umbraco.Web.Editors //we want to create the user with the UserManager, this ensures the 'empty' (special) password //format is applied without us having to duplicate that logic - var created = await UserManager.CreateAsync(new BackOfficeIdentityUser + var identityUser = new BackOfficeIdentityUser { Email = userSave.Email, Name = userSave.Name, UserName = userSave.Email - }); + }; + var created = await UserManager.CreateAsync(identityUser); if (created.Succeeded == false) { throw new HttpResponseException( Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); } + //we need to generate a password, however we can only do that if the user manager has a password validator that + //we can read values from + var passwordValidator = UserManager.PasswordValidator as PasswordValidator; + var resetPassword = string.Empty; + if (passwordValidator != null) + { + var password = UserManager.GeneratePassword(); + + var result = await UserManager.AddPasswordAsync(identityUser.Id, password); + if (result.Succeeded == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + } + resetPassword = password; + } + //now re-look the user back up which will now exist var user = Services.UserService.GetByEmail(userSave.Email); @@ -269,7 +289,9 @@ namespace Umbraco.Web.Editors Services.UserService.Save(user); - return Mapper.Map(user); + var display = Mapper.Map(user); + display.ResetPasswordValue = resetPassword; + return display; } ///