From c515ce191701e770ff7231d1b973d64771c56686 Mon Sep 17 00:00:00 2001 From: Alessandro Agostini <102729761+AgostiniAlessandro@users.noreply.github.com> Date: Fri, 27 Jan 2023 11:23:22 +0100 Subject: [PATCH] Added Username policies (#8638) * Added Username policies * Added newline at the end of files # Conflicts: # src/Orchard.Web/Modules/Orchard.Users/Services/AccountValidationService.cs * Added check for username length that must be under 255 characters (even if username policies are disabled). If username isn't modified, policies are not enforced when saving the user inside backoffice. Default length limits are 1 and 255. * Added UsernameValidationError.cs Added a setting to bypass non fatal errors and show them as warning when creating/editing users from the backoffice Added the relative checkbox in RegistrationSettings.cshtml Modified the UsernameMeetsPolicies method to use the new class Modified AdminController (CreatePOST, EditPOST) and AccountController (Register) * If username is an email check that it matches the specified email * Added hints to UserRegistrationSettings view Changed the severity of some custom policies errors * Removed UsernameValidLengthAttribute.cs, if MinimumUsernameLength and MaximumUsernameLength settings don't make sense these settings are ignored * bugfix. The admin could change the a username setting an already existing username. Co-authored-by: Andrea Piovanelli --- .../Constants/UsernameValidationResults.cs | 10 ++ .../Controllers/AccountController.cs | 2 +- .../Controllers/AdminController.cs | 93 +++++++++++++++++-- .../MembershipSettingsExtensions.cs | 14 ++- .../Models/RegistrationSettingsPart.cs | 43 ++++++++- .../Orchard.Users/Orchard.Users.csproj | 2 + .../Services/AccountValidationService.cs | 16 ++-- .../Orchard.Users/Services/IUserService.cs | 3 +- .../Orchard.Users/Services/UserService.cs | 63 ++++++++++++- .../Services/UsernameValidationError.cs | 33 +++++++ .../Parts/Users.RegistrationSettings.cshtml | 43 ++++++++- src/Orchard/Security/IMembershipSettings.cs | 10 +- 12 files changed, 311 insertions(+), 21 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.Users/Constants/UsernameValidationResults.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Users/Services/UsernameValidationError.cs diff --git a/src/Orchard.Web/Modules/Orchard.Users/Constants/UsernameValidationResults.cs b/src/Orchard.Web/Modules/Orchard.Users/Constants/UsernameValidationResults.cs new file mode 100644 index 000000000..986332de6 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Users/Constants/UsernameValidationResults.cs @@ -0,0 +1,10 @@ +namespace Orchard.Users.Constants { + public static class UsernameValidationResults { + public const string UsernameIsTooShort = "UsernameIsTooShort"; + public const string UsernameIsTooLong = "UsernameIsTooLong"; + public const string UsernameContainsWhitespaces = "UsernameContainsWhitespaces"; + public const string UsernameContainsSpecialChars = "UsernameContainsSpecialChars"; + public const string UsernameAndEmailMustMatch = "UsernameAndEmailMustMatch"; + } +} + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs index 1b9bd7403..f26e5a400 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs @@ -661,4 +661,4 @@ namespace Orchard.Users.Controllers { #endregion } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs index 9c2ab60cf..8645d62b5 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs @@ -19,6 +19,8 @@ using Orchard.Users.Models; using Orchard.Users.Services; using Orchard.Users.ViewModels; using Orchard.Utility.Extensions; +using Orchard.Mvc.Html; +using Orchard.Users.Constants; namespace Orchard.Users.Controllers { [ValidateInput(false)] @@ -28,6 +30,7 @@ namespace Orchard.Users.Controllers { private readonly IUserEventHandler _userEventHandlers; private readonly ISiteService _siteService; private readonly IEnumerable _userManagementActionsProviders; + private readonly UrlHelper _urlHelper; public AdminController( IOrchardServices services, @@ -36,7 +39,8 @@ namespace Orchard.Users.Controllers { IShapeFactory shapeFactory, IUserEventHandler userEventHandlers, ISiteService siteService, - IEnumerable userManagementActionsProviders) { + IEnumerable userManagementActionsProviders, + UrlHelper urlHelper) { Services = services; _membershipService = membershipService; @@ -44,6 +48,7 @@ namespace Orchard.Users.Controllers { _userEventHandlers = userEventHandlers; _siteService = siteService; _userManagementActionsProviders = userManagementActionsProviders; + _urlHelper = urlHelper; T = NullLocalizer.Instance; Shape = shapeFactory; @@ -183,12 +188,35 @@ namespace Orchard.Users.Controllers { if (!Services.Authorizer.Authorize(Permissions.ManageUsers, T("Not authorized to manage users"))) return new HttpUnauthorizedResult(); + IDictionary validationErrors; + List usernameValidationErrors = new List(); + + + bool usernameMeetsPolicies = true; + var settings = _siteService.GetSiteSettings().As(); + if (!string.IsNullOrEmpty(createModel.UserName)) { + usernameMeetsPolicies = _userService.UsernameMeetsPolicies(createModel.UserName, createModel.Email, out usernameValidationErrors); + if (!usernameMeetsPolicies) { + // If this setting is enabled we'd like to show the warning message but we can't right now + // because we didn't create the user yet (and maybe we won't if some other validation fails) + // and we can't generate the link to the edit page properly so here we only handle the + // situation where we have to show warnings as errors. + if (!settings.BypassPoliciesFromBackoffice) { + ShowWarningAsErrors(usernameValidationErrors); + } + // Show fatal errors anyway + ShowFatalErrors(usernameValidationErrors); + } if (!_userService.VerifyUserUnicity(createModel.UserName, createModel.Email)) { AddModelError("NotUniqueUserName", T("User with that username and/or email already exists.")); } } - + else { + AddModelError(UsernameValidationResults.UsernameIsTooShort, T("The username must not be empty.")); + } + + if (!Regex.IsMatch(createModel.Email ?? "", UserPart.EmailPattern, RegexOptions.IgnoreCase)) { // http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx ModelState.AddModelError("Email", T("You must specify a valid email address.")); @@ -198,12 +226,12 @@ namespace Orchard.Users.Controllers { AddModelError("ConfirmPassword", T("Password confirmation must match")); } - IDictionary validationErrors; - + if (!_userService.PasswordMeetsPolicies(createModel.Password, null, out validationErrors)) { ModelState.AddModelErrors(validationErrors); } + var user = Services.ContentManager.New("User"); if (ModelState.IsValid) { user = _membershipService.CreateUser(new CreateUserParams( @@ -214,6 +242,12 @@ namespace Orchard.Users.Controllers { createModel.ForcePasswordChange)); } + // Now that the user has been created we check if we have to show the warning since now we can generate the link + // to the user edit page + if (!usernameMeetsPolicies && settings.BypassPoliciesFromBackoffice && usernameValidationErrors.Any(uve => uve.Severity == Severity.Warning)) { + Services.Notifier.Warning(T("The username {1} doesn't meet the custom requirements.", _urlHelper.ItemEditUrl(user), createModel.UserName)); + } + var model = Services.ContentManager.UpdateEditor(user, this); if (!ModelState.IsValid) { @@ -253,6 +287,22 @@ namespace Orchard.Users.Controllers { return View(model); } + private void ShowWarningAsErrors(List validationErrors) { + if (validationErrors.Any(uve => uve.Severity == Severity.Warning)) { + foreach (var uve in validationErrors.Where(uve => uve.Severity == Severity.Warning)) { + AddModelError(uve.Key, uve.ErrorMessage); + } + } + } + + private void ShowFatalErrors(List validationErrors) { + if (validationErrors.Any(uve => uve.Severity == Severity.Fatal)) { + foreach (var uve in validationErrors.Where(uve => uve.Severity == Severity.Fatal)) { + AddModelError(uve.Key, uve.ErrorMessage); + } + } + } + [HttpPost, ActionName("Edit")] public ActionResult EditPOST(int id) { if (!Services.Authorizer.Authorize(Permissions.ManageUsers, T("Not authorized to manage users"))) @@ -274,10 +324,34 @@ namespace Orchard.Users.Controllers { var editModel = new UserEditViewModel { User = user }; if (TryUpdateModel(editModel)) { - if (!_userService.VerifyUserUnicity(id, editModel.UserName, editModel.Email)) { - AddModelError("NotUniqueUserName", T("User with that username and/or email already exists.")); + List validationErrors; + bool usernameMeetsPolicies = _userService.UsernameMeetsPolicies(editModel.UserName, editModel.Email, out validationErrors); + var settings = _siteService.GetSiteSettings().As(); + // Username has been modified + if (!previousName.Equals(editModel.UserName)) { + if (!usernameMeetsPolicies && settings.BypassPoliciesFromBackoffice) { + // If warnings have to be bypassed and there's at least one warning we show a generic warning message + if (validationErrors.Any(uve => uve.Severity == Severity.Warning)) { + Services.Notifier.Warning(T("The username {1} doesn't meet the custom requirements.", _urlHelper.ItemEditUrl(user), editModel.UserName)); + } + } + else if (!usernameMeetsPolicies) { + // If warnings don't have to be bypassed we show everyone of them as errors + ShowWarningAsErrors(validationErrors); + } } - else if (!Regex.IsMatch(editModel.Email ?? "", UserPart.EmailPattern, RegexOptions.IgnoreCase)) { + else { + if (!usernameMeetsPolicies && settings.BypassPoliciesFromBackoffice) { + // If warnings have to be bypassed and there's at least one warning we show a generic warning message + if (validationErrors.Any(uve => uve.Severity == Severity.Warning)) { + Services.Notifier.Warning(T("The username {1} doesn't meet the custom requirements.", _urlHelper.ItemEditUrl(user), editModel.UserName)); + } + } + } + // Show every Fatal validation error + ShowFatalErrors(validationErrors); + + if (!Regex.IsMatch(editModel.Email ?? "", UserPart.EmailPattern, RegexOptions.IgnoreCase)) { // http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx ModelState.AddModelError("Email", T("You must specify a valid email address.")); } @@ -289,6 +363,10 @@ namespace Orchard.Users.Controllers { user.NormalizedUserName = editModel.UserName.ToLowerInvariant(); } + + if (!_userService.VerifyUserUnicity(id, editModel.UserName, editModel.Email)) { + AddModelError("NotUniqueUserName", T("User with that username and/or email already exists.")); + } } if (!ModelState.IsValid) { @@ -422,3 +500,4 @@ namespace Orchard.Users.Controllers { } } + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Extensions/MembershipSettingsExtensions.cs b/src/Orchard.Web/Modules/Orchard.Users/Extensions/MembershipSettingsExtensions.cs index 496574fe7..fab430cf3 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Extensions/MembershipSettingsExtensions.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Extensions/MembershipSettingsExtensions.cs @@ -1,4 +1,6 @@ -namespace Orchard.Security { +using Orchard.Users.Models; + +namespace Orchard.Security { public static class MembershipSettingsExtensions { public static int GetMinimumPasswordLength(this IMembershipSettings membershipSettings) { return membershipSettings.EnableCustomPasswordPolicy ? membershipSettings.MinimumPasswordLength : 7; @@ -21,5 +23,13 @@ public static int GetPasswordReuseLimit(this IMembershipSettings membershipSettings) { return membershipSettings.EnablePasswordHistoryPolicy ? membershipSettings.PasswordReuseLimit : 5; } + + public static int GetMinimumUsernameLength(this IMembershipSettings membershipSettings) { + return membershipSettings.EnableCustomUsernamePolicy ? membershipSettings.MinimumUsernameLength : 3; + } + + public static int GetMaximumUsernameLength(this IMembershipSettings membershipSettings) { + return membershipSettings.EnableCustomUsernamePolicy ? membershipSettings.MaximumUsernameLength : UserPart.MaxUserNameLength; + } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Users/Models/RegistrationSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Users/Models/RegistrationSettingsPart.cs index 001264e0a..32c7f8fd5 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Models/RegistrationSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Models/RegistrationSettingsPart.cs @@ -101,5 +101,46 @@ namespace Orchard.Users.Models { get { return this.Retrieve(x => x.PasswordReuseLimit, 5); } set { this.Store(x => x.PasswordReuseLimit, value); } } + + public bool EnableCustomUsernamePolicy { + get { return this.Retrieve(x => x.EnableCustomUsernamePolicy); } + set { this.Store(x => x.EnableCustomUsernamePolicy, value); } + } + + [Range(1, UserPart.MaxUserNameLength, ErrorMessage = "The minimum username length must be between 1 and 255.")] + public int MinimumUsernameLength { + get { return this.Retrieve(x => x.MinimumUsernameLength, 1); } + set { this.Store(x => x.MinimumUsernameLength, value); } + + } + + [Range(1, UserPart.MaxUserNameLength, ErrorMessage = "The maximum username length must be between 1 and 255.")] + public int MaximumUsernameLength { + get { return this.Retrieve(x => x.MaximumUsernameLength, UserPart.MaxUserNameLength); } + set { this.Store(x => x.MaximumUsernameLength, value); } + } + + public bool ForbidUsernameSpecialChars { + get { return this.Retrieve(x => x.ForbidUsernameSpecialChars); } + set { this.Store(x => x.ForbidUsernameSpecialChars, value); } + } + + public bool AllowEmailAsUsername { + get { return this.Retrieve(x => x.AllowEmailAsUsername); } + set { this.Store(x => x.AllowEmailAsUsername, value); } + } + + public bool ForbidUsernameWhitespace { + get { return this.Retrieve(x => x.ForbidUsernameWhitespace); } + set { this.Store(x => x.ForbidUsernameWhitespace, value); } + } + + public bool BypassPoliciesFromBackoffice { + get { return this.Retrieve(x => x.BypassPoliciesFromBackoffice); } + set { this.Store(x => x.BypassPoliciesFromBackoffice, value); } + } + + } -} \ No newline at end of file +} + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj index 52cb86c1f..2603ee618 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj +++ b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj @@ -101,6 +101,7 @@ + @@ -155,6 +156,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/AccountValidationService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/AccountValidationService.cs index 9d3a379cc..1c8741dbf 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/AccountValidationService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/AccountValidationService.cs @@ -35,13 +35,16 @@ namespace Orchard.Users.Services { return context.ValidationSuccessful; } - public bool ValidateUserName(AccountValidationContext context) { + List validationErrors; + _userService.UsernameMeetsPolicies(context.UserName, context.Email, out validationErrors); - if (string.IsNullOrWhiteSpace(context.UserName)) { - context.ValidationErrors.Add("username", T("You must specify a username.")); - } else if (context.UserName.Length >= UserPart.MaxUserNameLength) { - context.ValidationErrors.Add("username", T("The username you provided is too long.")); + if (validationErrors != null && validationErrors.Any()) { + foreach (var err in validationErrors) { + if (!context.ValidationErrors.ContainsKey(err.Key)) { + context.ValidationErrors.Add(err.Key, err.ErrorMessage); + } + } } return context.ValidationSuccessful; @@ -62,4 +65,5 @@ namespace Orchard.Users.Services { } } -} \ No newline at end of file +} + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/IUserService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/IUserService.cs index 5d0720647..ed0c79602 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/IUserService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/IUserService.cs @@ -20,5 +20,6 @@ namespace Orchard.Users.Services { bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc); bool PasswordMeetsPolicies(string password, IUser user, out IDictionary validationErrors); + bool UsernameMeetsPolicies(string username, string email, out List validationErrors); } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs index 022b50790..290e5923c 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs @@ -245,6 +245,66 @@ namespace Orchard.Users.Services { return validationErrors.Count == 0; } + + + public bool UsernameMeetsPolicies(string username, string email, out List validationErrors) { + validationErrors = new List(); + var settings = _siteService.GetSiteSettings().As(); + + if (string.IsNullOrEmpty(username)) { + validationErrors.Add(new UsernameValidationError(Severity.Fatal, UsernameValidationResults.UsernameIsTooShort, + T("The username must not be empty."))); + return false; + } + + // Validate username length to check it's not over 255. + if (username.Length > UserPart.MaxUserNameLength) { + validationErrors.Add(new UsernameValidationError(Severity.Fatal, UsernameValidationResults.UsernameIsTooLong, + T("The username can't be longer than {0} characters.", UserPart.MaxUserNameLength))); + return false; + } + + var usernameIsEmail = Regex.IsMatch(username, UserPart.EmailPattern, RegexOptions.IgnoreCase); + + if (usernameIsEmail && !username.Equals(email, StringComparison.OrdinalIgnoreCase)){ + validationErrors.Add(new UsernameValidationError(Severity.Fatal, UsernameValidationResults.UsernameAndEmailMustMatch, + T("If the username is an email it must match the specified email address."))); + return false; + } + + if (settings.EnableCustomUsernamePolicy) { + + /// If the Maximum username length is smaller than the Minimum username length settings ignore this setting + if (settings.GetMaximumUsernameLength() >= settings.GetMinimumUsernameLength() && username.Length < settings.GetMinimumUsernameLength()) { + if (!settings.AllowEmailAsUsername || !usernameIsEmail) { + validationErrors.Add(new UsernameValidationError(Severity.Warning, UsernameValidationResults.UsernameIsTooShort, + T("You must specify a username of {0} or more characters.", settings.GetMinimumUsernameLength()))); + } + } + + /// If the Minimum username length is greater than the Maximum username length settings ignore this setting + if (settings.GetMaximumUsernameLength() >= settings.GetMinimumUsernameLength() && username.Length > settings.GetMaximumUsernameLength()) { + if (!settings.AllowEmailAsUsername || !usernameIsEmail) { + validationErrors.Add(new UsernameValidationError(Severity.Warning, UsernameValidationResults.UsernameIsTooLong, + T("You must specify a username of at most {0} characters.", settings.GetMaximumUsernameLength()))); + } + } + + if (settings.ForbidUsernameWhitespace && username.Any(x => char.IsWhiteSpace(x))) { + validationErrors.Add(new UsernameValidationError(Severity.Warning, UsernameValidationResults.UsernameContainsWhitespaces, + T("The username must not contain whitespaces."))); + } + + if (settings.ForbidUsernameSpecialChars && Regex.Match(username, "[^a-zA-Z0-9]").Success) { + if (!settings.AllowEmailAsUsername || !usernameIsEmail) { + validationErrors.Add(new UsernameValidationError(Severity.Warning, UsernameValidationResults.UsernameContainsSpecialChars, + T("The username must not contain special characters."))); + } + } + } + return validationErrors.Count == 0; + } + public UserPart GetUserByNameOrEmail(string usernameOrEmail) { var lowerName = usernameOrEmail.ToLowerInvariant(); return _contentManager @@ -254,4 +314,5 @@ namespace Orchard.Users.Services { .FirstOrDefault(); } } -} \ No newline at end of file +} + diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/UsernameValidationError.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/UsernameValidationError.cs new file mode 100644 index 000000000..64e766fc3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/UsernameValidationError.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Orchard.Localization; + +namespace Orchard.Users.Services { + + public enum Severity { + Warning, + Fatal + } + + public class UsernameValidationError { + + private Severity _severity; + private string _key; + private LocalizedString _errorMessage; + + public UsernameValidationError(Severity severity, string key, LocalizedString errorMessage) { + Severity = severity; + Key = key; + ErrorMessage = errorMessage; + } + + public Severity Severity { get => _severity; set => _severity = value; } + public string Key { get => _key; set => _key = value; } + public LocalizedString ErrorMessage { get => _errorMessage; set => _errorMessage = value; } + + + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Users/Views/EditorTemplates/Parts/Users.RegistrationSettings.cshtml b/src/Orchard.Web/Modules/Orchard.Users/Views/EditorTemplates/Parts/Users.RegistrationSettings.cshtml index 170b2d1b8..07382edc6 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Views/EditorTemplates/Parts/Users.RegistrationSettings.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Users/Views/EditorTemplates/Parts/Users.RegistrationSettings.cshtml @@ -1,6 +1,7 @@ @model Orchard.Users.Models.RegistrationSettingsPart @using Orchard.Messaging.Services; @using System.Web.Security; +@using Orchard.Users.Models; @{ var messageManager = WorkContext.Resolve(); @@ -13,6 +14,45 @@ @Html.EditorFor(m => m.UsersCanRegister) + +
+ @Html.EditorFor(m => m.EnableCustomUsernamePolicy) + +
+
+ @Html.EditorFor(m => m.BypassPoliciesFromBackoffice) + +
+
+ + @Html.TextBoxFor(m => m.MinimumUsernameLength, new { @class = "text medium", @Value = Model.MinimumUsernameLength }) + @T("Minimum value allowed is 1") + @T("If this value is greater than the value set for Maximum Username length this setting will be ignored.") + @Html.ValidationMessage("MinimumUsernameLength", "*") +
+
+ + @Html.TextBoxFor(m => m.MaximumUsernameLength, new { @class = "text medium", @Value = Model.MaximumUsernameLength }) + @T("Maximum value allowed is {0}", UserPart.MaxUserNameLength) + @T("If this value is smaller than the value set for Minimum Username length this setting will be ignored.") + @Html.ValidationMessage("MaximumUsernameLength", "*") +
+
+ @Html.EditorFor(m => m.ForbidUsernameWhitespace) + +
+
+ @Html.EditorFor(m => m.ForbidUsernameSpecialChars) + +
+ @Html.EditorFor(m => m.AllowEmailAsUsername) + +
+
+
+
+ +
@Html.EditorFor(m => m.EnableCustomPasswordPolicy) @@ -126,4 +166,5 @@ @Html.ValidationMessage("NotificationsRecipients", "*") @T("The usernames to send the notifications to (e.g., \"admin, user1, ...\").")
- \ No newline at end of file + + diff --git a/src/Orchard/Security/IMembershipSettings.cs b/src/Orchard/Security/IMembershipSettings.cs index ed7b68fcf..97c4642b2 100644 --- a/src/Orchard/Security/IMembershipSettings.cs +++ b/src/Orchard/Security/IMembershipSettings.cs @@ -21,5 +21,13 @@ namespace Orchard.Security { MembershipPasswordFormat PasswordFormat { get; set; } bool EnablePasswordHistoryPolicy { get; set; } int PasswordReuseLimit { get; set; } + bool EnableCustomUsernamePolicy { get; set; } + int MinimumUsernameLength { get; set; } + int MaximumUsernameLength { get; set; } + bool ForbidUsernameSpecialChars { get; set; } + bool AllowEmailAsUsername {get; set;} + bool ForbidUsernameWhitespace { get; set; } + bool BypassPoliciesFromBackoffice { get; set; } + } -} \ No newline at end of file +}