Registration settings

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2010-08-31 12:57:15 -07:00
parent e81ec58497
commit 6264af8888
24 changed files with 266 additions and 77 deletions

View File

@@ -5,7 +5,6 @@ using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using Autofac;
using Autofac.Features.Metadata;
using NHibernate;
@@ -14,10 +13,8 @@ using Orchard.Caching;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.Data.Conventions;
using Orchard.Data.Migration;
using Orchard.Data.Migration.Generator;
using Orchard.Data.Migration.Interpreters;
using Orchard.Data.Migration.Records;
using Orchard.Data.Migration.Schema;
using Orchard.DevTools.Services;
using Orchard.Environment.Configuration;

View File

@@ -4,32 +4,29 @@ using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Core.Messaging.Models;
using Orchard.Data;
using Orchard.Logging;
using Orchard.Messaging.Events;
using Orchard.Messaging.Models;
using Orchard.Messaging.Services;
using Orchard.Settings;
using Orchard.ContentManagement.Records;
namespace Orchard.Core.Messaging.Services {
public class DefaultMessageManager : IMessageManager {
private readonly IMessageEventHandler _messageEventHandler;
private readonly IEnumerable<IMessagingChannel> _channels;
private readonly IRepository<Message> _messageRepository;
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
public ILogger Logger { get; set; }
public DefaultMessageManager(
IMessageEventHandler messageEventHandler,
IEnumerable<IMessagingChannel> channels,
IRepository<Message> messageRepository) {
IEnumerable<IMessagingChannel> channels) {
_messageEventHandler = messageEventHandler;
_channels = channels;
_messageRepository = messageRepository;
}
public void Send(Message message) {
public void Send(ContentItemRecord recipient, string type, string service = null) {
if ( !HasChannels() )
return;
@@ -39,15 +36,19 @@ namespace Orchard.Core.Messaging.Services {
return;
}
Logger.Information("Sending message {0}", message.Type);
Logger.Information("Sending message {0}", type);
try {
// if the service is not explicit, use the default one, as per settings configuration
if ( String.IsNullOrWhiteSpace(message.Service) ) {
message.Service = messageSettings.DefaultChannelService;
if ( String.IsNullOrWhiteSpace(service) ) {
service = messageSettings.DefaultChannelService;
}
var context = new MessageContext(message);
var context = new MessageContext {
Recipient = recipient,
Type = type,
Service = service
};
_messageEventHandler.Sending(context);
@@ -57,10 +58,10 @@ namespace Orchard.Core.Messaging.Services {
_messageEventHandler.Sent(context);
Logger.Information("Message {0} sent", message.Type);
Logger.Information("Message {0} sent", type);
}
catch ( Exception e ) {
Logger.Error(e, "An error occured while sending the message {0}", message.Type);
Logger.Error(e, "An error occured while sending the message {0}", type);
}
}

View File

@@ -1,5 +1,4 @@
using Orchard.Messaging.Events;
using Orchard.Core.Messaging.Models;
using Orchard.ContentManagement;
using Orchard.Users.Models;
using Orchard.Messaging.Models;
@@ -13,7 +12,7 @@ namespace Orchard.Email.Services {
}
public void Sending(MessageContext context) {
var contentItem = _contentManager.Get(context.Message.Recipient.Id);
var contentItem = _contentManager.Get(context.Recipient.Id);
if ( contentItem == null )
return;
@@ -21,7 +20,7 @@ namespace Orchard.Email.Services {
if ( recipient == null )
return;
context.Properties.Add(EmailMessagingChannel.EmailAddress, recipient.Email);
context.MailMessage.To.Add(recipient.Email);
}
public void Sent(MessageContext context) {

View File

@@ -2,14 +2,10 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
using System.Web;
using System.Web.Hosting;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Core.Messaging.Models;
using Orchard.Core.Messaging.Services;
using Orchard.Email.Models;
using Orchard.Settings;
using Orchard.Messaging.Services;
@@ -19,7 +15,6 @@ namespace Orchard.Email.Services {
public class EmailMessagingChannel : IMessagingChannel {
public const string EmailService = "Email";
public const string EmailAddress = "EmailAddress";
public EmailMessagingChannel() {
Logger = NullLogger.Instance;
@@ -30,7 +25,7 @@ namespace Orchard.Email.Services {
public Localizer T { get; set; }
public void SendMessage(MessageContext context) {
if ( context.Message.Service.ToLower() != EmailService )
if ( !context.Service.Equals(EmailService, StringComparison.InvariantCultureIgnoreCase) )
return;
var smtpSettings = CurrentSite.As<SmtpSettingsPart>();
@@ -45,9 +40,7 @@ namespace Orchard.Email.Services {
smtpClient.Credentials = new NetworkCredential(smtpSettings.UserName, smtpSettings.Password);
}
var emailAddress = context.Properties[EmailAddress];
if(String.IsNullOrWhiteSpace(emailAddress)) {
if(context.MailMessage.To.Count == 0) {
Logger.Error("Recipient is missing an email address");
return;
}
@@ -59,33 +52,18 @@ namespace Orchard.Email.Services {
smtpClient.EnableSsl = smtpSettings.EnableSsl;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
var message = new MailMessage {
From = new MailAddress(smtpSettings.Address),
Subject = context.Message.Subject ?? "",
Body = context.Message.Body ?? "",
IsBodyHtml = context.Message.Body != null && context.Message.Body.Contains("<") && context.Message.Body.Contains(">")
};
message.To.Add(emailAddress);
context.MailMessage.From = new MailAddress(smtpSettings.Address);
context.MailMessage.IsBodyHtml = context.MailMessage.Body != null && context.MailMessage.Body.Contains("<") && context.MailMessage.Body.Contains(">");
try {
smtpClient.Send(message);
Logger.Debug("Message sent to {0}: {1}", emailAddress, context.Message.Subject);
smtpClient.Send(context.MailMessage);
Logger.Debug("Message sent to {0}: {1}", context.MailMessage.To[0].Address, context.Type);
}
catch(Exception e) {
Logger.Error(e, "An unexpected error while sending a message to {0}: {1}", emailAddress, context.Message.Subject);
Logger.Error(e, "An unexpected error while sending a message to {0}: {1}", context.MailMessage.To[0].Address, context.Type);
}
}
public bool IsRecipientValidated(ContentItem contentItem) {
return false;
}
public void ValidateRecipient(ContentItem contentItem) {
var context = new MessageContext(new Message { Recipient = contentItem.Record, Body = "Please validate your account", Service = "email", Subject = "Validate your account" } );
SendMessage(context);
}
public IEnumerable<string> GetAvailableServices() {
return new[] {EmailService};
}

View File

@@ -10,6 +10,11 @@ using Orchard.Mvc.ViewModels;
using Orchard.Security;
using Orchard.Users.Services;
using Orchard.Users.ViewModels;
using Orchard.Settings;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Users.Models;
using Orchard.Mvc.Results;
namespace Orchard.Users.Controllers {
[HandleError]
@@ -31,6 +36,7 @@ namespace Orchard.Users.Controllers {
public ILogger Logger { get; set; }
public Localizer T { get; set; }
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
public ActionResult AccessDenied() {
var returnUrl = Request.QueryString["ReturnUrl"];
@@ -87,6 +93,12 @@ namespace Orchard.Users.Controllers {
}
public ActionResult Register() {
// ensure users can register
var registrationSettings = CurrentSite.As<RegistrationSettingsPart>();
if ( !registrationSettings.UsersCanRegister ) {
return new NotFoundResult();
}
ViewData["PasswordLength"] = MinPasswordLength;
return View(new BaseViewModel());
@@ -94,6 +106,12 @@ namespace Orchard.Users.Controllers {
[HttpPost]
public ActionResult Register(string userName, string email, string password, string confirmPassword) {
// ensure users can register
var registrationSettings = CurrentSite.As<RegistrationSettingsPart>();
if ( !registrationSettings.UsersCanRegister ) {
return new NotFoundResult();
}
ViewData["PasswordLength"] = MinPasswordLength;
if (ValidateRegistration(userName, email, password, confirmPassword)) {

View File

@@ -18,5 +18,24 @@ namespace Orchard.Users.DataMigrations {
return 1;
}
public int UpdateFrom1() {
// Adds registration fields to previous versions
SchemaBuilder
.AlterTable("UserPartRecord", table => table.AddColumn<string>("RegistrationStatus", c => c.WithDefault("'Approved'")))
.AlterTable("UserPartRecord", table => table.AddColumn<string>("EmailStatus", c => c.WithDefault("'Approved'")));
// Site Settings record
SchemaBuilder.CreateTable("RegistrationSettingsPartRecord", table => table
.ContentPartRecord()
.Column<bool>("UsersCanRegister", c => c.WithDefault("'0'"))
.Column<bool>("UsersMustValidateEmail", c => c.WithDefault("'0'"))
.Column<bool>("UsersAreModerated", c => c.WithDefault("'0'"))
.Column<bool>("NotifyModeration", c => c.WithDefault("'0'"))
);
return 2;
}
}
}

View File

@@ -0,0 +1,38 @@
using Orchard.Messaging.Events;
using Orchard.Messaging.Models;
using Orchard.ContentManagement;
using Orchard.Users.Models;
namespace Orchard.Users.Handlers {
public class ModerationMessageAlteration : IMessageEventHandler {
private readonly IContentManager _contentManager;
public ModerationMessageAlteration(IContentManager contentManager) {
_contentManager = contentManager;
}
public void Sending(MessageContext context) {
var contentItem = _contentManager.Get(context.Recipient.Id);
if ( contentItem == null )
return;
var recipient = contentItem.As<UserPart>();
if ( recipient == null )
return;
if ( context.Type == MessageTypes.Moderation ) {
context.MailMessage.Subject = "User needs moderation";
context.MailMessage.Body = string.Format("The following user account needs to be moderated: {0}", recipient.UserName);
}
if ( context.Type == MessageTypes.Validation ) {
context.MailMessage.Subject = "User account validation";
context.MailMessage.Body = string.Format("Dear {0}, please click on the folowwing link to validate you email address: {1}", recipient.UserName, "http://foo");
}
}
public void Sent(MessageContext context) {
}
}
}

View File

@@ -0,0 +1,15 @@
using JetBrains.Annotations;
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using Orchard.Users.Models;
namespace Orchard.Users.Handlers {
[UsedImplicitly]
public class RegistrationSettingsPartHandler : ContentHandler {
public RegistrationSettingsPartHandler(IRepository<RegistrationSettingsPartRecord> repository) {
Filters.Add(new ActivatingFilter<RegistrationSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
Filters.Add(new TemplateFilterForRecord<RegistrationSettingsPartRecord>("RegistrationSettings", "Parts/Users.RegistrationSettings"));
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Orchard.Users.Models {
public static class MessageTypes {
public const string Moderation = "ORCHARD_USERS_MODERATION";
public const string Validation = "ORCHARD_USERS_VALIDATION";
}
}

View File

@@ -0,0 +1,27 @@
using Orchard.ContentManagement;
using System;
namespace Orchard.Users.Models {
public class RegistrationSettingsPart : ContentPart<RegistrationSettingsPartRecord> {
public bool UsersCanRegister {
get { return Record.UsersCanRegister; }
set { Record.UsersCanRegister = value; }
}
public bool UsersMustValidateEmail {
get { return Record.UsersMustValidateEmail; }
set { Record.UsersMustValidateEmail = value; }
}
public bool UsersAreModerated {
get { return Record.UsersAreModerated; }
set { Record.UsersAreModerated = value; }
}
public bool NotifyModeration {
get { return Record.NotifyModeration; }
set { Record.NotifyModeration = value; }
}
}
}

View File

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

View File

@@ -21,5 +21,15 @@ namespace Orchard.Users.Models {
get { return Record.NormalizedUserName; }
set { Record.NormalizedUserName = value; }
}
public UserStatus RegistrationStatus {
get { return Record.RegistrationStatus; }
set { Record.RegistrationStatus = value; }
}
public UserStatus EmailStatus {
get { return Record.EmailStatus; }
set { Record.EmailStatus = value; }
}
}
}

View File

@@ -11,5 +11,8 @@ namespace Orchard.Users.Models {
public virtual MembershipPasswordFormat PasswordFormat { get; set; }
public virtual string HashAlgorithm { get; set; }
public virtual string PasswordSalt { get; set; }
public virtual UserStatus RegistrationStatus { get; set; }
public virtual UserStatus EmailStatus { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace Orchard.Users.Models {
public enum UserStatus {
Pending,
Approved
}
}

View File

@@ -69,9 +69,15 @@
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="DataMigrations\UsersDataMigration.cs" />
<Compile Include="Drivers\UserPartDriver.cs" />
<Compile Include="Handlers\ModerationMessageAlteration.cs" />
<Compile Include="Handlers\RegistrationSettingsPartHandler.cs" />
<Compile Include="Models\MessageTypes.cs" />
<Compile Include="Models\RegistrationSettingsPart.cs" />
<Compile Include="Models\RegistrationSettingsPartRecord.cs" />
<Compile Include="Models\UserPart.cs" />
<Compile Include="Handlers\UserPartHandler.cs" />
<Compile Include="Models\UserPartRecord.cs" />
<Compile Include="Models\UserStatus.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\IUserService.cs" />
@@ -95,6 +101,7 @@
<Content Include="Views\Admin\EditorTemplates\inputPasswordLarge.ascx" />
<Content Include="Views\Admin\EditorTemplates\inputTextLarge.ascx" />
<Content Include="Views\Admin\Index.aspx" />
<Content Include="Views\EditorTemplates\Parts\Users.RegistrationSettings.ascx" />
<Content Include="Web.config" />
<Content Include="Views\Web.config" />
</ItemGroup>

View File

@@ -10,20 +10,25 @@ using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Users.Drivers;
using Orchard.Users.Models;
using Orchard.Settings;
using Orchard.Messaging.Services;
namespace Orchard.Users.Services {
[UsedImplicitly]
public class MembershipService : IMembershipService {
private readonly IContentManager _contentManager;
private readonly IMessageManager _messageManager;
private readonly IRepository<UserPartRecord> _userRepository;
public MembershipService(IContentManager contentManager, IRepository<UserPartRecord> userRepository) {
public MembershipService(IContentManager contentManager, IRepository<UserPartRecord> userRepository, IMessageManager messageManager ) {
_contentManager = contentManager;
_userRepository = userRepository;
_messageManager = messageManager;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
public MembershipSettings GetSettings() {
var settings = new MembershipSettings();
@@ -34,14 +39,35 @@ namespace Orchard.Users.Services {
public IUser CreateUser(CreateUserParams createUserParams) {
Logger.Information("CreateUser {0} {1}", createUserParams.Username, createUserParams.Email);
return _contentManager.Create<UserPart>(UserPartDriver.ContentType.Name, init =>
var registrationSettings = CurrentSite.As<RegistrationSettingsPart>();
var user = _contentManager.Create<UserPart>(UserPartDriver.ContentType.Name, init =>
{
init.Record.UserName = createUserParams.Username;
init.Record.Email = createUserParams.Email;
init.Record.NormalizedUserName = createUserParams.Username.ToLower();
init.Record.HashAlgorithm = "SHA1";
SetPassword(init.Record, createUserParams.Password);
init.Record.RegistrationStatus = registrationSettings.UsersAreModerated ? UserStatus.Pending : UserStatus.Approved;
init.Record.EmailStatus = registrationSettings.UsersMustValidateEmail ? UserStatus.Pending : UserStatus.Approved;
});
if ( registrationSettings.UsersMustValidateEmail ) {
SendEmailValidationMessage(user);
}
if ( registrationSettings.UsersAreModerated && registrationSettings.NotifyModeration ) {
var superUser = GetUser(CurrentSite.SuperUser);
if(superUser != null)
_messageManager.Send(superUser.ContentItem.Record, MessageTypes.Moderation);
}
return user;
}
public void SendEmailValidationMessage(IUser user) {
_messageManager.Send(user.ContentItem.Record, MessageTypes.Validation);
}
public IUser GetUser(string username) {
@@ -58,16 +84,22 @@ namespace Orchard.Users.Services {
var lowerName = userNameOrEmail == null ? "" : userNameOrEmail.ToLower();
var userRecord = _userRepository.Get(x => x.NormalizedUserName == lowerName);
if(userRecord == null)
userRecord = _userRepository.Get(x => x.Email == lowerName);
if (userRecord == null || ValidatePassword(userRecord, password) == false)
return null;
if ( userRecord.EmailStatus != UserStatus.Approved )
return null;
if ( userRecord.RegistrationStatus != UserStatus.Approved )
return null;
return _contentManager.Get<IUser>(userRecord.Id);
}
public void SetPassword(IUser user, string password) {
if (!user.Is<UserPart>())
throw new InvalidCastException();

View File

@@ -0,0 +1,25 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<RegistrationSettingsPartRecord>" %>
<%@ Import Namespace="Orchard.Users.Models"%>
<fieldset>
<legend><%: T("Users registration")%></legend>
<div>
<%: Html.EditorFor(m => m.UsersCanRegister) %>
<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.UsersMustValidateEmail)%>
<label class="forcheckbox" for="<%: Html.FieldIdFor( m => m.UsersMustValidateEmail) %>"><%: T("Users must justify their email address")%></label>
<%: Html.ValidationMessage("UsersMustValidateEmail", "*")%>
</div>
<div>
<%: 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>
<%: Html.ValidationMessage("UsersAreModerated", "*")%>
</div>
<div data-controllerid="<%:Html.FieldIdFor(m => m.UsersAreModerated) %>">
<%: Html.EditorFor(m => m.NotifyModeration)%>
<label class="forcheckbox" for="<%: Html.FieldIdFor( m => m.NotifyModeration) %>"><%: T("Send a notification when a user needs moderation")%></label>
<%: Html.ValidationMessage("NotifyModeration", "*")%>
</div>
</fieldset>

View File

@@ -18,6 +18,11 @@ namespace Orchard.Data.Migration.Schema {
TableCommands.Add(command);
}
public void AddColumn<T>(string columnName, Action<AddColumnCommand> column = null) {
var dbType = SchemaUtils.ToDbType(typeof(T));
AddColumn(columnName, dbType, column);
}
public void DropColumn(string columnName) {
var command = new DropColumnCommand(Name, columnName);
TableCommands.Add(command);

View File

@@ -1,11 +0,0 @@
using Orchard.ContentManagement.Records;
namespace Orchard.Messaging.Models {
public class Message {
public string Service { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public string Type { get; set; }
public ContentItemRecord Recipient { get; set; }
}
}

View File

@@ -1,16 +1,18 @@
using System.Collections.Generic;
using System.Net.Mail;
using Orchard.ContentManagement.Records;
namespace Orchard.Messaging.Models {
public class MessageContext {
public Dictionary<string, string> Properties { get; private set; }
public Message Message { get; private set; }
public MailMessage MailMessage { get; private set; }
public string Type { get; set; }
public string Service { get; set; }
public ContentItemRecord Recipient { get; set; }
public Dictionary<string, string> Properties { get; private set; }
public MessageContext(Message message) {
public MessageContext() {
Properties = new Dictionary<string, string>();
Message = message;
MailMessage = new MailMessage {Body = message.Body, Subject = message.Subject};
MailMessage = new MailMessage();
}
}
}

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic;
using Orchard.Messaging.Models;
using Orchard.ContentManagement.Records;
namespace Orchard.Messaging.Services {
public interface IMessageManager : IDependency {
/// <summary>
/// Sends a message without using the queue
/// Sends a message to a channel
/// </summary>
void Send(Message message);
void Send(ContentItemRecord recipient, string type, string service = null);
/// <summary>
/// Wether at least one channel is active on the current site

View File

@@ -9,11 +9,6 @@ namespace Orchard.Messaging.Services {
/// </summary>
void SendMessage(MessageContext message);
/// <summary>
/// Sends a message to the recipient to validate his account
/// </summary>
void ValidateRecipient(ContentItem recipient);
/// <summary>
/// Provides all the handled services, the user can choose from when receving messages
/// </summary>

View File

@@ -372,7 +372,6 @@
<Compile Include="Environment\IShellContainerRegistrations.cs" />
<Compile Include="FileSystems\Dependencies\DynamicModuleVirtualPathProvider.cs" />
<Compile Include="Messaging\Events\IMessageEventHandler.cs" />
<Compile Include="Messaging\Models\Message.cs" />
<Compile Include="Messaging\Models\MessageContext.cs" />
<Compile Include="Messaging\Services\IMessageManager.cs" />
<Compile Include="Messaging\Services\IMessagingChannel.cs" />