--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-11-26 18:10:18 -08:00
22 changed files with 272 additions and 130 deletions

View File

@@ -21,6 +21,10 @@ namespace Orchard.Core.Feeds.Services {
dynamic Shape { get; set; } dynamic Shape { get; set; }
public void OnResultExecuting(ResultExecutingContext filterContext) { public void OnResultExecuting(ResultExecutingContext filterContext) {
// should only run on a full view rendering result
if (!(filterContext.Result is ViewResult))
return;
var layout = _workContextAccessor.GetContext(filterContext).Layout; var layout = _workContextAccessor.GetContext(filterContext).Layout;
var feed = Shape.Feed() var feed = Shape.Feed()
.FeedManager(_feedManager); .FeedManager(_feedManager);

View File

@@ -72,8 +72,15 @@ namespace Orchard.Core.Routable.Handlers {
public class RoutePartHandlerBase : ContentHandlerBase { public class RoutePartHandlerBase : ContentHandlerBase {
public override void GetContentItemMetadata(GetContentItemMetadataContext context) { public override void GetContentItemMetadata(GetContentItemMetadataContext context) {
var routable = context.ContentItem.As<RoutePart>(); var routable = context.ContentItem.As<RoutePart>();
if (routable != null) {
context.Metadata.DisplayText = routable.Title; if (routable == null)
return;
context.Metadata.DisplayText = routable.Title;
// set the display route values if it hasn't been set or only has been set by the Contents module.
// allows other modules to set their own display. probably not common enough to warrant some priority implemntation
if (context.Metadata.DisplayRouteValues == null || context.Metadata.DisplayRouteValues["Area"] as string == "Contents") {
context.Metadata.DisplayRouteValues = new RouteValueDictionary { context.Metadata.DisplayRouteValues = new RouteValueDictionary {
{"Area", "Routable"}, {"Area", "Routable"},
{"Controller", "Item"}, {"Controller", "Item"},

View File

@@ -10,13 +10,16 @@ namespace Orchard.Core.Routable.Services {
[UsedImplicitly] [UsedImplicitly]
public class RoutableHomePageProvider : IHomePageProvider { public class RoutableHomePageProvider : IHomePageProvider {
private readonly IContentManager _contentManager; private readonly IContentManager _contentManager;
private readonly IWorkContextAccessor _workContextAccessor;
public const string Name = "RoutableHomePageProvider"; public const string Name = "RoutableHomePageProvider";
public RoutableHomePageProvider( public RoutableHomePageProvider(
IOrchardServices services, IOrchardServices services,
IContentManager contentManager, IContentManager contentManager,
IShapeFactory shapeFactory) { IShapeFactory shapeFactory,
IWorkContextAccessor workContextAccessor) {
_contentManager = contentManager; _contentManager = contentManager;
_workContextAccessor = workContextAccessor;
Services = services; Services = services;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
Shape = shapeFactory; Shape = shapeFactory;
@@ -39,11 +42,13 @@ namespace Orchard.Core.Routable.Services {
if (contentItem == null || !contentItem.Is<RoutePart>()) if (contentItem == null || !contentItem.Is<RoutePart>())
return new HttpNotFoundResult(); return new HttpNotFoundResult();
var model = _contentManager.BuildDisplay(contentItem); // get the display metadata for the home page item
var displayRouteValues = _contentManager.GetItemMetadata(contentItem).DisplayRouteValues;
return new ViewResult { var model = Shape.ViewModel(RouteValues: displayRouteValues);
return new PartialViewResult {
ViewName = "Routable.HomePage", ViewName = "Routable.HomePage",
ViewData = new ViewDataDictionary<dynamic>(model) ViewData = new ViewDataDictionary<object>(model)
}; };
} }
} }

View File

@@ -1 +1,4 @@
@Display(Model) @{
RouteValueDictionary routeValues = Model.RouteValues;
Html.RenderAction(routeValues["action"] as string, routeValues["controller"] as string, routeValues);
}

View File

@@ -3,6 +3,7 @@ using JetBrains.Annotations;
using Orchard.Blogs.Models; using Orchard.Blogs.Models;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers; using Orchard.ContentManagement.Handlers;
using Orchard.Core.Routable.Models;
using Orchard.Data; using Orchard.Data;
namespace Orchard.Blogs.Handlers { namespace Orchard.Blogs.Handlers {
@@ -23,6 +24,12 @@ namespace Orchard.Blogs.Handlers {
if (blog == null) if (blog == null)
return; return;
context.Metadata.DisplayRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Blogs"},
{"Controller", "Blog"},
{"Action", "Item"},
{"blogSlug", blog.As<RoutePart>().Slug}
};
context.Metadata.CreateRouteValues = new RouteValueDictionary { context.Metadata.CreateRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Blogs"}, {"Area", "Orchard.Blogs"},
{"Controller", "BlogAdmin"}, {"Controller", "BlogAdmin"},

View File

@@ -246,13 +246,13 @@ namespace Orchard.Setup.Services {
layerInitializer.CreateDefaultLayers(); layerInitializer.CreateDefaultLayers();
// add a layer for the homepage // add a layer for the homepage
var homepageLayer = contentManager.Create("Layer"); var homepageLayer = contentManager.Create("Layer", VersionOptions.Draft);
homepageLayer.As<LayerPart>().Name = "TheHomepage"; homepageLayer.As<LayerPart>().Name = "TheHomepage";
homepageLayer.As<LayerPart>().LayerRule = "url \"~/\""; homepageLayer.As<LayerPart>().LayerRule = "url \"~/\"";
contentManager.Publish(homepageLayer); contentManager.Publish(homepageLayer);
// and three more for the tripel...really need this elsewhere... // and three more for the tripel...really need this elsewhere...
var tripelFirst = contentManager.Create("HtmlWidget"); var tripelFirst = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelFirst.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>(); tripelFirst.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelFirst.As<WidgetPart>().Title = T("First Leader Aside").Text; tripelFirst.As<WidgetPart>().Title = T("First Leader Aside").Text;
tripelFirst.As<WidgetPart>().Zone = "TripelFirst"; tripelFirst.As<WidgetPart>().Zone = "TripelFirst";
@@ -260,7 +260,7 @@ namespace Orchard.Setup.Services {
tripelFirst.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>"; tripelFirst.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
contentManager.Publish(tripelFirst); contentManager.Publish(tripelFirst);
var tripelSecond = contentManager.Create("HtmlWidget"); var tripelSecond = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelSecond.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>(); tripelSecond.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelSecond.As<WidgetPart>().Title = T("Second Leader Aside").Text; tripelSecond.As<WidgetPart>().Title = T("Second Leader Aside").Text;
tripelSecond.As<WidgetPart>().Zone = "TripelSecond"; tripelSecond.As<WidgetPart>().Zone = "TripelSecond";
@@ -268,7 +268,7 @@ namespace Orchard.Setup.Services {
tripelSecond.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>"; tripelSecond.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
contentManager.Publish(tripelSecond); contentManager.Publish(tripelSecond);
var tripelThird = contentManager.Create("HtmlWidget"); var tripelThird = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelThird.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>(); tripelThird.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelThird.As<WidgetPart>().Title = T("Third Leader Aside").Text; tripelThird.As<WidgetPart>().Title = T("Third Leader Aside").Text;
tripelThird.As<WidgetPart>().Zone = "TripelThird"; tripelThird.As<WidgetPart>().Zone = "TripelThird";
@@ -278,8 +278,10 @@ namespace Orchard.Setup.Services {
} }
// create a welcome page that's promoted to the home page // create a welcome page that's promoted to the home page
var page = contentManager.Create("Page"); var page = contentManager.Create("Page", VersionOptions.Draft);
page.As<RoutePart>().Title = T("Welcome to Orchard!").Text; page.As<RoutePart>().Title = T("Welcome to Orchard!").Text;
page.As<RoutePart>().Path = "";
page.As<RoutePart>().Slug = "";
page.As<BodyPart>().Text = string.Format(CultureInfo.CurrentCulture, "<p>Youve successfully setup your Orchard Site and this is the homepage of your new site. Here are a few things you can look at to get familiar with the application. Once you feel confident you dont need this anymore, you can <a href=\"Admin/Contents/Edit/{0}\">remove this by going into editing mode</a> and replacing it with whatever you want.</p><p>First things first - Youll probably want to <a href=\"Admin/Settings\">manage your settings</a> and configure Orchard to your liking. After that, you can head over to <a href=\"Admin/Themes\">manage themes to change or install new themes</a> and really make it your own. Once youre happy with a look and feel, its time for some content. You can start creating new custom content types or start with some built-in ones by <a href=\"Admin/Pages/Create\">adding a page</a>, <a href=\"Admin/Blogs/Create\">creating a blog</a> or <a href=\"Admin/Navigation\">managing your menus.</a></p><p>Finally, Orchard has been designed to be extended. It comes with a few built-in modules such as pages and blogs or themes. If youre looking to add additional functionality, you can do so by creating your own module or installing a new one that someone has made. Modules are created by other users of Orchard just like you so if you feel up to it, <a href=\"http://www.orchardproject.net/\">please consider participating</a>. XOXO The Orchard Team </p>", page.Id); page.As<BodyPart>().Text = string.Format(CultureInfo.CurrentCulture, "<p>Youve successfully setup your Orchard Site and this is the homepage of your new site. Here are a few things you can look at to get familiar with the application. Once you feel confident you dont need this anymore, you can <a href=\"Admin/Contents/Edit/{0}\">remove this by going into editing mode</a> and replacing it with whatever you want.</p><p>First things first - Youll probably want to <a href=\"Admin/Settings\">manage your settings</a> and configure Orchard to your liking. After that, you can head over to <a href=\"Admin/Themes\">manage themes to change or install new themes</a> and really make it your own. Once youre happy with a look and feel, its time for some content. You can start creating new custom content types or start with some built-in ones by <a href=\"Admin/Pages/Create\">adding a page</a>, <a href=\"Admin/Blogs/Create\">creating a blog</a> or <a href=\"Admin/Navigation\">managing your menus.</a></p><p>Finally, Orchard has been designed to be extended. It comes with a few built-in modules such as pages and blogs or themes. If youre looking to add additional functionality, you can do so by creating your own module or installing a new one that someone has made. Modules are created by other users of Orchard just like you so if you feel up to it, <a href=\"http://www.orchardproject.net/\">please consider participating</a>. XOXO The Orchard Team </p>", page.Id);
contentManager.Publish(page); contentManager.Publish(page);

View File

@@ -12,6 +12,7 @@ using Orchard.Users.Services;
using Orchard.Users.ViewModels; using Orchard.Users.ViewModels;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Users.Models; using Orchard.Users.Models;
using Orchard.UI.Notify;
namespace Orchard.Users.Controllers { namespace Orchard.Users.Controllers {
[HandleError, Themed] [HandleError, Themed]
@@ -119,8 +120,8 @@ namespace Orchard.Users.Controllers {
if (user != null) { if (user != null) {
if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) { if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) {
string challengeToken = _membershipService.GetEncryptedChallengeToken(user.As<UserPart>()); string challengeToken = _userService.GetNonce(user.As<UserPart>());
_membershipService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken }))); _userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken })));
return RedirectToAction("ChallengeEmailSent"); return RedirectToAction("ChallengeEmailSent");
} }
@@ -141,6 +142,36 @@ namespace Orchard.Users.Controllers {
return Register(); return Register();
} }
public ActionResult LostPassword() {
return View();
}
[HttpPost]
public ActionResult LostPassword(string username) {
if(String.IsNullOrWhiteSpace(username)){
_orchardServices.Notifier.Error(T("Invalid username or E-mail"));
return View();
}
_userService.SendLostPasswordEmail(username, nonce => Url.AbsoluteAction(() => Url.Action("ValidateLostPassword", "Account", new { Area = "Orchard.Users", nonce = nonce })));
_orchardServices.Notifier.Information(T("Check your e-mail for the confirmation link."));
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] [Authorize]
public ActionResult ChangePassword() { public ActionResult ChangePassword() {
ViewData["PasswordLength"] = MinPasswordLength; ViewData["PasswordLength"] = MinPasswordLength;
@@ -150,32 +181,23 @@ namespace Orchard.Users.Controllers {
[Authorize] [Authorize]
[HttpPost] [HttpPost]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
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; ViewData["PasswordLength"] = MinPasswordLength;
if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword)) { 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."));
}
if (!ModelState.IsValid) {
return View(); return View();
} }
try { _membershipService.SetPassword(_orchardServices.WorkContext.CurrentUser, newPassword);
var validated = _membershipService.ValidateUser(User.Identity.Name, currentPassword); return RedirectToAction("ChangePasswordSuccess");
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 RegistrationPending() { public ActionResult RegistrationPending() {
@@ -199,7 +221,7 @@ namespace Orchard.Users.Controllers {
} }
public ActionResult ChallengeEmail(string token) { public ActionResult ChallengeEmail(string token) {
var user = _membershipService.ValidateChallengeToken(token); var user = _userService.ValidateChallenge(token);
if ( user != null ) { if ( user != null ) {
_authenticationService.SignIn(user, false /* createPersistentCookie */); _authenticationService.SignIn(user, false /* createPersistentCookie */);
@@ -217,21 +239,6 @@ namespace Orchard.Users.Controllers {
#region Validation Methods #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) { private IUser ValidateLogOn(string userNameOrEmail, string password) {
bool validate = true; bool validate = true;

View File

@@ -197,8 +197,8 @@ namespace Orchard.Users.Controllers {
var user = Services.ContentManager.Get(id); var user = Services.ContentManager.Get(id);
if ( user != null ) { if ( user != null ) {
string challengeToken = _membershipService.GetEncryptedChallengeToken(user.As<UserPart>()); string challengeToken = _userService.GetNonce(user.As<UserPart>());
_membershipService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", token = challengeToken}))); _userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", token = challengeToken})));
} }
Services.Notifier.Information(T("Challenge email sent")); Services.Notifier.Information(T("Challenge email sent"));

View File

@@ -29,11 +29,16 @@ namespace Orchard.Users.Handlers {
context.MailMessage.Body = T("The following user account needs to be moderated: {0}", recipient.UserName).Text; context.MailMessage.Body = T("The following user account needs to be moderated: {0}", recipient.UserName).Text;
} }
if ( context.Type == MessageTypes.Validation ) { if (context.Type == MessageTypes.Validation) {
context.MailMessage.Subject = T("User account validation").Text; context.MailMessage.Subject = T("User account validation").Text;
context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to validate you email address.", recipient.UserName, context.Properties["ChallengeUrl"]).Text; context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to validate you email address.", recipient.UserName, context.Properties["ChallengeUrl"]).Text;
} }
if (context.Type == MessageTypes.LostPassword) {
context.MailMessage.Subject = T("Lost password").Text;
context.MailMessage.Body = T("Dear {0}, please <a href=\"{1}\">click here</a> to change your password.", recipient.UserName, context.Properties["LostPasswordUrl"]).Text;
}
} }
public void Sent(MessageContext context) { public void Sent(MessageContext context) {

View File

@@ -7,6 +7,7 @@ namespace Orchard.Users.Models {
public static class MessageTypes { public static class MessageTypes {
public const string Moderation = "ORCHARD_USERS_MODERATION"; public const string Moderation = "ORCHARD_USERS_MODERATION";
public const string Validation = "ORCHARD_USERS_VALIDATION"; public const string Validation = "ORCHARD_USERS_VALIDATION";
public const string LostPassword = "ORCHARD_USERS_RESETPASSWORD";
} }
} }

View File

@@ -123,6 +123,9 @@
<Content Include="Views\EditorTemplates\Parts\User.Edit.cshtml" /> <Content Include="Views\EditorTemplates\Parts\User.Edit.cshtml" />
<Content Include="Views\EditorTemplates\Parts\User.Create.cshtml" /> <Content Include="Views\EditorTemplates\Parts\User.Create.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Views\Account\LostPassword.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -1,6 +1,16 @@
using Orchard.Security;
using System;
namespace Orchard.Users.Services { namespace Orchard.Users.Services {
public interface IUserService : IDependency { public interface IUserService : IDependency {
string VerifyUserUnicity(string userName, string email); string VerifyUserUnicity(string userName, string email);
string VerifyUserUnicity(int id, string userName, string email); string VerifyUserUnicity(int id, string userName, string email);
void SendChallengeEmail(IUser user, string url);
IUser ValidateChallenge(string challengeToken);
bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl);
IUser ValidateLostPassword(string nonce);
string GetNonce(IUser user);
} }
} }

View File

@@ -14,6 +14,7 @@ using Orchard.Users.Events;
using Orchard.Users.Models; using Orchard.Users.Models;
using Orchard.Messaging.Services; using Orchard.Messaging.Services;
using System.Collections.Generic; using System.Collections.Generic;
using Orchard.Services;
namespace Orchard.Users.Services { namespace Orchard.Users.Services {
[UsedImplicitly] [UsedImplicitly]
@@ -22,11 +23,13 @@ namespace Orchard.Users.Services {
private readonly IOrchardServices _orchardServices; private readonly IOrchardServices _orchardServices;
private readonly IMessageManager _messageManager; private readonly IMessageManager _messageManager;
private readonly IEnumerable<IUserEventHandler> _userEventHandlers; private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
private readonly IClock _clock;
public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers) { public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers, IClock clock) {
_orchardServices = orchardServices; _orchardServices = orchardServices;
_messageManager = messageManager; _messageManager = messageManager;
_userEventHandlers = userEventHandlers; _userEventHandlers = userEventHandlers;
_clock = clock;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
@@ -87,54 +90,6 @@ namespace Orchard.Users.Services {
return user; return user;
} }
public void SendChallengeEmail(IUser user, string url) {
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
}
public IUser ValidateChallengeToken(string challengeToken) {
string username;
DateTime validateByUtc;
if(!DecryptChallengeToken(challengeToken, out username, out validateByUtc)) {
return null;
}
if ( validateByUtc < DateTime.UtcNow )
return null;
var user = GetUser(username);
if ( user == null )
return null;
user.As<UserPart>().EmailStatus = UserStatus.Approved;
return user;
}
public string GetEncryptedChallengeToken(IUser user) {
var challengeToken = new XElement("Token", new XAttribute("username", user.UserName), new XAttribute("validate-by-utc", DateTime.UtcNow.Add(DelayToValidate).ToString(CultureInfo.InvariantCulture))).ToString();
var data = Encoding.UTF8.GetBytes(challengeToken);
return MachineKey.Encode(data, MachineKeyProtection.All);
}
private static bool DecryptChallengeToken(string challengeToken, out string username, out DateTime validateByUtc) {
username = null;
validateByUtc = DateTime.UtcNow;
try {
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
var xml = Encoding.UTF8.GetString(data);
var element = XElement.Parse(xml);
username = element.Attribute("username").Value;
validateByUtc = DateTime.Parse(element.Attribute("validate-by-utc").Value, CultureInfo.InvariantCulture);
return true;
}
catch {
return false;
}
}
public IUser GetUser(string username) { public IUser GetUser(string username) {
var lowerName = username == null ? "" : username.ToLower(); var lowerName = username == null ? "" : username.ToLower();

View File

@@ -1,17 +1,33 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using Orchard.Logging; using Orchard.Logging;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Users.Models; using Orchard.Users.Models;
using Orchard.Security;
using System.Xml.Linq;
using Orchard.Services;
using System.Globalization;
using System.Text;
using System.Web.Security;
using Orchard.Messaging.Services;
namespace Orchard.Users.Services { namespace Orchard.Users.Services {
[UsedImplicitly] [UsedImplicitly]
public class UserService : IUserService { public class UserService : IUserService {
private readonly IContentManager _contentManager; private static readonly TimeSpan DelayToValidate = new TimeSpan(7, 0, 0, 0); // one week to validate email
public UserService(IContentManager contentManager) { private readonly IContentManager _contentManager;
private readonly IMembershipService _membershipService;
private readonly IClock _clock;
private readonly IMessageManager _messageManager;
public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager) {
_contentManager = contentManager; _contentManager = contentManager;
_membershipService = membershipService;
_clock = clock;
_messageManager = messageManager;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
} }
@@ -46,5 +62,86 @@ namespace Orchard.Users.Services {
} }
return null; return null;
} }
public string GetNonce(IUser user) {
var challengeToken = new XElement("Token", new XAttribute("username", user.UserName), new XAttribute("validate-by-utc", _clock.UtcNow.Add(DelayToValidate).ToString(CultureInfo.InvariantCulture))).ToString();
var data = Encoding.UTF8.GetBytes(challengeToken);
return MachineKey.Encode(data, MachineKeyProtection.All);
}
private bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc) {
username = null;
validateByUtc = _clock.UtcNow;
try {
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
var xml = Encoding.UTF8.GetString(data);
var element = XElement.Parse(xml);
username = element.Attribute("username").Value;
validateByUtc = DateTime.Parse(element.Attribute("validate-by-utc").Value, CultureInfo.InvariantCulture);
return true;
}
catch {
return false;
}
}
public IUser ValidateChallenge(string nonce) {
string username;
DateTime validateByUtc;
if (!DecryptNonce(nonce, out username, out validateByUtc)) {
return null;
}
if (validateByUtc < _clock.UtcNow)
return null;
var user = _membershipService.GetUser(username);
if (user == null)
return null;
user.As<UserPart>().EmailStatus = UserStatus.Approved;
return user;
}
public void SendChallengeEmail(IUser user, string url) {
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
}
public bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl) {
var lowerName = usernameOrEmail.ToLower();
var user = _contentManager.Query<UserPart, UserPartRecord>().Where(u => u.NormalizedUserName == lowerName || u.Email == lowerName).List().FirstOrDefault();
if (user != null) {
string nonce = GetNonce(user);
string url = createUrl(nonce);
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
return true;
}
return false;
}
public IUser ValidateLostPassword(string nonce) {
string username;
DateTime validateByUtc;
if (!DecryptNonce(nonce, out username, out validateByUtc)) {
return null;
}
if (validateByUtc < _clock.UtcNow)
return null;
var user = _membershipService.GetUser(username);
if (user == null)
return null;
return user;
}
} }
} }

View File

@@ -6,11 +6,6 @@
@using (Html.BeginFormAntiForgeryPost()) { @using (Html.BeginFormAntiForgeryPost()) {
<fieldset> <fieldset>
<legend>@T("Account Information")</legend> <legend>@T("Account Information")</legend>
<div>
<label for="currentPassword">@T("Current password:")</label>
@Html.Password("currentPassword")
@Html.ValidationMessage("currentPassword")
</div>
<div> <div>
<label for="newPassword">@T("New password:")</label> <label for="newPassword">@T("New password:")</label>
@Html.Password("newPassword") @Html.Password("newPassword")

View File

@@ -6,10 +6,14 @@
} }
<h1 class="page-title">@Html.TitleForPage(Model.Title)</h1> <h1 class="page-title">@Html.TitleForPage(Model.Title)</h1>
<p>@T("Please enter your username and password.") @if(userCanRegister) { <text> </text> @Html.ActionLink("Register", "Register") @T(" if you don't have an account.") }</p> <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")
</p>
@Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString()) @Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString())
@using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", new {ReturnUrl = Request.QueryString["ReturnUrl"]}))) { @using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", new { ReturnUrl = Request.QueryString["ReturnUrl"] }))) {
<fieldset class="login-form group"> <fieldset class="login-form group">
<legend>@T("Account Information")</legend> <legend>@T("Account Information")</legend>
<ol> <ol>

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

@@ -9,13 +9,10 @@ namespace Orchard.Mvc.Html {
/// <see cref="http://en.wikipedia.org/wiki/Harry_Houdini"/> /// <see cref="http://en.wikipedia.org/wiki/Harry_Houdini"/>
/// <returns>himself</returns> /// <returns>himself</returns>
public static TService Resolve<TService>(this HtmlHelper html) { public static TService Resolve<TService>(this HtmlHelper html) {
var workContextAccessor = html.ViewContext.RouteData.DataTokens["IWorkContextAccessor"] as IWorkContextAccessor; var workContext = html.ViewContext.RequestContext.GetWorkContext();
if (workContextAccessor == null)
throw new ApplicationException("Unable to resolve");
var workContext = workContextAccessor.GetContext(html.ViewContext.HttpContext);
if (workContext == null) if (workContext == null)
throw new ApplicationException("Unable to resolve"); return default(TService);
return workContext.Resolve<TService>(); return workContext.Resolve<TService>();
} }

View File

@@ -8,8 +8,5 @@
IUser ValidateUser(string userNameOrEmail, string password); IUser ValidateUser(string userNameOrEmail, string password);
void SetPassword(IUser user, string password); void SetPassword(IUser user, string password);
IUser ValidateChallengeToken(string challengeToken);
void SendChallengeEmail(IUser user, string url);
string GetEncryptedChallengeToken(IUser user);
} }
} }

View File

@@ -21,6 +21,10 @@ namespace Orchard.UI.Navigation {
} }
public void OnResultExecuting(ResultExecutingContext filterContext) { public void OnResultExecuting(ResultExecutingContext filterContext) {
// should only run on a full view rendering result
if (!(filterContext.Result is ViewResult))
return;
var workContext = _workContextAccessor.GetContext(filterContext); var workContext = _workContextAccessor.GetContext(filterContext);
var menuName = "main"; var menuName = "main";

View File

@@ -1,4 +1,3 @@
using System;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.DisplayManagement; using Orchard.DisplayManagement;
using Orchard.Mvc.Filters; using Orchard.Mvc.Filters;
@@ -20,6 +19,10 @@ namespace Orchard.UI.Resources {
public void OnResultExecuting(ResultExecutingContext filterContext) { public void OnResultExecuting(ResultExecutingContext filterContext) {
// should only run on a full view rendering result
if (!(filterContext.Result is ViewResult))
return;
var ctx = _workContextAccessor.GetContext(); var ctx = _workContextAccessor.GetContext();
var head = ctx.Layout.Head; var head = ctx.Layout.Head;
var tail = ctx.Layout.Tail; var tail = ctx.Layout.Tail;

View File

@@ -10,27 +10,48 @@ namespace Orchard {
} }
public static WorkContext GetWorkContext(this RequestContext requestContext) { public static WorkContext GetWorkContext(this RequestContext requestContext) {
if (requestContext == null) { if (requestContext == null)
return null; return null;
}
var routeData = requestContext.RouteData; var routeData = requestContext.RouteData;
object value; if (routeData == null || routeData.DataTokens == null)
if (routeData == null ||
routeData.DataTokens == null ||
!routeData.DataTokens.TryGetValue("IWorkContextAccessor", out value) ||
!(value is IWorkContextAccessor)) {
return null; return null;
object workContextValue;
if (!routeData.DataTokens.TryGetValue("IWorkContextAccessor", out workContextValue)) {
workContextValue = FindWorkContextInParent(routeData);
} }
var workContextAccessor = (IWorkContextAccessor)value; if (!(workContextValue is IWorkContextAccessor))
return null;
var workContextAccessor = (IWorkContextAccessor)workContextValue;
return workContextAccessor.GetContext(requestContext.HttpContext); return workContextAccessor.GetContext(requestContext.HttpContext);
} }
public static WorkContext GetWorkContext(this ControllerContext controllerContext) { private static object FindWorkContextInParent(RouteData routeData) {
if (controllerContext == null) { object parentViewContextValue;
if (!routeData.DataTokens.TryGetValue("ParentActionViewContext", out parentViewContextValue)
|| !(parentViewContextValue is ViewContext)) {
return null; return null;
} }
var parentRouteData = ((ViewContext)parentViewContextValue).RouteData;
if (parentRouteData == null || parentRouteData.DataTokens == null)
return null;
object workContextValue;
if (!parentRouteData.DataTokens.TryGetValue("IWorkContextAccessor", out workContextValue)) {
workContextValue = FindWorkContextInParent(parentRouteData);
}
return workContextValue;
}
public static WorkContext GetWorkContext(this ControllerContext controllerContext) {
if (controllerContext == null)
return null;
return WorkContextExtensions.GetWorkContext(controllerContext.RequestContext); return WorkContextExtensions.GetWorkContext(controllerContext.RequestContext);
} }