diff --git a/src/Orchard.Web/Modules/Orchard.Users/Handlers/UserMessagesAlteration.cs b/src/Orchard.Web/Modules/Orchard.Users/Handlers/UserMessagesAlteration.cs deleted file mode 100644 index 1d33c4a58..000000000 --- a/src/Orchard.Web/Modules/Orchard.Users/Handlers/UserMessagesAlteration.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Linq; -using Orchard.Localization; -using Orchard.Messaging.Events; -using Orchard.Messaging.Models; -using Orchard.ContentManagement; -using Orchard.Settings; -using Orchard.Users.Models; - -namespace Orchard.Users.Handlers { - public class UserMessagesAlteration : IMessageEventHandler { - private readonly IContentManager _contentManager; - private readonly ISiteService _siteService; - - public UserMessagesAlteration(IContentManager contentManager, ISiteService siteService) { - _contentManager = contentManager; - _siteService = siteService; - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - public void Sending(MessageContext context) { - if (context.MessagePrepared) - return; - - switch (context.Type) { - case MessageTypes.Moderation: - context.MailMessage.Subject = T("New account").Text; - context.MailMessage.Body = - T("The user {0} with email {1} has requested a new account. This user won't be able to log while his account has not been approved.", - context.Properties["UserName"], context.Properties["Email"]).Text; - FormatEmailBody(context); - context.MessagePrepared = true; - break; - - case MessageTypes.Validation: - var registeredWebsite = _siteService.GetSiteSettings().As().ValidateEmailRegisteredWebsite; - var contactEmail = _siteService.GetSiteSettings().As().ValidateEmailContactEMail; - context.MailMessage.Subject = T("Verification E-Mail").Text; - context.MailMessage.Body = - T("Thank you for registering with {0}.


Final Step
To verify that you own this e-mail address, please click the following link:
{1}

Troubleshooting:
If clicking on the link above does not work, try the following:

Select and copy the entire link.
Open a browser window and paste the link in the address bar.
Click Go or, on your keyboard, press Enter or Return.", - registeredWebsite, context.Properties["ChallengeUrl"]).Text; - - if (!String.IsNullOrWhiteSpace(contactEmail)) { - context.MailMessage.Body += - T("

If you continue to have access problems or want to report other issues, please Contact Us.", - contactEmail).Text; - } - FormatEmailBody(context); - context.MessagePrepared = true; - break; - - case MessageTypes.LostPassword: - var recipient = GetRecipient(context); - context.MailMessage.Subject = T("Lost password").Text; - context.MailMessage.Body = - T("Dear {0}, please click here to change your password.", recipient.UserName, - context.Properties["LostPasswordUrl"]).Text; - FormatEmailBody(context); - context.MessagePrepared = true; - break; - } - } - - private UserPart GetRecipient(MessageContext context) { - // we expect a single account to be created - var contentItem = _contentManager.Get(context.Recipients.Single().Id); - if (contentItem == null) { - return null; - } - return contentItem.As(); - } - - private static void FormatEmailBody(MessageContext context) { - context.MailMessage.Body = "

" + context.MailMessage.Body; - context.MailMessage.Body += "

"; - } - - public void Sent(MessageContext context) { - } - } -} diff --git a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj index 793b9391b..53be99315 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj +++ b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj @@ -49,6 +49,10 @@ + + False + ..\..\..\..\lib\newtonsoft.json\Newtonsoft.Json.dll + 3.5 @@ -73,7 +77,6 @@ - @@ -120,6 +123,10 @@ {9916839C-39FC-4CEB-A5AF-89CA7E87119F} Orchard.Core + + {085948FF-0E9B-4A9A-B564-F8B8B4BDDDBC} + Orchard.Messaging + @@ -151,6 +158,15 @@ Designer + + + + + + + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs index 1187127a3..b9cb972ca 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/MembershipService.cs @@ -4,6 +4,8 @@ using System.Security.Cryptography; using System.Text; using System.Web.Security; using JetBrains.Annotations; +using Newtonsoft.Json; +using Orchard.DisplayManagement; using Orchard.Localization; using Orchard.Logging; using Orchard.ContentManagement; @@ -18,15 +20,26 @@ namespace Orchard.Users.Services { [UsedImplicitly] public class MembershipService : IMembershipService { private readonly IOrchardServices _orchardServices; - private readonly IMessageManager _messageManager; + private readonly IMessageService _messageService; private readonly IEnumerable _userEventHandlers; private readonly IEncryptionService _encryptionService; + private readonly IShapeFactory _shapeFactory; + private readonly IShapeDisplay _shapeDisplay; - public MembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable userEventHandlers, IClock clock, IEncryptionService encryptionService) { + public MembershipService( + IOrchardServices orchardServices, + IMessageService messageService, + IEnumerable userEventHandlers, + IClock clock, + IEncryptionService encryptionService, + IShapeFactory shapeFactory, + IShapeDisplay shapeDisplay) { _orchardServices = orchardServices; - _messageManager = messageManager; + _messageService = messageService; _userEventHandlers = userEventHandlers; _encryptionService = encryptionService; + _shapeFactory = shapeFactory; + _shapeDisplay = shapeDisplay; Logger = NullLogger.Instance; T = NullLocalizer.Instance; } @@ -51,7 +64,7 @@ namespace Orchard.Users.Services { user.Record.Email = createUserParams.Email; user.Record.NormalizedUserName = createUserParams.Username.ToLowerInvariant(); user.Record.HashAlgorithm = "SHA1"; - SetPassword(user.Record, createUserParams.Password); + SetPassword(user, createUserParams.Password); if ( registrationSettings != null ) { user.Record.RegistrationStatus = registrationSettings.UsersAreModerated ? UserStatus.Pending : UserStatus.Approved; @@ -81,7 +94,10 @@ namespace Orchard.Users.Services { } } - if ( registrationSettings != null && registrationSettings.UsersAreModerated && registrationSettings.NotifyModeration && !createUserParams.IsApproved ) { + if ( registrationSettings != null + && registrationSettings.UsersAreModerated + && registrationSettings.NotifyModeration + && !createUserParams.IsApproved ) { var usernames = String.IsNullOrWhiteSpace(registrationSettings.NotificationsRecipients) ? new string[0] : registrationSettings.NotificationsRecipients.Split(new[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries); @@ -91,8 +107,15 @@ namespace Orchard.Users.Services { continue; } var recipient = GetUser(userName); - if (recipient != null) - _messageManager.Send(recipient.ContentItem.Record, MessageTypes.Moderation, "email" , new Dictionary { { "UserName", createUserParams.Username}, { "Email" , createUserParams.Email } }); + if (recipient != null) { + var template = _shapeFactory.Create("Template_User_Moderated", Arguments.From(createUserParams)); + var payload = new { + Subject = T("New account").Text, + Body = _shapeDisplay.Display(template), + Recipients = new [] { recipient.Email } + }; + _messageService.Send("Email", JsonConvert.SerializeObject(payload)); + } } } @@ -113,7 +136,7 @@ namespace Orchard.Users.Services { if (user == null) user = _orchardServices.ContentManager.Query().Where(u => u.Email == lowerName).List().FirstOrDefault(); - if ( user == null || ValidatePassword(user.As().Record, password) == false ) + if ( user == null || ValidatePassword(user.As(), password) == false ) return null; if ( user.EmailStatus != UserStatus.Approved ) @@ -129,54 +152,50 @@ namespace Orchard.Users.Services { if (!user.Is()) throw new InvalidCastException(); - var userRecord = user.As().Record; - SetPassword(userRecord, password); - } + var userPart = user.As(); - - void SetPassword(UserPartRecord partRecord, string password) { switch (GetSettings().PasswordFormat) { case MembershipPasswordFormat.Clear: - SetPasswordClear(partRecord, password); + SetPasswordClear(userPart, password); break; case MembershipPasswordFormat.Hashed: - SetPasswordHashed(partRecord, password); + SetPasswordHashed(userPart, password); break; case MembershipPasswordFormat.Encrypted: - SetPasswordEncrypted(partRecord, password); + SetPasswordEncrypted(userPart, password); break; default: throw new ApplicationException(T("Unexpected password format value").ToString()); } } - private bool ValidatePassword(UserPartRecord partRecord, string password) { + private bool ValidatePassword(UserPart userPart, string password) { // Note - the password format stored with the record is used // otherwise changing the password format on the site would invalidate // all logins - switch (partRecord.PasswordFormat) { + switch (userPart.PasswordFormat) { case MembershipPasswordFormat.Clear: - return ValidatePasswordClear(partRecord, password); + return ValidatePasswordClear(userPart, password); case MembershipPasswordFormat.Hashed: - return ValidatePasswordHashed(partRecord, password); + return ValidatePasswordHashed(userPart, password); case MembershipPasswordFormat.Encrypted: - return ValidatePasswordEncrypted(partRecord, password); + return ValidatePasswordEncrypted(userPart, password); default: throw new ApplicationException("Unexpected password format value"); } } - private static void SetPasswordClear(UserPartRecord partRecord, string password) { - partRecord.PasswordFormat = MembershipPasswordFormat.Clear; - partRecord.Password = password; - partRecord.PasswordSalt = null; + private static void SetPasswordClear(UserPart userPart, string password) { + userPart.PasswordFormat = MembershipPasswordFormat.Clear; + userPart.Password = password; + userPart.PasswordSalt = null; } - private static bool ValidatePasswordClear(UserPartRecord partRecord, string password) { - return partRecord.Password == password; + private static bool ValidatePasswordClear(UserPart userPart, string password) { + return userPart.Password == password; } - private static void SetPasswordHashed(UserPartRecord partRecord, string password) { + private static void SetPasswordHashed(UserPart userPart, string password) { var saltBytes = new byte[0x10]; using (var random = new RNGCryptoServiceProvider()) { @@ -188,39 +207,39 @@ namespace Orchard.Users.Services { var combinedBytes = saltBytes.Concat(passwordBytes).ToArray(); byte[] hashBytes; - using (var hashAlgorithm = HashAlgorithm.Create(partRecord.HashAlgorithm)) { + using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) { hashBytes = hashAlgorithm.ComputeHash(combinedBytes); } - partRecord.PasswordFormat = MembershipPasswordFormat.Hashed; - partRecord.Password = Convert.ToBase64String(hashBytes); - partRecord.PasswordSalt = Convert.ToBase64String(saltBytes); + userPart.PasswordFormat = MembershipPasswordFormat.Hashed; + userPart.Password = Convert.ToBase64String(hashBytes); + userPart.PasswordSalt = Convert.ToBase64String(saltBytes); } - private static bool ValidatePasswordHashed(UserPartRecord partRecord, string password) { + private static bool ValidatePasswordHashed(UserPart userPart, string password) { - var saltBytes = Convert.FromBase64String(partRecord.PasswordSalt); + var saltBytes = Convert.FromBase64String(userPart.PasswordSalt); var passwordBytes = Encoding.Unicode.GetBytes(password); var combinedBytes = saltBytes.Concat(passwordBytes).ToArray(); byte[] hashBytes; - using (var hashAlgorithm = HashAlgorithm.Create(partRecord.HashAlgorithm)) { + using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) { hashBytes = hashAlgorithm.ComputeHash(combinedBytes); } - - return partRecord.Password == Convert.ToBase64String(hashBytes); + + return userPart.Password == Convert.ToBase64String(hashBytes); } - private void SetPasswordEncrypted(UserPartRecord partRecord, string password) { - partRecord.Password = Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(password))); - partRecord.PasswordSalt = null; - partRecord.PasswordFormat = MembershipPasswordFormat.Encrypted; + private void SetPasswordEncrypted(UserPart userPart, string password) { + userPart.Password = Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(password))); + userPart.PasswordSalt = null; + userPart.PasswordFormat = MembershipPasswordFormat.Encrypted; } - private bool ValidatePasswordEncrypted(UserPartRecord partRecord, string password) { - return String.Equals(password, Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(partRecord.Password))), StringComparison.Ordinal); + private bool ValidatePasswordEncrypted(UserPart userPart, string password) { + return String.Equals(password, Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(userPart.Password))), StringComparison.Ordinal); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs index 5e3212912..06a8266e8 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs @@ -1,9 +1,12 @@ using System; using System.Linq; -using System.Collections.Generic; using JetBrains.Annotations; +using Newtonsoft.Json; +using Orchard.DisplayManagement; +using Orchard.Localization; using Orchard.Logging; using Orchard.ContentManagement; +using Orchard.Settings; using Orchard.Users.Models; using Orchard.Security; using System.Xml.Linq; @@ -22,19 +25,36 @@ namespace Orchard.Users.Services { private readonly IContentManager _contentManager; private readonly IMembershipService _membershipService; private readonly IClock _clock; - private readonly IMessageManager _messageManager; + private readonly IMessageService _messageService; private readonly IEncryptionService _encryptionService; + private readonly IShapeFactory _shapeFactory; + private readonly IShapeDisplay _shapeDisplay; + private readonly ISiteService _siteService; - public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings, IEncryptionService encryptionService) { + public UserService( + IContentManager contentManager, + IMembershipService membershipService, + IClock clock, + IMessageService messageService, + ShellSettings shellSettings, + IEncryptionService encryptionService, + IShapeFactory shapeFactory, + IShapeDisplay shapeDisplay, + ISiteService siteService + ) { _contentManager = contentManager; _membershipService = membershipService; _clock = clock; - _messageManager = messageManager; + _messageService = messageService; _encryptionService = encryptionService; + _shapeFactory = shapeFactory; + _shapeDisplay = shapeDisplay; + _siteService = siteService; Logger = NullLogger.Instance; } public ILogger Logger { get; set; } + public Localizer T { get; set; } public bool VerifyUserUnicity(string userName, string email) { string normalizedUserName = userName.ToLowerInvariant(); @@ -111,7 +131,24 @@ namespace Orchard.Users.Services { public void SendChallengeEmail(IUser user, Func createUrl) { string nonce = CreateNonce(user, DelayToValidate); string url = createUrl(nonce); - _messageManager.Send(user.ContentItem.Record, MessageTypes.Validation, "email", new Dictionary { { "ChallengeUrl", url } }); + + if (user != null) { + var site = _siteService.GetSiteSettings(); + + var template = _shapeFactory.Create("Template_User_Validated", Arguments.From(new { + RegisteredWebsite = site.As().ValidateEmailRegisteredWebsite, + ContactEmail = site.As().ValidateEmailContactEMail, + ChallengeUrl = url + })); + + var payload = new { + Subject = T("Verification E-Mail").Text, + Body = _shapeDisplay.Display(template), + Recipients = new[] { user.Email } + }; + + _messageService.Send("Email", JsonConvert.SerializeObject(payload)); + } } public bool SendLostPasswordEmail(string usernameOrEmail, Func createUrl) { @@ -122,7 +159,18 @@ namespace Orchard.Users.Services { string nonce = CreateNonce(user, DelayToResetPassword); string url = createUrl(nonce); - _messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary { { "LostPasswordUrl", url } }); + var template = _shapeFactory.Create("Template_User_LostPassword", Arguments.From(new { + User = user, + LostPasswordUrl = url + })); + + var payload = new { + Subject = T("Lost password").Text, + Body = _shapeDisplay.Display(template), + Recipients = new[] { user.Email } + }; + + _messageService.Send("Email", JsonConvert.SerializeObject(payload)); return true; } diff --git a/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.LostPassword.cshtml b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.LostPassword.cshtml new file mode 100644 index 000000000..105d00c7a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.LostPassword.cshtml @@ -0,0 +1 @@ +@T("Dear {0}, please click here to change your password.", Model.User.UserName, Model.LostPasswordUrl) \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Moderated.cshtml b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Moderated.cshtml new file mode 100644 index 000000000..dee49e5ab --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Moderated.cshtml @@ -0,0 +1 @@ +@T("The user {0} with email {1} has requested a new account. This user won't be able to log while his account has not been approved.", Model.Username, Model.Email) \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Validated.cshtml b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Validated.cshtml new file mode 100644 index 000000000..8e04109ba --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Users/Views/Template.User.Validated.cshtml @@ -0,0 +1,4 @@ +@T("Thank you for registering with {0}.


Final Step
To verify that you own this e-mail address, please click the following link:
{1}

Troubleshooting:
If clicking on the link above does not work, try the following:

Select and copy the entire link.
Open a browser window and paste the link in the address bar.
Click Go or, on your keyboard, press Enter or Return.", Model.RegisteredWebsite, Model.ChallengeUrl) +@if (!String.IsNullOrWhiteSpace(Model.ContactEmail)) { + @T("

If you continue to have access problems or want to report other issues, please Contact Us.",Model.ContactEmail) +}