Improving lost password functionality

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2010-11-30 17:19:13 -08:00
parent 4a474fffac
commit 081a3dc53b
13 changed files with 168 additions and 29 deletions

View File

@@ -231,13 +231,14 @@ namespace Orchard.Tests.Modules.Users.Controllers {
public void ResetPasswordLinkShouldBeSent() {
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
registrationSettings.UsersCanRegister = true;
registrationSettings.EnableLostPassword = true;
_session.Flush();
_controller.Register("bar", "bar@baz.com", "66554321", "66554321");
_session.Flush();
_controller.Url = new UrlHelper(new RequestContext(new HttpContextStub(), new RouteData()));
var result = _controller.LostPassword("bar");
var result = _controller.RequestLostPassword("bar");
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
Assert.That(((RedirectToRouteResult)result).RouteValues["action"], Is.EqualTo("LogOn"));

View File

@@ -141,12 +141,23 @@ namespace Orchard.Users.Controllers {
return Register();
}
public ActionResult LostPassword() {
public ActionResult RequestLostPassword() {
// ensure users can request lost password
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
if ( !registrationSettings.EnableLostPassword ) {
return HttpNotFound();
}
return View();
}
[HttpPost]
public ActionResult LostPassword(string username) {
public ActionResult RequestLostPassword(string username) {
// ensure users can request lost password
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
if ( !registrationSettings.EnableLostPassword ) {
return HttpNotFound();
}
if(String.IsNullOrWhiteSpace(username)){
_orchardServices.Notifier.Error(T("Invalid username or E-mail"));
@@ -160,17 +171,6 @@ namespace Orchard.Users.Controllers {
return RedirectToAction("LogOn");
}
public ActionResult ValidateLostPassword(string nonce) {
IUser user;
if (null != (user = _userService.ValidateLostPassword(nonce))) {
_authenticationService.SignIn(user, false);
return RedirectToAction("ChangePassword");
}
else {
return new RedirectResult("~/");
}
}
[Authorize]
public ActionResult ChangePassword() {
ViewData["PasswordLength"] = MinPasswordLength;
@@ -180,9 +180,53 @@ namespace Orchard.Users.Controllers {
[Authorize]
[HttpPost]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Exceptions result in password not being changed.")]
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
ViewData["PasswordLength"] = MinPasswordLength;
if ( !ValidateChangePassword(currentPassword, newPassword, confirmPassword) ) {
return View();
}
try {
var validated = _membershipService.ValidateUser(User.Identity.Name, currentPassword);
if ( validated != null ) {
_membershipService.SetPassword(validated, newPassword);
return RedirectToAction("ChangePasswordSuccess");
}
else {
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."));
return ChangePassword();
}
}
public ActionResult LostPassword(string nonce) {
if ( _userService.ValidateLostPassword(nonce) == null ) {
return RedirectToAction("LogOn");
}
ViewData["PasswordLength"] = MinPasswordLength;
return View();
}
[HttpPost]
public ActionResult LostPassword(string nonce, string newPassword, string confirmPassword) {
IUser user;
if ( (user = _userService.ValidateLostPassword(nonce)) == null ) {
return Redirect("~/");
}
ViewData["PasswordLength"] = MinPasswordLength;
if (newPassword == null || newPassword.Length < MinPasswordLength) {
ModelState.AddModelError("newPassword", T("You must specify a new password of {0} or more characters.", MinPasswordLength));
}
@@ -195,15 +239,15 @@ namespace Orchard.Users.Controllers {
return View();
}
_membershipService.SetPassword(_orchardServices.WorkContext.CurrentUser, newPassword);
_membershipService.SetPassword(user, newPassword);
return RedirectToAction("ChangePasswordSuccess");
}
public ActionResult RegistrationPending() {
public ActionResult ChangePasswordSuccess() {
return View();
}
public ActionResult ChangePasswordSuccess() {
public ActionResult RegistrationPending() {
return View();
}
@@ -237,6 +281,21 @@ namespace Orchard.Users.Controllers {
}
#region Validation Methods
private bool ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword) {
if ( String.IsNullOrEmpty(currentPassword) ) {
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(newPassword, confirmPassword, StringComparison.Ordinal) ) {
ModelState.AddModelError("_FORM", T("The new password and confirmation password do not match."));
}
return ModelState.IsValid;
}
private IUser ValidateLogOn(string userNameOrEmail, string password) {
bool validate = true;

View File

@@ -5,10 +5,10 @@ using Orchard.ContentManagement;
using Orchard.Users.Models;
namespace Orchard.Users.Handlers {
public class ModerationMessageAlteration : IMessageEventHandler {
public class UserMessagesAlteration : IMessageEventHandler {
private readonly IContentManager _contentManager;
public ModerationMessageAlteration(IContentManager contentManager) {
public UserMessagesAlteration(IContentManager contentManager) {
_contentManager = contentManager;
T = NullLocalizer.Instance;
}

View File

@@ -26,6 +26,7 @@ namespace Orchard.Users {
.Column<bool>("UsersMustValidateEmail", c => c.WithDefault(false))
.Column<bool>("UsersAreModerated", c => c.WithDefault(false))
.Column<bool>("NotifyModeration", c => c.WithDefault(false))
.Column<bool>("EnableLostPassword", c => c.WithDefault(false))
);
return 1;

View File

@@ -23,5 +23,10 @@ namespace Orchard.Users.Models {
set { Record.NotifyModeration = value; }
}
public bool EnableLostPassword {
get { return Record.EnableLostPassword; }
set { Record.EnableLostPassword = value; }
}
}
}

View File

@@ -1,6 +1,4 @@
using System.Net.Mail;
using Orchard.ContentManagement.Records;
using System.ComponentModel.DataAnnotations;
namespace Orchard.Users.Models {
public class RegistrationSettingsPartRecord : ContentPartRecord {
@@ -8,5 +6,6 @@ namespace Orchard.Users.Models {
public virtual bool UsersMustValidateEmail { get; set; }
public virtual bool UsersAreModerated { get; set; }
public virtual bool NotifyModeration { get; set; }
public virtual bool EnableLostPassword { get; set; }
}
}

View File

@@ -59,7 +59,7 @@
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Events\UserContext.cs" />
<Compile Include="Handlers\ModerationMessageAlteration.cs" />
<Compile Include="Handlers\UserMessagesAlteration.cs" />
<Compile Include="Handlers\RegistrationSettingsPartHandler.cs" />
<Compile Include="Events\IUserEventHandler.cs" />
<Compile Include="Models\MessageTypes.cs" />
@@ -74,6 +74,7 @@
<Compile Include="Services\IUserService.cs" />
<Compile Include="Services\MembershipService.cs" />
<Compile Include="AdminMenu.cs" />
<Compile Include="Services\MissingSettingsBanner.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="ViewModels\LogOnViewModel.cs" />
<Compile Include="ViewModels\UserCreateViewModel.cs" />
@@ -123,6 +124,9 @@
<Content Include="Views\EditorTemplates\Parts\User.Edit.cshtml" />
<Content Include="Views\EditorTemplates\Parts\User.Create.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Account\RequestLostPassword.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Account\LostPassword.cshtml" />
</ItemGroup>

View File

@@ -0,0 +1,36 @@
using System.Linq;
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Messaging.Services;
using Orchard.UI.Admin.Notification;
using Orchard.UI.Notify;
using Orchard.Users.Models;
namespace Orchard.Users.Services {
public class MissingSettingsBanner : INotificationProvider {
private readonly IOrchardServices _orchardServices;
private readonly IMessageManager _messageManager;
public MissingSettingsBanner(IOrchardServices orchardServices, IMessageManager messageManager) {
_orchardServices = orchardServices;
_messageManager = messageManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<NotifyEntry> GetNotifications() {
var registrationSettings = _orchardServices.WorkContext.CurrentSite.As<RegistrationSettingsPart>();
if ( registrationSettings != null &&
( registrationSettings.UsersMustValidateEmail ||
registrationSettings.NotifyModeration ||
registrationSettings.EnableLostPassword ) &&
!_messageManager.GetAvailableChannelServices().Contains("email") ) {
yield return new NotifyEntry { Message = T("Some Orchard.User settings require an Email channel to be enabled."), Type = NotifyType.Warning };
}
}
}
}

View File

@@ -6,6 +6,11 @@
@using (Html.BeginFormAntiForgeryPost()) {
<fieldset>
<legend>@T("Account Information")</legend>
<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")

View File

@@ -3,13 +3,14 @@
@{
var userCanRegister = @WorkContext.CurrentSite.As<Orchard.Users.Models.RegistrationSettingsPart>().UsersCanRegister;
var enableLostPassword = @WorkContext.CurrentSite.As<Orchard.Users.Models.RegistrationSettingsPart>().EnableLostPassword;
}
<h1 class="page-title">@Html.TitleForPage(Model.Title)</h1>
<p>
@T("Please enter your username and password.")
@if(userCanRegister) { @Html.ActionLink(T("Register").Text, "Register") @T(" if you don't have an account.") }
@Html.ActionLink(T("Lost your Password?").Text, "LostPassword")
@if(enableLostPassword) { @Html.ActionLink(T("Lost your Password?").Text, "RequestLostPassword") }
</p>
@Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString())

View File

@@ -1,15 +1,23 @@
<h1>@Html.TitleForPage(T("Lost Password").ToString())</h1>
<p>@T("Please enter your username or email address. You will receive a link to create a new password via email.")</p>
@model dynamic
<h1>@Html.TitleForPage(T("Change Password").ToString()) </h1>
<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"] as string) </p>
@Html.ValidationSummary(T("Password change was unsuccessful. Please correct the errors and try again.").ToString())
@using (Html.BeginFormAntiForgeryPost()) {
<fieldset>
<legend>@T("Account Information")</legend>
<div>
<label for="username">@T("Username or E-mail:")</label>
@Html.TextBox("username")
@Html.ValidationMessage("username")
<label for="newPassword">@T("New password:")</label>
@Html.Password("newPassword")
@Html.ValidationMessage("newPassword")
</div>
<div>
<button class="primaryAction" type="submit">@T("Send Request")</button>
<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>
}

View File

@@ -0,0 +1,15 @@
<h1>@Html.TitleForPage(T("Lost Password").ToString())</h1>
<p>@T("Please enter your username or email address. You will receive a link to create a new password via email.")</p>
@using (Html.BeginFormAntiForgeryPost()) {
<fieldset>
<legend>@T("Account Information")</legend>
<div>
<label for="username">@T("Username or E-mail:")</label>
@Html.TextBox("username")
@Html.ValidationMessage("username")
</div>
<div>
<button class="primaryAction" type="submit">@T("Send Request")</button>
</div>
</fieldset>
}

View File

@@ -7,6 +7,11 @@
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.UsersCanRegister)">@T("Users can create new accounts on the site")</label>
@Html.ValidationMessage("UsersCanRegister", "*")
</div>
<div>
@Html.EditorFor(m => m.EnableLostPassword)
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.EnableLostPassword)">@T("Display a link to enable users to reset their email")</label>
@Html.ValidationMessage("EnableLostPassword", "*")
</div>
<div>
@Html.EditorFor(m => m.UsersMustValidateEmail)
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.UsersMustValidateEmail)">@T("Users must verify their email address")</label>