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;
}
///