mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Refactoring users challenge emails
- Adding unit tests - Creating a stub for email messages - Adding tenant's name to nonces in order to prevent cross-tenants substitution --HG-- branch : dev
This commit is contained in:
@@ -120,8 +120,7 @@ namespace Orchard.Users.Controllers {
|
||||
|
||||
if (user != null) {
|
||||
if ( user.As<UserPart>().EmailStatus == UserStatus.Pending ) {
|
||||
string challengeToken = _userService.GetNonce(user.As<UserPart>());
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken })));
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce })));
|
||||
|
||||
return RedirectToAction("ChallengeEmailSent");
|
||||
}
|
||||
|
@@ -197,8 +197,7 @@ namespace Orchard.Users.Controllers {
|
||||
var user = Services.ContentManager.Get(id);
|
||||
|
||||
if ( user != null ) {
|
||||
string challengeToken = _userService.GetNonce(user.As<UserPart>());
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", token = challengeToken})));
|
||||
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", nonce = nonce})));
|
||||
}
|
||||
|
||||
Services.Notifier.Information(T("Challenge email sent"));
|
||||
|
@@ -5,12 +5,13 @@ namespace Orchard.Users.Services {
|
||||
string VerifyUserUnicity(string userName, string email);
|
||||
string VerifyUserUnicity(int id, string userName, string email);
|
||||
|
||||
void SendChallengeEmail(IUser user, string url);
|
||||
void SendChallengeEmail(IUser user, Func<string, string> createUrl);
|
||||
IUser ValidateChallenge(string challengeToken);
|
||||
|
||||
bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl);
|
||||
IUser ValidateLostPassword(string nonce);
|
||||
|
||||
string GetNonce(IUser user);
|
||||
string CreateNonce(IUser user, TimeSpan delay);
|
||||
bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc);
|
||||
}
|
||||
}
|
@@ -19,7 +19,6 @@ using Orchard.Services;
|
||||
namespace Orchard.Users.Services {
|
||||
[UsedImplicitly]
|
||||
public class MembershipService : IMembershipService {
|
||||
private static readonly TimeSpan DelayToValidate = new TimeSpan(7, 0, 0, 0); // one week to validate email
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
private readonly IMessageManager _messageManager;
|
||||
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
||||
|
@@ -12,22 +12,26 @@ using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using Orchard.Messaging.Services;
|
||||
using Orchard.Environment.Configuration;
|
||||
|
||||
namespace Orchard.Users.Services {
|
||||
[UsedImplicitly]
|
||||
public class UserService : IUserService {
|
||||
private static readonly TimeSpan DelayToValidate = new TimeSpan(7, 0, 0, 0); // one week to validate email
|
||||
private static readonly TimeSpan DelayToResetPassword = new TimeSpan(1, 0, 0, 0); // 24 hours to validate email
|
||||
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IMembershipService _membershipService;
|
||||
private readonly IClock _clock;
|
||||
private readonly IMessageManager _messageManager;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
|
||||
public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager) {
|
||||
public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings) {
|
||||
_contentManager = contentManager;
|
||||
_membershipService = membershipService;
|
||||
_clock = clock;
|
||||
_messageManager = messageManager;
|
||||
_shellSettings = shellSettings;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
@@ -63,23 +67,25 @@ namespace Orchard.Users.Services {
|
||||
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);
|
||||
public string CreateNonce(IUser user, TimeSpan delay) {
|
||||
// the tenant's name is added to the token to prevent cross-tenant requests
|
||||
var challengeToken = new XElement("n", new XAttribute("s", _shellSettings.Name), new XAttribute("un", user.UserName), new XAttribute("utc", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString();
|
||||
var data = Encoding.Unicode.GetBytes(challengeToken);
|
||||
return MachineKey.Encode(data, MachineKeyProtection.All);
|
||||
}
|
||||
|
||||
private bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc) {
|
||||
public 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 xml = Encoding.Unicode.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;
|
||||
var tenant = element.Attribute("s").Value;
|
||||
username = element.Attribute("un").Value;
|
||||
validateByUtc = DateTime.Parse(element.Attribute("utc").Value, CultureInfo.InvariantCulture);
|
||||
return String.Equals(_shellSettings.Name, tenant, StringComparison.Ordinal) && _clock.UtcNow <= validateByUtc;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
@@ -107,7 +113,9 @@ namespace Orchard.Users.Services {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void SendChallengeEmail(IUser user, string url) {
|
||||
public void SendChallengeEmail(IUser user, Func<string, string> createUrl) {
|
||||
string nonce = CreateNonce(user, DelayToValidate);
|
||||
string url = createUrl(nonce);
|
||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary<string, string> { { "ChallengeUrl", url } });
|
||||
}
|
||||
|
||||
@@ -116,7 +124,7 @@ namespace Orchard.Users.Services {
|
||||
var user = _contentManager.Query<UserPart, UserPartRecord>().Where(u => u.NormalizedUserName == lowerName || u.Email == lowerName).List().FirstOrDefault();
|
||||
|
||||
if (user != null) {
|
||||
string nonce = GetNonce(user);
|
||||
string nonce = CreateNonce(user, DelayToResetPassword);
|
||||
string url = createUrl(nonce);
|
||||
|
||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
|
||||
|
Reference in New Issue
Block a user