mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-25 19:17:13 +08:00
Implementing configurable password policies (#7051)
* Implementing configurable password policies, see #5380. Policies include: - Minimum password length - Password should contain uppercase and/or lowercase characters - Password should contain numbers - Password should contain special characters * Adding missing IMembershipSettings and removing now unneeded MembershipSettings * Removing hard-coded password length limits * Removing unnecessary checks when building the model state dictionary * Simplifying password length policy configuration by removing explicit enable option
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
using Orchard.Commands;
|
using Orchard.Commands;
|
||||||
|
using Orchard.Localization;
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
using Orchard.Users.Services;
|
using Orchard.Users.Services;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Orchard.Users.Commands {
|
namespace Orchard.Users.Commands {
|
||||||
public class UserCommands : DefaultOrchardCommandHandler {
|
public class UserCommands : DefaultOrchardCommandHandler {
|
||||||
@@ -40,8 +42,11 @@ namespace Orchard.Users.Commands {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Password == null || Password.Length < MinPasswordLength) {
|
IDictionary<string, LocalizedString> validationErrors;
|
||||||
Context.Output.WriteLine(T("You must specify a password of {0} or more characters.", MinPasswordLength));
|
if (!_userService.PasswordMeetsPolicies(Password, out validationErrors)) {
|
||||||
|
foreach (var error in validationErrors) {
|
||||||
|
Context.Output.WriteLine(error.Value);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +58,5 @@ namespace Orchard.Users.Commands {
|
|||||||
|
|
||||||
Context.Output.WriteLine(T("User created successfully"));
|
Context.Output.WriteLine(T("User created successfully"));
|
||||||
}
|
}
|
||||||
|
|
||||||
int MinPasswordLength {
|
|
||||||
get {
|
|
||||||
return _membershipService.GetSettings().MinRequiredPasswordLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Orchard.Users.Constants {
|
||||||
|
public static class UserPasswordValidationResults {
|
||||||
|
public const string PasswordIsTooShort = "PasswordIsTooShort";
|
||||||
|
public const string PasswordDoesNotContainNumbers = "PasswordDoesNotContainNumbers";
|
||||||
|
public const string PasswordDoesNotContainUppercase = "PasswordDoesNotContainUppercase";
|
||||||
|
public const string PasswordDoesNotContainLowercase = "PasswordDoesNotContainLowercase";
|
||||||
|
public const string PasswordDoesNotContainSpecialCharacters = "PasswordDoesNotContainSpecialCharacters";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
using System;
|
using Orchard.ContentManagement;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using System.Web.Mvc;
|
|
||||||
using System.Web.Security;
|
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.Mvc;
|
using Orchard.Mvc;
|
||||||
using Orchard.Mvc.Extensions;
|
using Orchard.Mvc.Extensions;
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
using Orchard.Themes;
|
using Orchard.Themes;
|
||||||
using Orchard.Users.Services;
|
|
||||||
using Orchard.ContentManagement;
|
|
||||||
using Orchard.Users.Models;
|
|
||||||
using Orchard.UI.Notify;
|
using Orchard.UI.Notify;
|
||||||
using Orchard.Users.Events;
|
using Orchard.Users.Events;
|
||||||
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.Users.Services;
|
||||||
using Orchard.Utility.Extensions;
|
using Orchard.Utility.Extensions;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Web.Mvc;
|
||||||
|
using System.Web.Security;
|
||||||
|
using Orchard.Services;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Orchard.Users.Controllers {
|
namespace Orchard.Users.Controllers {
|
||||||
[HandleError, Themed]
|
[HandleError, Themed]
|
||||||
@@ -24,18 +26,23 @@ namespace Orchard.Users.Controllers {
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IOrchardServices _orchardServices;
|
private readonly IOrchardServices _orchardServices;
|
||||||
private readonly IUserEventHandler _userEventHandler;
|
private readonly IUserEventHandler _userEventHandler;
|
||||||
|
private readonly IClock _clock;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
IAuthenticationService authenticationService,
|
IAuthenticationService authenticationService,
|
||||||
IMembershipService membershipService,
|
IMembershipService membershipService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IOrchardServices orchardServices,
|
IOrchardServices orchardServices,
|
||||||
IUserEventHandler userEventHandler) {
|
IUserEventHandler userEventHandler,
|
||||||
|
IClock clock) {
|
||||||
|
|
||||||
_authenticationService = authenticationService;
|
_authenticationService = authenticationService;
|
||||||
_membershipService = membershipService;
|
_membershipService = membershipService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_orchardServices = orchardServices;
|
_orchardServices = orchardServices;
|
||||||
_userEventHandler = userEventHandler;
|
_userEventHandler = userEventHandler;
|
||||||
|
_clock = clock;
|
||||||
|
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
T = NullLocalizer.Instance;
|
T = NullLocalizer.Instance;
|
||||||
}
|
}
|
||||||
@@ -51,7 +58,7 @@ namespace Orchard.Users.Controllers {
|
|||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
Logger.Information("Access denied to anonymous request on {0}", returnUrl);
|
Logger.Information("Access denied to anonymous request on {0}", returnUrl);
|
||||||
var shape = _orchardServices.New.LogOn().Title(T("Access Denied").Text);
|
var shape = _orchardServices.New.LogOn().Title(T("Access Denied").Text);
|
||||||
return new ShapeResult(this, shape);
|
return new ShapeResult(this, shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: (erikpo) Add a setting for whether or not to log access denieds since these can fill up a database pretty fast from bots on a high traffic site
|
//TODO: (erikpo) Add a setting for whether or not to log access denieds since these can fill up a database pretty fast from bots on a high traffic site
|
||||||
@@ -69,7 +76,7 @@ namespace Orchard.Users.Controllers {
|
|||||||
return this.RedirectLocal(returnUrl);
|
return this.RedirectLocal(returnUrl);
|
||||||
|
|
||||||
var shape = _orchardServices.New.LogOn().Title(T("Log On").Text);
|
var shape = _orchardServices.New.LogOn().Title(T("Log On").Text);
|
||||||
return new ShapeResult(this, shape);
|
return new ShapeResult(this, shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -83,7 +90,16 @@ namespace Orchard.Users.Controllers {
|
|||||||
var user = ValidateLogOn(userNameOrEmail, password);
|
var user = ValidateLogOn(userNameOrEmail, password);
|
||||||
if (!ModelState.IsValid) {
|
if (!ModelState.IsValid) {
|
||||||
var shape = _orchardServices.New.LogOn().Title(T("Log On").Text);
|
var shape = _orchardServices.New.LogOn().Title(T("Log On").Text);
|
||||||
return new ShapeResult(this, shape);
|
|
||||||
|
return new ShapeResult(this, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
if (user != null &&
|
||||||
|
membershipSettings.EnableCustomPasswordPolicy &&
|
||||||
|
membershipSettings.EnablePasswordExpiration &&
|
||||||
|
_membershipService.PasswordIsExpired(user, membershipSettings.PasswordExpirationTimeInDays)) {
|
||||||
|
return RedirectToAction("ChangeExpiredPassword", new { username = user.UserName });
|
||||||
}
|
}
|
||||||
|
|
||||||
_authenticationService.SignIn(user, rememberMe);
|
_authenticationService.SignIn(user, rememberMe);
|
||||||
@@ -103,24 +119,19 @@ namespace Orchard.Users.Controllers {
|
|||||||
return this.RedirectLocal(returnUrl);
|
return this.RedirectLocal(returnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MinPasswordLength {
|
|
||||||
get {
|
|
||||||
return _membershipService.GetSettings().MinRequiredPasswordLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AlwaysAccessible]
|
[AlwaysAccessible]
|
||||||
public ActionResult Register() {
|
public ActionResult Register() {
|
||||||
// ensure users can register
|
// ensure users can register
|
||||||
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
if ( !registrationSettings.UsersCanRegister ) {
|
if (!membershipSettings.UsersCanRegister) {
|
||||||
return HttpNotFound();
|
return HttpNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength();
|
||||||
|
|
||||||
var shape = _orchardServices.New.Register();
|
var shape = _orchardServices.New.Register();
|
||||||
return new ShapeResult(this, shape);
|
|
||||||
|
return new ShapeResult(this, shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -128,12 +139,12 @@ namespace Orchard.Users.Controllers {
|
|||||||
[ValidateInput(false)]
|
[ValidateInput(false)]
|
||||||
public ActionResult Register(string userName, string email, string password, string confirmPassword, string returnUrl = null) {
|
public ActionResult Register(string userName, string email, string password, string confirmPassword, string returnUrl = null) {
|
||||||
// ensure users can register
|
// ensure users can register
|
||||||
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
if ( !registrationSettings.UsersCanRegister ) {
|
if (!membershipSettings.UsersCanRegister) {
|
||||||
return HttpNotFound();
|
return HttpNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength();
|
||||||
|
|
||||||
if (ValidateRegistration(userName, email, password, confirmPassword)) {
|
if (ValidateRegistration(userName, email, password, confirmPassword)) {
|
||||||
// Attempt to register the user
|
// Attempt to register the user
|
||||||
@@ -141,13 +152,13 @@ namespace Orchard.Users.Controllers {
|
|||||||
var user = _membershipService.CreateUser(new CreateUserParams(userName, password, email, null, null, false));
|
var user = _membershipService.CreateUser(new CreateUserParams(userName, password, email, null, null, false));
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) {
|
if (user.As<UserPart>().EmailStatus == UserStatus.Pending) {
|
||||||
var siteUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl;
|
var siteUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl;
|
||||||
if(String.IsNullOrWhiteSpace(siteUrl)) {
|
if (String.IsNullOrWhiteSpace(siteUrl)) {
|
||||||
siteUrl = HttpContext.Request.ToRootUrlString();
|
siteUrl = HttpContext.Request.ToRootUrlString();
|
||||||
}
|
}
|
||||||
|
|
||||||
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.MakeAbsolute(Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", nonce = nonce}), siteUrl));
|
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.MakeAbsolute(Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce }), siteUrl));
|
||||||
|
|
||||||
_userEventHandler.SentChallengeEmail(user);
|
_userEventHandler.SentChallengeEmail(user);
|
||||||
return RedirectToAction("ChallengeEmailSent", new { ReturnUrl = returnUrl });
|
return RedirectToAction("ChallengeEmailSent", new { ReturnUrl = returnUrl });
|
||||||
@@ -163,20 +174,20 @@ namespace Orchard.Users.Controllers {
|
|||||||
|
|
||||||
return this.RedirectLocal(returnUrl);
|
return this.RedirectLocal(returnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelState.AddModelError("_FORM", T(ErrorCodeToString(/*createStatus*/MembershipCreateStatus.ProviderError)));
|
ModelState.AddModelError("_FORM", T(ErrorCodeToString(/*createStatus*/MembershipCreateStatus.ProviderError)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
// If we got this far, something failed, redisplay form
|
||||||
var shape = _orchardServices.New.Register();
|
var shape = _orchardServices.New.Register();
|
||||||
return new ShapeResult(this, shape);
|
return new ShapeResult(this, shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
[AlwaysAccessible]
|
[AlwaysAccessible]
|
||||||
public ActionResult RequestLostPassword() {
|
public ActionResult RequestLostPassword() {
|
||||||
// ensure users can request lost password
|
// ensure users can request lost password
|
||||||
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
if ( !registrationSettings.EnableLostPassword ) {
|
if (!membershipSettings.EnableLostPassword) {
|
||||||
return HttpNotFound();
|
return HttpNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,12 +198,12 @@ namespace Orchard.Users.Controllers {
|
|||||||
[AlwaysAccessible]
|
[AlwaysAccessible]
|
||||||
public ActionResult RequestLostPassword(string username) {
|
public ActionResult RequestLostPassword(string username) {
|
||||||
// ensure users can request lost password
|
// ensure users can request lost password
|
||||||
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
if ( !registrationSettings.EnableLostPassword ) {
|
if (!membershipSettings.EnableLostPassword) {
|
||||||
return HttpNotFound();
|
return HttpNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(String.IsNullOrWhiteSpace(username)){
|
if (String.IsNullOrWhiteSpace(username)) {
|
||||||
ModelState.AddModelError("username", T("You must specify a username or e-mail."));
|
ModelState.AddModelError("username", T("You must specify a username or e-mail."));
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -205,14 +216,15 @@ namespace Orchard.Users.Controllers {
|
|||||||
_userService.SendLostPasswordEmail(username, nonce => Url.MakeAbsolute(Url.Action("LostPassword", "Account", new { Area = "Orchard.Users", nonce = nonce }), siteUrl));
|
_userService.SendLostPasswordEmail(username, nonce => Url.MakeAbsolute(Url.Action("LostPassword", "Account", new { Area = "Orchard.Users", nonce = nonce }), siteUrl));
|
||||||
|
|
||||||
_orchardServices.Notifier.Information(T("Check your e-mail for the confirmation link."));
|
_orchardServices.Notifier.Information(T("Check your e-mail for the confirmation link."));
|
||||||
|
|
||||||
return RedirectToAction("LogOn");
|
return RedirectToAction("LogOn");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[AlwaysAccessible]
|
[AlwaysAccessible]
|
||||||
public ActionResult ChangePassword() {
|
public ActionResult ChangePassword() {
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength();
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -224,27 +236,76 @@ namespace Orchard.Users.Controllers {
|
|||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
|
||||||
Justification = "Exceptions result in password not being changed.")]
|
Justification = "Exceptions result in password not being changed.")]
|
||||||
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
|
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength(); ;
|
||||||
|
|
||||||
if ( !ValidateChangePassword(currentPassword, newPassword, confirmPassword) ) {
|
if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword)) {
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (PasswordChangeIsSuccess(currentPassword, newPassword, _orchardServices.WorkContext.CurrentUser.UserName)) {
|
||||||
var validated = _membershipService.ValidateUser(User.Identity.Name, currentPassword);
|
return RedirectToAction("ChangePasswordSuccess");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ChangePassword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( validated != null ) {
|
[AlwaysAccessible]
|
||||||
|
public ActionResult ChangeExpiredPassword(string username) {
|
||||||
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
var lastPasswordChangeUtc = _membershipService.GetUser(username).As<UserPart>().LastPasswordChangeUtc;
|
||||||
|
|
||||||
|
if (lastPasswordChangeUtc.Value.AddDays(membershipSettings.PasswordExpirationTimeInDays) >
|
||||||
|
_clock.UtcNow) {
|
||||||
|
return RedirectToAction("LogOn");
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewModel = _orchardServices.New.ViewModel(
|
||||||
|
Username: username,
|
||||||
|
PasswordLength: membershipSettings.GetMinimumPasswordLength());
|
||||||
|
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost, AlwaysAccessible, ValidateInput(false)]
|
||||||
|
public ActionResult ChangeExpiredPassword(string currentPassword, string newPassword, string confirmPassword, string username) {
|
||||||
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
var viewModel = _orchardServices.New.ViewModel(
|
||||||
|
Username: username,
|
||||||
|
PasswordLength: membershipSettings.GetMinimumPasswordLength());
|
||||||
|
|
||||||
|
if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword)) {
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PasswordChangeIsSuccess(currentPassword, newPassword, username)) {
|
||||||
|
return RedirectToAction("ChangePasswordSuccess");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PasswordChangeIsSuccess(string currentPassword, string newPassword, string username) {
|
||||||
|
try {
|
||||||
|
var validated = _membershipService.ValidateUser(username, currentPassword);
|
||||||
|
|
||||||
|
if (validated != null) {
|
||||||
_membershipService.SetPassword(validated, newPassword);
|
_membershipService.SetPassword(validated, newPassword);
|
||||||
_userEventHandler.ChangedPassword(validated);
|
_userEventHandler.ChangedPassword(validated);
|
||||||
return RedirectToAction("ChangePasswordSuccess");
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelState.AddModelError("_FORM",
|
|
||||||
T("The current password is incorrect or the new password is invalid."));
|
|
||||||
return ChangePassword();
|
|
||||||
} catch {
|
|
||||||
ModelState.AddModelError("_FORM", T("The current password is incorrect or the new password is invalid."));
|
ModelState.AddModelError("_FORM", T("The current password is incorrect or the new password is invalid."));
|
||||||
return ChangePassword();
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
ModelState.AddModelError("_FORM", T("The current password is incorrect or the new password is invalid."));
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +315,8 @@ namespace Orchard.Users.Controllers {
|
|||||||
return RedirectToAction("LogOn");
|
return RedirectToAction("LogOn");
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength();
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -268,11 +330,10 @@ namespace Orchard.Users.Controllers {
|
|||||||
return Redirect("~/");
|
return Redirect("~/");
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewData["PasswordLength"] = MinPasswordLength;
|
var membershipSettings = _membershipService.GetSettings();
|
||||||
|
ViewData["PasswordLength"] = membershipSettings.GetMinimumPasswordLength();
|
||||||
|
|
||||||
if (newPassword == null || newPassword.Length < MinPasswordLength) {
|
ValidatePassword(newPassword);
|
||||||
ModelState.AddModelError("newPassword", T("You must specify a new password of {0} or more characters.", MinPasswordLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal)) {
|
if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal)) {
|
||||||
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
||||||
@@ -327,10 +388,13 @@ namespace Orchard.Users.Controllers {
|
|||||||
if ( String.IsNullOrEmpty(currentPassword) ) {
|
if ( String.IsNullOrEmpty(currentPassword) ) {
|
||||||
ModelState.AddModelError("currentPassword", T("You must specify a current password."));
|
ModelState.AddModelError("currentPassword", T("You must specify a current password."));
|
||||||
}
|
}
|
||||||
if ( newPassword == null || newPassword.Length < MinPasswordLength ) {
|
|
||||||
ModelState.AddModelError("newPassword", T("You must specify a new password of {0} or more characters.", MinPasswordLength));
|
if (String.Equals(currentPassword, newPassword, StringComparison.Ordinal)) {
|
||||||
|
ModelState.AddModelError("newPassword", T("The new password must be different from the current password."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValidatePassword(newPassword);
|
||||||
|
|
||||||
if ( !String.Equals(newPassword, confirmPassword, StringComparison.Ordinal) ) {
|
if ( !String.Equals(newPassword, confirmPassword, StringComparison.Ordinal) ) {
|
||||||
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
||||||
}
|
}
|
||||||
@@ -397,15 +461,25 @@ namespace Orchard.Users.Controllers {
|
|||||||
if (!_userService.VerifyUserUnicity(userName, email)) {
|
if (!_userService.VerifyUserUnicity(userName, email)) {
|
||||||
ModelState.AddModelError("userExists", T("User with that username and/or email already exists."));
|
ModelState.AddModelError("userExists", T("User with that username and/or email already exists."));
|
||||||
}
|
}
|
||||||
if (password == null || password.Length < MinPasswordLength) {
|
|
||||||
ModelState.AddModelError("password", T("You must specify a password of {0} or more characters.", MinPasswordLength));
|
ValidatePassword(password);
|
||||||
}
|
|
||||||
if (!String.Equals(password, confirmPassword, StringComparison.Ordinal)) {
|
if (!String.Equals(password, confirmPassword, StringComparison.Ordinal)) {
|
||||||
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
|
||||||
}
|
}
|
||||||
return ModelState.IsValid;
|
return ModelState.IsValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidatePassword(string password) {
|
||||||
|
IDictionary<string, LocalizedString> validationErrors;
|
||||||
|
|
||||||
|
if (!_userService.PasswordMeetsPolicies(password, out validationErrors)) {
|
||||||
|
foreach (var error in validationErrors) {
|
||||||
|
ModelState.AddModelError(error.Key, error.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string ErrorCodeToString(MembershipCreateStatus createStatus) {
|
private static string ErrorCodeToString(MembershipCreateStatus createStatus) {
|
||||||
// See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
|
// See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
|
||||||
// a full list of status codes.
|
// a full list of status codes.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -8,16 +9,15 @@ using Orchard.Core.Settings.Models;
|
|||||||
using Orchard.DisplayManagement;
|
using Orchard.DisplayManagement;
|
||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using Orchard.Mvc;
|
using Orchard.Mvc;
|
||||||
|
using Orchard.Mvc.Extensions;
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
|
using Orchard.Settings;
|
||||||
|
using Orchard.UI.Navigation;
|
||||||
using Orchard.UI.Notify;
|
using Orchard.UI.Notify;
|
||||||
using Orchard.Users.Events;
|
using Orchard.Users.Events;
|
||||||
using Orchard.Users.Models;
|
using Orchard.Users.Models;
|
||||||
using Orchard.Users.Services;
|
using Orchard.Users.Services;
|
||||||
using Orchard.Users.ViewModels;
|
using Orchard.Users.ViewModels;
|
||||||
using Orchard.Mvc.Extensions;
|
|
||||||
using System;
|
|
||||||
using Orchard.Settings;
|
|
||||||
using Orchard.UI.Navigation;
|
|
||||||
using Orchard.Utility.Extensions;
|
using Orchard.Utility.Extensions;
|
||||||
|
|
||||||
namespace Orchard.Users.Controllers {
|
namespace Orchard.Users.Controllers {
|
||||||
@@ -35,6 +35,7 @@ namespace Orchard.Users.Controllers {
|
|||||||
IShapeFactory shapeFactory,
|
IShapeFactory shapeFactory,
|
||||||
IUserEventHandler userEventHandlers,
|
IUserEventHandler userEventHandlers,
|
||||||
ISiteService siteService) {
|
ISiteService siteService) {
|
||||||
|
|
||||||
Services = services;
|
Services = services;
|
||||||
_membershipService = membershipService;
|
_membershipService = membershipService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
@@ -189,6 +190,12 @@ namespace Orchard.Users.Controllers {
|
|||||||
AddModelError("ConfirmPassword", T("Password confirmation must match"));
|
AddModelError("ConfirmPassword", T("Password confirmation must match"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IDictionary<string, LocalizedString> validationErrors;
|
||||||
|
|
||||||
|
if (!_userService.PasswordMeetsPolicies(createModel.Password, out validationErrors)) {
|
||||||
|
ModelState.AddModelErrors(validationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
var user = Services.ContentManager.New<IUser>("User");
|
var user = Services.ContentManager.New<IUser>("User");
|
||||||
if (ModelState.IsValid) {
|
if (ModelState.IsValid) {
|
||||||
user = _membershipService.CreateUser(new CreateUserParams(
|
user = _membershipService.CreateUser(new CreateUserParams(
|
||||||
|
|||||||
@@ -4,18 +4,26 @@ using Orchard.Environment.Extensions;
|
|||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
using Orchard.Users.Models;
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.Users.Services;
|
||||||
using Orchard.Users.ViewModels;
|
using Orchard.Users.ViewModels;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Web.Mvc;
|
||||||
|
|
||||||
namespace Orchard.Users.Drivers{
|
namespace Orchard.Users.Drivers{
|
||||||
|
|
||||||
[OrchardFeature("Orchard.Users.PasswordEditor")]
|
[OrchardFeature("Orchard.Users.PasswordEditor")]
|
||||||
public class UserPartPasswordDriver : ContentPartDriver<UserPart> {
|
public class UserPartPasswordDriver : ContentPartDriver<UserPart> {
|
||||||
private readonly IMembershipService _membershipService;
|
private readonly IMembershipService _membershipService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
public Localizer T { get; set; }
|
public Localizer T { get; set; }
|
||||||
|
|
||||||
public UserPartPasswordDriver(IMembershipService membershipService) {
|
public UserPartPasswordDriver(
|
||||||
|
MembershipService membershipService,
|
||||||
|
IUserService userService) {
|
||||||
|
|
||||||
_membershipService = membershipService;
|
_membershipService = membershipService;
|
||||||
|
_userService = userService;
|
||||||
T = NullLocalizer.Instance;
|
T = NullLocalizer.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +51,11 @@ namespace Orchard.Users.Drivers{
|
|||||||
_membershipService.SetPassword(actUser, editModel.Password);
|
_membershipService.SetPassword(actUser, editModel.Password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IDictionary<string, LocalizedString> validationErrors;
|
||||||
|
if (!_userService.PasswordMeetsPolicies(editModel.Password, out validationErrors)) {
|
||||||
|
updater.AddModelErrors(validationErrors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Editor(part, shapeHelper);
|
return Editor(part, shapeHelper);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Orchard.Security {
|
||||||
|
public static class MembershipSettingsExtensions {
|
||||||
|
public static int GetMinimumPasswordLength(this IMembershipSettings membershipSettings) {
|
||||||
|
return membershipSettings.EnableCustomPasswordPolicy ? membershipSettings.MinimumPasswordLength : 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Orchard.Localization;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Orchard.Mvc.Extensions;
|
||||||
|
|
||||||
|
namespace System.Web.Mvc {
|
||||||
|
public static class ModelStateDictionaryExtensions {
|
||||||
|
public static void AddModelErrors(this ModelStateDictionary modelStateDictionary, IDictionary<string, LocalizedString> validationErrors) {
|
||||||
|
foreach (var error in validationErrors) {
|
||||||
|
modelStateDictionary.AddModelError(error.Key, error.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Orchard.Localization;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Orchard.ContentManagement {
|
||||||
|
public static class UpdateModelExtensions {
|
||||||
|
public static void AddModelErrors(this IUpdateModel updateModel, IDictionary<string, LocalizedString> validationErrors) {
|
||||||
|
foreach (var error in validationErrors) {
|
||||||
|
updateModel.AddModelError(error.Key, error.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using Orchard.ContentManagement.MetaData;
|
||||||
using Orchard.ContentManagement.MetaData;
|
|
||||||
using Orchard.Core.Contents.Extensions;
|
using Orchard.Core.Contents.Extensions;
|
||||||
using Orchard.Data.Migration;
|
using Orchard.Data.Migration;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Orchard.Users {
|
namespace Orchard.Users {
|
||||||
public class UsersDataMigration : DataMigrationImpl {
|
public class UsersDataMigration : DataMigrationImpl {
|
||||||
@@ -23,11 +23,12 @@ namespace Orchard.Users {
|
|||||||
.Column<DateTime>("CreatedUtc")
|
.Column<DateTime>("CreatedUtc")
|
||||||
.Column<DateTime>("LastLoginUtc")
|
.Column<DateTime>("LastLoginUtc")
|
||||||
.Column<DateTime>("LastLogoutUtc")
|
.Column<DateTime>("LastLogoutUtc")
|
||||||
|
.Column<DateTime>("LastPasswordChangeUtc")
|
||||||
);
|
);
|
||||||
|
|
||||||
ContentDefinitionManager.AlterTypeDefinition("User", cfg => cfg.Creatable(false));
|
ContentDefinitionManager.AlterTypeDefinition("User", cfg => cfg.Creatable(false));
|
||||||
|
|
||||||
return 4;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int UpdateFrom1() {
|
public int UpdateFrom1() {
|
||||||
@@ -54,5 +55,14 @@ namespace Orchard.Users {
|
|||||||
|
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int UpdateFrom4() {
|
||||||
|
SchemaBuilder.AlterTable("UserPartRecord",
|
||||||
|
table => {
|
||||||
|
table.AddColumn<DateTime>("LastPasswordChangeUtc");
|
||||||
|
});
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
|
using Orchard.Security;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Web.Security;
|
||||||
|
|
||||||
namespace Orchard.Users.Models {
|
namespace Orchard.Users.Models {
|
||||||
public class RegistrationSettingsPart : ContentPart {
|
public class RegistrationSettingsPart : ContentPart, IMembershipSettings {
|
||||||
public bool UsersCanRegister {
|
public bool UsersCanRegister {
|
||||||
get { return this.Retrieve(x => x.UsersCanRegister); }
|
get { return this.Retrieve(x => x.UsersCanRegister); }
|
||||||
set { this.Store(x => x.UsersCanRegister, value); }
|
set { this.Store(x => x.UsersCanRegister, value); }
|
||||||
@@ -42,5 +45,51 @@ namespace Orchard.Users.Models {
|
|||||||
set { this.Store(x => x.EnableLostPassword, value); }
|
set { this.Store(x => x.EnableLostPassword, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool EnableCustomPasswordPolicy {
|
||||||
|
get { return this.Retrieve(x => x.EnableCustomPasswordPolicy); }
|
||||||
|
set { this.Store(x => x.EnableCustomPasswordPolicy, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "The minimum password length must be at least 1.")]
|
||||||
|
public int MinimumPasswordLength {
|
||||||
|
get { return this.Retrieve(x => x.MinimumPasswordLength, 7); }
|
||||||
|
set { this.Store(x => x.MinimumPasswordLength, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePasswordUppercaseRequirement {
|
||||||
|
get { return this.Retrieve(x => x.EnablePasswordUppercaseRequirement); }
|
||||||
|
set { this.Store(x => x.EnablePasswordUppercaseRequirement, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePasswordLowercaseRequirement {
|
||||||
|
get { return this.Retrieve(x => x.EnablePasswordLowercaseRequirement); }
|
||||||
|
set { this.Store(x => x.EnablePasswordLowercaseRequirement, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePasswordNumberRequirement {
|
||||||
|
get { return this.Retrieve(x => x.EnablePasswordNumberRequirement); }
|
||||||
|
set { this.Store(x => x.EnablePasswordNumberRequirement, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePasswordSpecialRequirement {
|
||||||
|
get { return this.Retrieve(x => x.EnablePasswordSpecialRequirement); }
|
||||||
|
set { this.Store(x => x.EnablePasswordSpecialRequirement, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePasswordExpiration {
|
||||||
|
get { return this.Retrieve(x => x.EnablePasswordExpiration); }
|
||||||
|
set { this.Store(x => x.EnablePasswordExpiration, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "The password expiration time must be a minimum of 1 day.")]
|
||||||
|
public int PasswordExpirationTimeInDays {
|
||||||
|
get { return this.Retrieve(x => x.PasswordExpirationTimeInDays, 30); }
|
||||||
|
set { this.Store(x => x.PasswordExpirationTimeInDays, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public MembershipPasswordFormat PasswordFormat {
|
||||||
|
get { return this.Retrieve(x => x.PasswordFormat, MembershipPasswordFormat.Hashed); }
|
||||||
|
set { this.Store(x => x.PasswordFormat, value); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using Orchard.ContentManagement;
|
||||||
using System.Web.Security;
|
|
||||||
using Orchard.ContentManagement;
|
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
|
using System;
|
||||||
|
using System.Web.Security;
|
||||||
|
|
||||||
namespace Orchard.Users.Models {
|
namespace Orchard.Users.Models {
|
||||||
public sealed class UserPart : ContentPart<UserPartRecord>, IUser {
|
public sealed class UserPart : ContentPart<UserPartRecord>, IUser {
|
||||||
@@ -76,5 +76,10 @@ namespace Orchard.Users.Models {
|
|||||||
get { return Retrieve(x => x.LastLogoutUtc); }
|
get { return Retrieve(x => x.LastLogoutUtc); }
|
||||||
set { Store(x => x.LastLogoutUtc, value); }
|
set { Store(x => x.LastLogoutUtc, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DateTime? LastPasswordChangeUtc {
|
||||||
|
get { return Retrieve(x => x.LastPasswordChangeUtc); }
|
||||||
|
set { Store(x => x.LastPasswordChangeUtc, value); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
using Orchard.ContentManagement.Records;
|
||||||
using System;
|
using System;
|
||||||
using System.Web.Security;
|
using System.Web.Security;
|
||||||
using Orchard.ContentManagement.Records;
|
|
||||||
|
|
||||||
namespace Orchard.Users.Models {
|
namespace Orchard.Users.Models {
|
||||||
public class UserPartRecord : ContentPartRecord {
|
public class UserPartRecord : ContentPartRecord {
|
||||||
@@ -19,5 +19,6 @@ namespace Orchard.Users.Models {
|
|||||||
public virtual DateTime? CreatedUtc { get; set; }
|
public virtual DateTime? CreatedUtc { get; set; }
|
||||||
public virtual DateTime? LastLoginUtc { get; set; }
|
public virtual DateTime? LastLoginUtc { get; set; }
|
||||||
public virtual DateTime? LastLogoutUtc { get; set; }
|
public virtual DateTime? LastLogoutUtc { get; set; }
|
||||||
|
public virtual DateTime? LastPasswordChangeUtc { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,15 +99,19 @@
|
|||||||
<Compile Include="Activities\UserActivity.cs" />
|
<Compile Include="Activities\UserActivity.cs" />
|
||||||
<Compile Include="Activities\UserIsApprovedActivity.cs" />
|
<Compile Include="Activities\UserIsApprovedActivity.cs" />
|
||||||
<Compile Include="Commands\UserCommands.cs" />
|
<Compile Include="Commands\UserCommands.cs" />
|
||||||
|
<Compile Include="Constants\UserPasswordValidationResults.cs" />
|
||||||
<Compile Include="Controllers\AccountController.cs" />
|
<Compile Include="Controllers\AccountController.cs" />
|
||||||
<Compile Include="Controllers\AdminController.cs" />
|
<Compile Include="Controllers\AdminController.cs" />
|
||||||
<Compile Include="Drivers\UserPartDriver.cs" />
|
<Compile Include="Drivers\UserPartDriver.cs" />
|
||||||
<Compile Include="Drivers\UserPartPasswordDriver.cs" />
|
<Compile Include="Drivers\UserPartPasswordDriver.cs" />
|
||||||
<Compile Include="Events\LoginUserEventHandler.cs" />
|
<Compile Include="Events\LoginUserEventHandler.cs" />
|
||||||
|
<Compile Include="Extensions\MembershipSettingsExtensions.cs" />
|
||||||
|
<Compile Include="Extensions\UpdateModelExtensions.cs" />
|
||||||
<Compile Include="Forms\SignInUserForm.cs" />
|
<Compile Include="Forms\SignInUserForm.cs" />
|
||||||
<Compile Include="Forms\VerifyUserUnicityForm.cs" />
|
<Compile Include="Forms\VerifyUserUnicityForm.cs" />
|
||||||
<Compile Include="Forms\CreateUserForm.cs" />
|
<Compile Include="Forms\CreateUserForm.cs" />
|
||||||
<Compile Include="Handlers\WorkflowUserEventHandler.cs" />
|
<Compile Include="Handlers\WorkflowUserEventHandler.cs" />
|
||||||
|
<Compile Include="Extensions\ModelStateDistionaryExtensions.cs" />
|
||||||
<Compile Include="Migrations.cs" />
|
<Compile Include="Migrations.cs" />
|
||||||
<Compile Include="Events\UserContext.cs" />
|
<Compile Include="Events\UserContext.cs" />
|
||||||
<Compile Include="Handlers\RegistrationSettingsPartHandler.cs" />
|
<Compile Include="Handlers\RegistrationSettingsPartHandler.cs" />
|
||||||
@@ -211,7 +215,9 @@
|
|||||||
<Content Include="Views\LogOn.cshtml" />
|
<Content Include="Views\LogOn.cshtml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Web.config" />
|
<Content Include="Web.config">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Styles\Web.config">
|
<Content Include="Styles\Web.config">
|
||||||
@@ -236,6 +242,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="packages.config" />
|
<Content Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Views\Account\ChangeExpiredPassword.cshtml" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using Orchard.Localization;
|
||||||
using Orchard.Security;
|
using Orchard.Security;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
public interface IUserService : IDependency {
|
public interface IUserService : IDependency {
|
||||||
bool VerifyUserUnicity(string userName, string email);
|
bool VerifyUserUnicity(string userName, string email);
|
||||||
@@ -13,5 +16,7 @@ namespace Orchard.Users.Services {
|
|||||||
|
|
||||||
string CreateNonce(IUser user, TimeSpan delay);
|
string CreateNonce(IUser user, TimeSpan delay);
|
||||||
bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc);
|
bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc);
|
||||||
|
|
||||||
|
bool PasswordMeetsPolicies(string password, out IDictionary<string, LocalizedString> validationErrors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
using System;
|
using Orchard.ContentManagement;
|
||||||
|
using Orchard.DisplayManagement;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
|
using Orchard.Environment.Extensions;
|
||||||
|
using Orchard.Localization;
|
||||||
|
using Orchard.Logging;
|
||||||
|
using Orchard.Messaging.Services;
|
||||||
|
using Orchard.Security;
|
||||||
|
using Orchard.Services;
|
||||||
|
using Orchard.Users.Events;
|
||||||
|
using Orchard.Users.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Web.Security;
|
|
||||||
using Orchard.DisplayManagement;
|
|
||||||
using Orchard.Localization;
|
|
||||||
using Orchard.Logging;
|
|
||||||
using Orchard.ContentManagement;
|
|
||||||
using Orchard.Security;
|
|
||||||
using Orchard.Users.Events;
|
|
||||||
using Orchard.Users.Models;
|
|
||||||
using Orchard.Messaging.Services;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Orchard.Services;
|
|
||||||
using System.Web.Helpers;
|
using System.Web.Helpers;
|
||||||
using Orchard.Environment.Configuration;
|
using System.Web.Security;
|
||||||
using Orchard.Environment.Extensions;
|
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
[OrchardSuppressDependency("Orchard.Security.NullMembershipService")]
|
[OrchardSuppressDependency("Orchard.Security.NullMembershipService")]
|
||||||
@@ -56,11 +56,9 @@ namespace Orchard.Users.Services {
|
|||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
public Localizer T { get; set; }
|
public Localizer T { get; set; }
|
||||||
|
|
||||||
public MembershipSettings GetSettings() {
|
public IMembershipSettings GetSettings(){
|
||||||
var settings = new MembershipSettings();
|
return _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
|
||||||
// accepting defaults
|
}
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IUser CreateUser(CreateUserParams createUserParams) {
|
public IUser CreateUser(CreateUserParams createUserParams) {
|
||||||
Logger.Information("CreateUser {0} {1}", createUserParams.Username, createUserParams.Email);
|
Logger.Information("CreateUser {0} {1}", createUserParams.Username, createUserParams.Email);
|
||||||
@@ -157,6 +155,10 @@ namespace Orchard.Users.Services {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PasswordIsExpired(IUser user, int days){
|
||||||
|
return user.As<UserPart>().LastPasswordChangeUtc.Value.AddDays(days) < _clock.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetPassword(IUser user, string password) {
|
public void SetPassword(IUser user, string password) {
|
||||||
if (!user.Is<UserPart>())
|
if (!user.Is<UserPart>())
|
||||||
throw new InvalidCastException();
|
throw new InvalidCastException();
|
||||||
@@ -176,6 +178,7 @@ namespace Orchard.Users.Services {
|
|||||||
default:
|
default:
|
||||||
throw new ApplicationException(T("Unexpected password format value").ToString());
|
throw new ApplicationException(T("Unexpected password format value").ToString());
|
||||||
}
|
}
|
||||||
|
userPart.LastPasswordChangeUtc = _clock.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidatePassword(UserPart userPart, string password) {
|
private bool ValidatePassword(UserPart userPart, string password) {
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
using System;
|
using Orchard.ContentManagement;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Orchard.DisplayManagement;
|
using Orchard.DisplayManagement;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.ContentManagement;
|
|
||||||
using Orchard.Settings;
|
|
||||||
using Orchard.Users.Models;
|
|
||||||
using Orchard.Security;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using Orchard.Services;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using Orchard.Messaging.Services;
|
using Orchard.Messaging.Services;
|
||||||
using Orchard.Environment.Configuration;
|
using Orchard.Security;
|
||||||
|
using Orchard.Services;
|
||||||
|
using Orchard.Settings;
|
||||||
|
using Orchard.Users.Constants;
|
||||||
|
using Orchard.Users.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
public class UserService : IUserService {
|
public class UserService : IUserService {
|
||||||
@@ -38,8 +40,8 @@ namespace Orchard.Users.Services {
|
|||||||
IEncryptionService encryptionService,
|
IEncryptionService encryptionService,
|
||||||
IShapeFactory shapeFactory,
|
IShapeFactory shapeFactory,
|
||||||
IShapeDisplay shapeDisplay,
|
IShapeDisplay shapeDisplay,
|
||||||
ISiteService siteService
|
ISiteService siteService) {
|
||||||
) {
|
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
_membershipService = membershipService;
|
_membershipService = membershipService;
|
||||||
_clock = clock;
|
_clock = clock;
|
||||||
@@ -48,7 +50,9 @@ namespace Orchard.Users.Services {
|
|||||||
_shapeFactory = shapeFactory;
|
_shapeFactory = shapeFactory;
|
||||||
_shapeDisplay = shapeDisplay;
|
_shapeDisplay = shapeDisplay;
|
||||||
_siteService = siteService;
|
_siteService = siteService;
|
||||||
|
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
|
T = NullLocalizer.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
@@ -194,5 +198,42 @@ namespace Orchard.Users.Services {
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PasswordMeetsPolicies(string password, out IDictionary<string, LocalizedString> validationErrors) {
|
||||||
|
validationErrors = new Dictionary<string, LocalizedString>();
|
||||||
|
var settings = _siteService.GetSiteSettings().As<RegistrationSettingsPart>();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(password)) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordIsTooShort,
|
||||||
|
T("The password can't be empty."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.Length < settings.GetMinimumPasswordLength()) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordIsTooShort,
|
||||||
|
T("You must specify a password of {0} or more characters.", settings.MinimumPasswordLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.EnableCustomPasswordPolicy) {
|
||||||
|
if (settings.EnablePasswordNumberRequirement && !Regex.Match(password, "[0-9]").Success) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordDoesNotContainNumbers,
|
||||||
|
T("The password must contain at least one number."));
|
||||||
|
}
|
||||||
|
if (settings.EnablePasswordUppercaseRequirement && !password.Any(c => char.IsUpper(c))) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordDoesNotContainUppercase,
|
||||||
|
T("The password must contain at least one uppercase letter."));
|
||||||
|
}
|
||||||
|
if (settings.EnablePasswordLowercaseRequirement && !password.Any(c => char.IsLower(c))) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordDoesNotContainLowercase,
|
||||||
|
T("The password must contain at least one lowercase letter."));
|
||||||
|
}
|
||||||
|
if (settings.EnablePasswordSpecialRequirement && !Regex.Match(password, "[^a-zA-Z0-9]").Success) {
|
||||||
|
validationErrors.Add(UserPasswordValidationResults.PasswordDoesNotContainSpecialCharacters,
|
||||||
|
T("The password must contain at least one special character."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationErrors.Count == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@ namespace Orchard.Users.ViewModels {
|
|||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
[Required, DataType(DataType.Password)]
|
[Required, DataType(DataType.Password)]
|
||||||
[StringLength(50, MinimumLength = 7)]
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[Required, DataType(DataType.Password)]
|
[Required, DataType(DataType.Password)]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace Orchard.Users.ViewModels
|
|||||||
[OrchardFeature("Orchard.Users.EditPasswordByAdmin")]
|
[OrchardFeature("Orchard.Users.EditPasswordByAdmin")]
|
||||||
public class UserEditPasswordViewModel {
|
public class UserEditPasswordViewModel {
|
||||||
[DataType(DataType.Password)]
|
[DataType(DataType.Password)]
|
||||||
[StringLength(50, MinimumLength = 7)]
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[DataType(DataType.Password)]
|
[DataType(DataType.Password)]
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@model dynamic
|
||||||
|
<h1>@Html.TitleForPage(T("Change Expired Password"))</h1>
|
||||||
|
<p>@T("Your password has expired. Use the form below to change your password.")</p>
|
||||||
|
<p>@T.Plural("The password can't be empty.", "Passwords are required to be a minimum of {0} characters in length.", (int)Model.PasswordLength)</p>
|
||||||
|
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").Text)
|
||||||
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
|
<fieldset>
|
||||||
|
<legend>@T("Account Information")</legend>
|
||||||
|
<div>
|
||||||
|
@T("Username: {0}", Model.Username)
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="currentPassword">@T("Current Password:")</label>
|
||||||
|
@Html.Password("currentPassword")
|
||||||
|
@Html.ValidationMessage("currentPassword")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="newPassword">@T("New Password:")</label>
|
||||||
|
@Html.Password("newPassword")
|
||||||
|
@Html.ValidationMessage("newPassword")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="confirmPassword">@T("Confirm New Password:")</label>
|
||||||
|
@Html.Password("confirmPassword")
|
||||||
|
@Html.ValidationMessage("confirmPassword")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="primaryAction" type="submit">@T("Change Password")</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@model dynamic
|
@model dynamic
|
||||||
<h1>@Html.TitleForPage(T("Change Password").ToString()) </h1>
|
<h1>@Html.TitleForPage(T("Change Password").ToString()) </h1>
|
||||||
<p>@T("Use the form below to change your password.")</p>
|
<p>@T("Use the form below to change your password.")</p>
|
||||||
<p>@T("New passwords are required to be a minimum of {0} characters in length.", ViewData["PasswordLength"]) </p>
|
<p>@T.Plural("The password can't be empty.", "New passwords are required to be a minimum of {0} characters in length.", (int)ViewData["PasswordLength"])</p>
|
||||||
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").ToString())
|
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").ToString())
|
||||||
@using (Html.BeginFormAntiForgeryPost()) {
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@model dynamic
|
@model dynamic
|
||||||
<h1>@Html.TitleForPage(T("Change Password").ToString()) </h1>
|
<h1>@Html.TitleForPage(T("Change Password").ToString()) </h1>
|
||||||
<p>@T("Use the form below to change your password.")</p>
|
<p>@T("Use the form below to change your password.")</p>
|
||||||
<p>@T("New passwords are required to be a minimum of {0} characters in length.", ViewData["PasswordLength"]) </p>
|
<p>@T.Plural("The password can't be empty.", "New passwords are required to be a minimum of {0} characters in length.", (int)ViewData["PasswordLength"])</p>
|
||||||
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").ToString())
|
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").ToString())
|
||||||
@using (Html.BeginFormAntiForgeryPost()) {
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@model Orchard.Users.Models.RegistrationSettingsPart
|
@model Orchard.Users.Models.RegistrationSettingsPart
|
||||||
@using Orchard.Messaging.Services;
|
@using Orchard.Messaging.Services;
|
||||||
|
@using System.Web.Security;
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var messageManager = WorkContext.Resolve<IMessageManager>();
|
var messageManager = WorkContext.Resolve<IMessageManager>();
|
||||||
@@ -9,13 +10,69 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>@T("Users")</legend>
|
<legend>@T("Users")</legend>
|
||||||
<div>
|
<div>
|
||||||
@Html.EditorFor(m => m.UsersCanRegister)
|
@Html.EditorFor(m => m.UsersCanRegister)
|
||||||
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.UsersCanRegister)">@T("Users can create new accounts on the site")</label>
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.UsersCanRegister)">@T("Users can create new accounts on the site")</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
@Html.EditorFor(m => m.EnableCustomPasswordPolicy)
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnableCustomPasswordPolicy)">@T("Passwords must meet custom requirements")</label>
|
||||||
|
<div data-controllerid="@Html.FieldIdFor(m => m.EnableCustomPasswordPolicy)" style="margin-left: 30px;">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnablePasswordLowercaseRequirement)"
|
||||||
|
name="@Html.FieldNameFor(m => m.EnablePasswordLowercaseRequirement)" @(Model.EnablePasswordLowercaseRequirement && Model.EnableCustomPasswordPolicy ? "checked=\"checked\"" : "") />
|
||||||
|
<input name="@Html.FieldNameFor(m => m.EnablePasswordLowercaseRequirement)" type="hidden" value="false">
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnablePasswordLowercaseRequirement)">@T("Password must contain at least one lower case letter (a-z)")</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnablePasswordUppercaseRequirement)"
|
||||||
|
name="@Html.FieldNameFor(m => m.EnablePasswordUppercaseRequirement)" @(Model.EnablePasswordUppercaseRequirement && Model.EnableCustomPasswordPolicy ? "checked=\"checked\"" : "") />
|
||||||
|
<input name="@Html.FieldNameFor(m => m.EnablePasswordUppercaseRequirement)" type="hidden" value="false">
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnablePasswordUppercaseRequirement)">@T("Password must contain at least one upper case letter (A-Z)")</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnablePasswordNumberRequirement)"
|
||||||
|
name="@Html.FieldNameFor(m => m.EnablePasswordNumberRequirement)" @(Model.EnablePasswordNumberRequirement && Model.EnableCustomPasswordPolicy ? "checked=\"checked\"" : "") />
|
||||||
|
<input name="@Html.FieldNameFor(m => m.EnablePasswordNumberRequirement)" type="hidden" value="false">
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnablePasswordNumberRequirement)">@T("Password must contain at least one number (0-9)")</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnablePasswordSpecialRequirement)"
|
||||||
|
name="@Html.FieldNameFor(m => m.EnablePasswordSpecialRequirement)" @(Model.EnablePasswordSpecialRequirement && Model.EnableCustomPasswordPolicy ? "checked=\"checked\"" : "") />
|
||||||
|
<input name="@Html.FieldNameFor(m => m.EnablePasswordSpecialRequirement)" type="hidden" value="false">
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnablePasswordSpecialRequirement)">@T("Password must contain at least one special character")</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="@Html.FieldIdFor(m => m.MinimumPasswordLength)">@T("Minimum Password length")</label>
|
||||||
|
@Html.TextBoxFor(m => m.MinimumPasswordLength, new { @class = "text medium", @Value = Model.MinimumPasswordLength })
|
||||||
|
@Html.ValidationMessage("MinimumPasswordLength", "*")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnablePasswordExpiration)"
|
||||||
|
name="@Html.FieldNameFor(m => m.EnablePasswordExpiration)" @(Model.EnablePasswordExpiration && Model.EnableCustomPasswordPolicy ? "checked=\"checked\"" : "") />
|
||||||
|
<input name="@Html.FieldNameFor(m => m.EnablePasswordExpiration)" type="hidden" value="false">
|
||||||
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnablePasswordExpiration)">@T("Password expires after a time period (30 days by default)")</label>
|
||||||
|
</div>
|
||||||
|
<div data-controllerid="@Html.FieldIdFor(m => m.EnablePasswordExpiration)" style="margin-left: 30px;">
|
||||||
|
<label for="@Html.FieldIdFor(m => m.PasswordExpirationTimeInDays)">@T("Password expiration period in days")</label>
|
||||||
|
@Html.TextBoxFor(m => m.PasswordExpirationTimeInDays, new { @class = "text medium", @Value = Model.PasswordExpirationTimeInDays })
|
||||||
|
@Html.ValidationMessage("PasswordExpirationTimeInDays", "*")
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="@Html.FieldIdFor(m => m.PasswordFormat)">@T("Password format")</label>
|
||||||
|
@Html.DropDownListFor(m => m.PasswordFormat, new SelectList(new[]
|
||||||
|
{
|
||||||
|
new SelectListItem { Text = T("Clear").Text, Value = MembershipPasswordFormat.Clear.ToString() },
|
||||||
|
new SelectListItem { Text = T("Hashed").Text, Value = MembershipPasswordFormat.Hashed.ToString(), Selected = true },
|
||||||
|
new SelectListItem { Text = T("Encrypted").Text, Value = MembershipPasswordFormat.Encrypted.ToString() }
|
||||||
|
}, "Value", "Text"))
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnableLostPassword)" name="@Html.FieldNameFor(m => m.EnableLostPassword)" @(Model.EnableLostPassword ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.EnableLostPassword)" name="@Html.FieldNameFor(m => m.EnableLostPassword)" @(Model.EnableLostPassword ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
||||||
<input name="@Html.FieldNameFor(m => m.EnableLostPassword)" type="hidden" value="false">
|
<input name="@Html.FieldNameFor(m => m.EnableLostPassword)" type="hidden" value="false">
|
||||||
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.EnableLostPassword)">@T("Display a link to enable users to reset their password")</label>
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnableLostPassword)">@T("Display a link to enable users to reset their password")</label>
|
||||||
|
|
||||||
@if(!emailEnabled) {
|
@if(!emailEnabled) {
|
||||||
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
||||||
@@ -24,31 +81,31 @@
|
|||||||
<div>
|
<div>
|
||||||
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.UsersMustValidateEmail)" name="@Html.FieldNameFor(m => m.UsersMustValidateEmail)" @(Model.UsersMustValidateEmail ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.UsersMustValidateEmail)" name="@Html.FieldNameFor(m => m.UsersMustValidateEmail)" @(Model.UsersMustValidateEmail ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
||||||
<input name="@Html.FieldNameFor(m => m.UsersMustValidateEmail)" type="hidden" value="false">
|
<input name="@Html.FieldNameFor(m => m.UsersMustValidateEmail)" type="hidden" value="false">
|
||||||
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.UsersMustValidateEmail)">@T("Users must verify their email address")</label>
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.UsersMustValidateEmail)">@T("Users must verify their email address")</label>
|
||||||
|
|
||||||
@if(!emailEnabled) {
|
@if(!emailEnabled) {
|
||||||
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div data-controllerid="@Html.FieldIdFor(m => m.UsersMustValidateEmail)">
|
<div data-controllerid="@Html.FieldIdFor(m => m.UsersMustValidateEmail)">
|
||||||
<label for="@Html.FieldIdFor( m => m.ValidateEmailRegisteredWebsite)">@T("Website public name")</label>
|
<label for="@Html.FieldIdFor(m => m.ValidateEmailRegisteredWebsite)">@T("Website public name")</label>
|
||||||
@Html.TextBoxFor(m => m.ValidateEmailRegisteredWebsite, new { @class = "text medium" } )
|
@Html.TextBoxFor(m => m.ValidateEmailRegisteredWebsite, new { @class = "text medium" } )
|
||||||
@Html.ValidationMessage("ValidateEmailRegisteredWebsite", "*")
|
@Html.ValidationMessage("ValidateEmailRegisteredWebsite", "*")
|
||||||
<span class="hint">@T("The name of your website as it will appear in the verification e-mail.")</span>
|
<span class="hint">@T("The name of your website as it will appear in the verification e-mail.")</span>
|
||||||
|
|
||||||
<label for="@Html.FieldIdFor( m => m.ValidateEmailContactEMail)">@T("Contact Us E-Mail address")</label>
|
<label for="@Html.FieldIdFor(m => m.ValidateEmailContactEMail)">@T("Contact Us E-Mail address")</label>
|
||||||
@Html.TextBoxFor(m => m.ValidateEmailContactEMail, new { @class = "text medium" } )
|
@Html.TextBoxFor(m => m.ValidateEmailContactEMail, new { @class = "text medium" } )
|
||||||
@Html.ValidationMessage("ValidateEmailContactEMail", "*")
|
@Html.ValidationMessage("ValidateEmailContactEMail", "*")
|
||||||
<span class="hint">@T("The e-mail address displayed in the verification e-mail for a Contact Us link. Leave empty for no link.")</span>
|
<span class="hint">@T("The e-mail address displayed in the verification e-mail for a Contact Us link. Leave empty for no link.")</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@Html.EditorFor(m => m.UsersAreModerated)
|
@Html.EditorFor(m => m.UsersAreModerated)
|
||||||
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.UsersAreModerated)">@T("Users must be approved before they can log in")</label>
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.UsersAreModerated)">@T("Users must be approved before they can log in")</label>
|
||||||
</div>
|
</div>
|
||||||
<div data-controllerid="@Html.FieldIdFor(m => m.UsersAreModerated)">
|
<div data-controllerid="@Html.FieldIdFor(m => m.UsersAreModerated)">
|
||||||
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.NotifyModeration)" name="@Html.FieldNameFor(m => m.NotifyModeration)" @(Model.NotifyModeration ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
<input type="checkbox" value="true" class="check-box" id="@Html.FieldIdFor(m => m.NotifyModeration)" name="@Html.FieldNameFor(m => m.NotifyModeration)" @(Model.NotifyModeration ? "checked=\"checked\"" : "") @(emailEnabled ? "" : "disabled=\"disabled\"")/>
|
||||||
<input name="@Html.FieldNameFor(m => m.NotifyModeration)" type="hidden" value="false">
|
<input name="@Html.FieldNameFor(m => m.NotifyModeration)" type="hidden" value="false">
|
||||||
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.NotifyModeration)">@T("Send a notification when a user needs moderation")</label>
|
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.NotifyModeration)">@T("Send a notification when a user needs moderation")</label>
|
||||||
|
|
||||||
@if(!emailEnabled) {
|
@if(!emailEnabled) {
|
||||||
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
<div class="message message-Warning">@T("This option is available when an email module is activated.")</div>
|
||||||
@@ -56,7 +113,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div data-controllerid="@Html.FieldIdFor(m => m.NotifyModeration)">
|
<div data-controllerid="@Html.FieldIdFor(m => m.NotifyModeration)">
|
||||||
<label for="@Html.FieldIdFor( m => m.NotificationsRecipients)">@T("Moderators")</label>
|
<label for="@Html.FieldIdFor(m => m.NotificationsRecipients)">@T("Moderators")</label>
|
||||||
@Html.TextBoxFor(m => m.NotificationsRecipients, new { @class = "text medium" } )
|
@Html.TextBoxFor(m => m.NotificationsRecipients, new { @class = "text medium" } )
|
||||||
@Html.ValidationMessage("NotificationsRecipients", "*")
|
@Html.ValidationMessage("NotificationsRecipients", "*")
|
||||||
<span class="hint">@T("The usernames to send the notifications to (e.g., \"admin, user1, ...\").")</span>
|
<span class="hint">@T("The usernames to send the notifications to (e.g., \"admin, user1, ...\").")</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<h1>@Html.TitleForPage(T("Create a New Account").ToString())</h1>
|
<h1>@Html.TitleForPage(T("Create a New Account").ToString())</h1>
|
||||||
<p>@T("Use the form below to create a new account.")</p>
|
<p>@T("Use the form below to create a new account.")</p>
|
||||||
<p>@T("Passwords are required to be a minimum of {0} characters in length.", ViewData["PasswordLength"])</p>
|
<p>@T.Plural("The password can't be empty.", "Passwords are required to be a minimum of {0} characters in length.", (int)ViewData["PasswordLength"])</p>
|
||||||
@Html.ValidationSummary(T("Account creation was unsuccessful. Please correct the errors and try again.").ToString())
|
@Html.ValidationSummary(T("Account creation was unsuccessful. Please correct the errors and try again.").ToString())
|
||||||
@using (Html.BeginFormAntiForgeryPost(Url.Action("Register", new { ReturnUrl = Request.QueryString["ReturnUrl"] }))) {
|
@using (Html.BeginFormAntiForgeryPost(Url.Action("Register", new { ReturnUrl = Request.QueryString["ReturnUrl"] }))) {
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="email">@T("Email:")</label>
|
<label for="email">@T("Email:")</label>
|
||||||
@Html.TextBox("email")
|
@Html.TextBox("email")
|
||||||
@Html.ValidationMessage("email")
|
@Html.ValidationMessage("email")
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="password">@T("Password:")</label>
|
<label for="password">@T("Password:")</label>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
<add assembly="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
<add assembly="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
||||||
<add assembly="Orchard.Framework" />
|
<add assembly="Orchard.Framework" />
|
||||||
<add assembly="Orchard.Core" />
|
<add assembly="Orchard.Core" />
|
||||||
|
<add assembly="System.Web.ApplicationServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
||||||
</assemblies>
|
</assemblies>
|
||||||
</compilation>
|
</compilation>
|
||||||
</system.web>
|
</system.web>
|
||||||
|
|||||||
@@ -209,6 +209,7 @@
|
|||||||
<Compile Include="Reports\Services\ReportsCoordinator.cs" />
|
<Compile Include="Reports\Services\ReportsCoordinator.cs" />
|
||||||
<Compile Include="Reports\Services\ReportsManager.cs" />
|
<Compile Include="Reports\Services\ReportsManager.cs" />
|
||||||
<Compile Include="Reports\Services\ReportsPersister.cs" />
|
<Compile Include="Reports\Services\ReportsPersister.cs" />
|
||||||
|
<Compile Include="Security\IMembershipSettings.cs" />
|
||||||
<Compile Include="Security\IMembershipValidationService.cs" />
|
<Compile Include="Security\IMembershipValidationService.cs" />
|
||||||
<Compile Include="Localization\Services\ILocalizationStreamParser.cs" />
|
<Compile Include="Localization\Services\ILocalizationStreamParser.cs" />
|
||||||
<Compile Include="Localization\Services\LocalizationStreamParser.cs" />
|
<Compile Include="Localization\Services\LocalizationStreamParser.cs" />
|
||||||
@@ -939,7 +940,6 @@
|
|||||||
<Compile Include="Mvc\Wrappers\HttpResponseBaseWrapper.cs" />
|
<Compile Include="Mvc\Wrappers\HttpResponseBaseWrapper.cs" />
|
||||||
<Compile Include="OrchardException.cs" />
|
<Compile Include="OrchardException.cs" />
|
||||||
<Compile Include="Security\IAuthorizationServiceEventHandler.cs" />
|
<Compile Include="Security\IAuthorizationServiceEventHandler.cs" />
|
||||||
<Compile Include="Security\MembershipSettings.cs" />
|
|
||||||
<Compile Include="Security\StandardPermissions.cs" />
|
<Compile Include="Security\StandardPermissions.cs" />
|
||||||
<Compile Include="Security\OrchardSecurityException.cs" />
|
<Compile Include="Security\OrchardSecurityException.cs" />
|
||||||
<Compile Include="Tasks\Scheduling\IPublishingTaskManager.cs" />
|
<Compile Include="Tasks\Scheduling\IPublishingTaskManager.cs" />
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
namespace Orchard.Security {
|
namespace Orchard.Security {
|
||||||
public interface IMembershipService : IDependency {
|
public interface IMembershipService : IDependency {
|
||||||
MembershipSettings GetSettings();
|
IMembershipSettings GetSettings();
|
||||||
|
|
||||||
IUser CreateUser(CreateUserParams createUserParams);
|
IUser CreateUser(CreateUserParams createUserParams);
|
||||||
IUser GetUser(string username);
|
IUser GetUser(string username);
|
||||||
IUser ValidateUser(string userNameOrEmail, string password);
|
IUser ValidateUser(string userNameOrEmail, string password);
|
||||||
void SetPassword(IUser user, string password);
|
void SetPassword(IUser user, string password);
|
||||||
|
|
||||||
|
bool PasswordIsExpired(IUser user, int days);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/Orchard/Security/IMembershipSettings.cs
Normal file
23
src/Orchard/Security/IMembershipSettings.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Web.Security;
|
||||||
|
|
||||||
|
namespace Orchard.Security {
|
||||||
|
public interface IMembershipSettings {
|
||||||
|
bool UsersCanRegister { get; set; }
|
||||||
|
bool UsersMustValidateEmail { get; set; }
|
||||||
|
string ValidateEmailRegisteredWebsite { get; set; }
|
||||||
|
string ValidateEmailContactEMail { get; set; }
|
||||||
|
bool UsersAreModerated { get; set; }
|
||||||
|
bool NotifyModeration { get; set; }
|
||||||
|
string NotificationsRecipients { get; set; }
|
||||||
|
bool EnableLostPassword { get; set; }
|
||||||
|
bool EnableCustomPasswordPolicy { get; set; }
|
||||||
|
int MinimumPasswordLength { get; set; }
|
||||||
|
bool EnablePasswordUppercaseRequirement { get; set; }
|
||||||
|
bool EnablePasswordLowercaseRequirement { get; set; }
|
||||||
|
bool EnablePasswordNumberRequirement { get; set; }
|
||||||
|
bool EnablePasswordSpecialRequirement { get; set; }
|
||||||
|
bool EnablePasswordExpiration { get; set; }
|
||||||
|
int PasswordExpirationTimeInDays { get; set; }
|
||||||
|
MembershipPasswordFormat PasswordFormat { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using System.Web.Security;
|
|
||||||
|
|
||||||
namespace Orchard.Security {
|
|
||||||
public class MembershipSettings {
|
|
||||||
public MembershipSettings() {
|
|
||||||
EnablePasswordRetrieval = false;
|
|
||||||
EnablePasswordReset = true;
|
|
||||||
RequiresQuestionAndAnswer = true;
|
|
||||||
RequiresUniqueEmail = true;
|
|
||||||
MaxInvalidPasswordAttempts = 5;
|
|
||||||
PasswordAttemptWindow = 10;
|
|
||||||
MinRequiredPasswordLength = 7;
|
|
||||||
MinRequiredNonAlphanumericCharacters = 1;
|
|
||||||
PasswordStrengthRegularExpression = "";
|
|
||||||
PasswordFormat = MembershipPasswordFormat.Hashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EnablePasswordRetrieval { get; set; }
|
|
||||||
public bool EnablePasswordReset { get; set; }
|
|
||||||
public bool RequiresQuestionAndAnswer { get; set; }
|
|
||||||
public int MaxInvalidPasswordAttempts { get; set; }
|
|
||||||
public int PasswordAttemptWindow { get; set; }
|
|
||||||
public bool RequiresUniqueEmail { get; set; }
|
|
||||||
public MembershipPasswordFormat PasswordFormat { get; set; }
|
|
||||||
public int MinRequiredPasswordLength { get; set; }
|
|
||||||
public int MinRequiredNonAlphanumericCharacters { get; set; }
|
|
||||||
public string PasswordStrengthRegularExpression { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ namespace Orchard.Security {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MembershipSettings GetSettings() {
|
public IMembershipSettings GetSettings() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,5 +26,9 @@ namespace Orchard.Security {
|
|||||||
public IUser ValidateUser(string userNameOrEmail, string password) {
|
public IUser ValidateUser(string userNameOrEmail, string password) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PasswordIsExpired(IUser user, int weeks) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user