mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 03:25:23 +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:
@@ -148,6 +148,8 @@
|
|||||||
<Compile Include="Settings\Blueprint\ShellDescriptorManagerTests.cs" />
|
<Compile Include="Settings\Blueprint\ShellDescriptorManagerTests.cs" />
|
||||||
<Compile Include="Tags\Services\TagsServiceTests.cs" />
|
<Compile Include="Tags\Services\TagsServiceTests.cs" />
|
||||||
<Compile Include="Themes\Services\ThemeServiceTests.cs" />
|
<Compile Include="Themes\Services\ThemeServiceTests.cs" />
|
||||||
|
<Compile Include="Users\Controllers\AccountControllerTests.cs" />
|
||||||
|
<Compile Include="Users\Services\UserServiceTests.cs" />
|
||||||
<Compile Include="Values.cs" />
|
<Compile Include="Values.cs" />
|
||||||
<Compile Include="Users\Controllers\AdminControllerTests.cs" />
|
<Compile Include="Users\Controllers\AdminControllerTests.cs" />
|
||||||
<Compile Include="Users\Services\MembershipServiceTests.cs" />
|
<Compile Include="Users\Services\MembershipServiceTests.cs" />
|
||||||
|
@@ -0,0 +1,292 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.Mvc;
|
||||||
|
using System.Web.Routing;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Orchard.Caching;
|
||||||
|
using Orchard.ContentManagement.MetaData;
|
||||||
|
using Orchard.ContentManagement.MetaData.Models;
|
||||||
|
using Orchard.ContentManagement.MetaData.Services;
|
||||||
|
using Orchard.Core.Settings.Metadata;
|
||||||
|
using Orchard.Data;
|
||||||
|
using Orchard.DisplayManagement;
|
||||||
|
using Orchard.DisplayManagement.Descriptors;
|
||||||
|
using Orchard.DisplayManagement.Implementation;
|
||||||
|
using Orchard.Environment;
|
||||||
|
using Orchard.ContentManagement;
|
||||||
|
using Orchard.ContentManagement.Handlers;
|
||||||
|
using Orchard.ContentManagement.Records;
|
||||||
|
using Orchard.Environment.Extensions;
|
||||||
|
using Orchard.Localization;
|
||||||
|
using Orchard.Messaging.Events;
|
||||||
|
using Orchard.Messaging.Services;
|
||||||
|
using Orchard.Security;
|
||||||
|
using Orchard.Security.Permissions;
|
||||||
|
using Orchard.Tests.Stubs;
|
||||||
|
using Orchard.UI.Notify;
|
||||||
|
using Orchard.Users.Controllers;
|
||||||
|
using Orchard.Users.Handlers;
|
||||||
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.Users.Services;
|
||||||
|
using Orchard.Users.ViewModels;
|
||||||
|
using Orchard.Settings;
|
||||||
|
using Orchard.Core.Settings.Services;
|
||||||
|
using Orchard.Tests.Messaging;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
|
using Orchard.Core.Settings.Models;
|
||||||
|
using Orchard.Core.Settings.Handlers;
|
||||||
|
using Orchard.Messaging.Models;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace Orchard.Tests.Modules.Users.Controllers {
|
||||||
|
[TestFixture]
|
||||||
|
public class AccountControllerTests : DatabaseEnabledTestsBase {
|
||||||
|
private AccountController _controller;
|
||||||
|
private Mock<IAuthorizer> _authorizer;
|
||||||
|
private Mock<WorkContext> _workContext;
|
||||||
|
private MessagingChannelStub _channel;
|
||||||
|
|
||||||
|
public override void Register(ContainerBuilder builder) {
|
||||||
|
builder.RegisterType<AccountController>().SingleInstance();
|
||||||
|
builder.RegisterType<SiteService>().As<ISiteService>();
|
||||||
|
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
|
||||||
|
builder.RegisterType(typeof(SettingsFormatter))
|
||||||
|
.As(typeof(IMapper<XElement, SettingsDictionary>))
|
||||||
|
.As(typeof(IMapper<SettingsDictionary, XElement>));
|
||||||
|
builder.RegisterType<ContentDefinitionManager>().As<IContentDefinitionManager>();
|
||||||
|
builder.RegisterType<DefaultContentManagerSession>().As<IContentManagerSession>();
|
||||||
|
builder.RegisterType<DefaultContentQuery>().As<IContentQuery>().InstancePerDependency();
|
||||||
|
builder.RegisterType<DefaultMessageManager>().As<IMessageManager>();
|
||||||
|
builder.RegisterInstance(_channel = new MessagingChannelStub()).As<IMessagingChannel>();
|
||||||
|
builder.RegisterInstance(new Mock<IMessageEventHandler>().Object);
|
||||||
|
builder.RegisterInstance(new Mock<IAuthenticationService>().Object);
|
||||||
|
builder.RegisterType<MembershipService>().As<IMembershipService>();
|
||||||
|
builder.RegisterType<UserService>().As<IUserService>();
|
||||||
|
builder.RegisterType<UserPartHandler>().As<IContentHandler>();
|
||||||
|
builder.RegisterType<OrchardServices>().As<IOrchardServices>();
|
||||||
|
builder.RegisterType<TransactionManager>().As<ITransactionManager>();
|
||||||
|
builder.RegisterType<DefaultShapeTableManager>().As<IShapeTableManager>();
|
||||||
|
builder.RegisterType<DefaultShapeFactory>().As<IShapeFactory>();
|
||||||
|
builder.RegisterType<StubExtensionManager>().As<IExtensionManager>();
|
||||||
|
builder.RegisterType<SiteSettingsPartHandler>().As<IContentHandler>();
|
||||||
|
builder.RegisterType<RegistrationSettingsPartHandler>().As<IContentHandler>();
|
||||||
|
builder.RegisterInstance(new Mock<INotifier>().Object);
|
||||||
|
builder.RegisterInstance(new Mock<IContentDisplay>().Object);
|
||||||
|
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
|
||||||
|
builder.RegisterType<Signals>().As<ISignals>();
|
||||||
|
builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" });
|
||||||
|
|
||||||
|
_authorizer = new Mock<IAuthorizer>();
|
||||||
|
builder.RegisterInstance(_authorizer.Object);
|
||||||
|
|
||||||
|
_authorizer.Setup(x => x.Authorize(It.IsAny<Permission>(), It.IsAny<LocalizedString>())).Returns(true);
|
||||||
|
|
||||||
|
_workContext = new Mock<WorkContext>();
|
||||||
|
_workContext.Setup(w => w.GetState<ISite>(It.Is<string>(s => s == "CurrentSite"))).Returns(() => { return _container.Resolve<ISiteService>().GetSiteSettings(); });
|
||||||
|
|
||||||
|
var _workContextAccessor = new Mock<IWorkContextAccessor>();
|
||||||
|
_workContextAccessor.Setup(w => w.GetContext()).Returns(_workContext.Object);
|
||||||
|
builder.RegisterInstance(_workContextAccessor.Object).As<IWorkContextAccessor>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<Type> DatabaseTypes {
|
||||||
|
get {
|
||||||
|
return new[] { typeof(UserPartRecord),
|
||||||
|
typeof(SiteSettingsPartRecord),
|
||||||
|
typeof(RegistrationSettingsPartRecord),
|
||||||
|
typeof(ContentTypeRecord),
|
||||||
|
typeof(ContentItemRecord),
|
||||||
|
typeof(ContentItemVersionRecord),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Init() {
|
||||||
|
base.Init();
|
||||||
|
|
||||||
|
var manager = _container.Resolve<IContentManager>();
|
||||||
|
|
||||||
|
var superUser = manager.New<UserPart>("User");
|
||||||
|
superUser.Record = new UserPartRecord { UserName = "admin", NormalizedUserName = "admin", Email = "admin@orcharproject.com" };
|
||||||
|
manager.Create(superUser.ContentItem);
|
||||||
|
|
||||||
|
_controller = _container.Resolve<AccountController>();
|
||||||
|
|
||||||
|
var mockHttpContext = new Mock<HttpContextBase>();
|
||||||
|
_controller.ControllerContext = new ControllerContext(
|
||||||
|
mockHttpContext.Object,
|
||||||
|
new RouteData(
|
||||||
|
new Route("foo", new MvcRouteHandler()),
|
||||||
|
new MvcRouteHandler()),
|
||||||
|
_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void UsersShouldNotBeAbleToRegisterIfNotAllowed() {
|
||||||
|
|
||||||
|
// enable user registration
|
||||||
|
_container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>().UsersCanRegister = false;
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var result = _controller.Register();
|
||||||
|
Assert.That(result, Is.TypeOf<HttpNotFoundResult>());
|
||||||
|
|
||||||
|
result = _controller.Register("bar", "bar@baz.com", "66554321", "66554321");
|
||||||
|
Assert.That(result, Is.TypeOf<HttpNotFoundResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void UsersShouldBeAbleToRegisterIfAllowed() {
|
||||||
|
|
||||||
|
// disable user registration
|
||||||
|
_container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>().UsersCanRegister = true;
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var result = _controller.Register();
|
||||||
|
Assert.That(result, Is.TypeOf<ViewResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RegisteredUserShouldBeRedirectedToHomePage() {
|
||||||
|
|
||||||
|
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
|
||||||
|
registrationSettings.UsersCanRegister = true;
|
||||||
|
registrationSettings.UsersAreModerated = false;
|
||||||
|
registrationSettings.UsersMustValidateEmail = false;
|
||||||
|
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var result = _controller.Register("bar", "bar@baz.com", "66554321", "66554321");
|
||||||
|
|
||||||
|
Assert.That(result, Is.TypeOf<RedirectResult>());
|
||||||
|
Assert.That(((RedirectResult)result).Url, Is.EqualTo("~/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RegisteredUserShouldBeModerated() {
|
||||||
|
|
||||||
|
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
|
||||||
|
registrationSettings.UsersCanRegister = true;
|
||||||
|
registrationSettings.UsersAreModerated = true;
|
||||||
|
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var result = _controller.Register("bar", "bar@baz.com", "66554321", "66554321");
|
||||||
|
|
||||||
|
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
|
||||||
|
Assert.That(((RedirectToRouteResult)result).RouteValues["action"], Is.EqualTo("RegistrationPending"));
|
||||||
|
Assert.That(_channel.Messages.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SuperAdminShouldReceiveAMessageOnUserRegistration() {
|
||||||
|
|
||||||
|
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
|
||||||
|
registrationSettings.UsersCanRegister = true;
|
||||||
|
registrationSettings.UsersAreModerated = true;
|
||||||
|
registrationSettings.NotifyModeration = true;
|
||||||
|
|
||||||
|
_container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<SiteSettingsPart>().SuperUser = "admin";
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var result = _controller.Register("bar", "bar@baz.com", "66554321", "66554321");
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
var user = _container.Resolve<IMembershipService>().GetUser("bar");
|
||||||
|
|
||||||
|
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
|
||||||
|
Assert.That(((RedirectToRouteResult)result).RouteValues["action"], Is.EqualTo("RegistrationPending"));
|
||||||
|
Assert.That(_channel.Messages.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(user, Is.Not.Null);
|
||||||
|
Assert.That(user.UserName, Is.EqualTo("bar"));
|
||||||
|
Assert.That(user.As<UserPart>(), Is.Not.Null);
|
||||||
|
Assert.That(user.As<UserPart>().EmailStatus, Is.EqualTo(UserStatus.Approved));
|
||||||
|
Assert.That(user.As<UserPart>().RegistrationStatus, Is.EqualTo(UserStatus.Pending));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void InvalidLostPasswordRequestShouldNotResultInAnError() {
|
||||||
|
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
|
||||||
|
registrationSettings.UsersCanRegister = true;
|
||||||
|
_session.Flush();
|
||||||
|
|
||||||
|
_controller.Register("bar", "bar@baz.com", "66554321", "66554321");
|
||||||
|
|
||||||
|
_controller.Url = new UrlHelper(new RequestContext(new HttpContextStub(), new RouteData()));
|
||||||
|
|
||||||
|
var result = _controller.LostPassword("foo");
|
||||||
|
|
||||||
|
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
|
||||||
|
Assert.That(((RedirectToRouteResult)result).RouteValues["action"], Is.EqualTo("LogOn"));
|
||||||
|
Assert.That(_channel.Messages.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResetPasswordLinkShouldBeSent() {
|
||||||
|
var registrationSettings = _container.Resolve<IWorkContextAccessor>().GetContext().CurrentSite.As<RegistrationSettingsPart>();
|
||||||
|
registrationSettings.UsersCanRegister = 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");
|
||||||
|
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
|
||||||
|
|
||||||
|
Assert.That(((RedirectToRouteResult)result).RouteValues["action"], Is.EqualTo("LogOn"));
|
||||||
|
Assert.That(_channel.Messages.Count, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Ignore("To be implemented")]
|
||||||
|
public void ChallengeEmailShouldUnlockAccount() {
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Ignore("To be implemented")]
|
||||||
|
public void LostPasswordEmailShouldAuthenticateUser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpContextStub : HttpContextBase {
|
||||||
|
public override HttpRequestBase Request {
|
||||||
|
get { return new HttpRequestStub(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IHttpHandler Handler { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpRequestStub : HttpRequestBase {
|
||||||
|
public override bool IsAuthenticated {
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override NameValueCollection Form {
|
||||||
|
get {
|
||||||
|
return new NameValueCollection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Uri Url {
|
||||||
|
get {
|
||||||
|
return new Uri("http://orchardproject.net");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override NameValueCollection Headers {
|
||||||
|
get {
|
||||||
|
var nv = new NameValueCollection();
|
||||||
|
nv["Host"] = "orchardproject.net";
|
||||||
|
return nv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
145
src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs
Normal file
145
src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Web.Security;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Autofac;
|
||||||
|
using Moq;
|
||||||
|
using NHibernate;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Orchard.ContentManagement.MetaData;
|
||||||
|
using Orchard.ContentManagement.MetaData.Models;
|
||||||
|
using Orchard.ContentManagement.MetaData.Services;
|
||||||
|
using Orchard.Core.Settings.Metadata;
|
||||||
|
using Orchard.Data;
|
||||||
|
using Orchard.ContentManagement;
|
||||||
|
using Orchard.ContentManagement.Handlers;
|
||||||
|
using Orchard.ContentManagement.Records;
|
||||||
|
using Orchard.DisplayManagement;
|
||||||
|
using Orchard.DisplayManagement.Descriptors;
|
||||||
|
using Orchard.DisplayManagement.Implementation;
|
||||||
|
using Orchard.Environment;
|
||||||
|
using Orchard.Environment.Extensions;
|
||||||
|
using Orchard.Messaging.Events;
|
||||||
|
using Orchard.Messaging.Services;
|
||||||
|
using Orchard.Security;
|
||||||
|
using Orchard.Tests.Stubs;
|
||||||
|
using Orchard.Tests.Utility;
|
||||||
|
using Orchard.Users.Handlers;
|
||||||
|
using Orchard.Users.Models;
|
||||||
|
using Orchard.Users.Services;
|
||||||
|
using Orchard.Services;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
|
using Orchard.Tests.Messaging;
|
||||||
|
|
||||||
|
namespace Orchard.Tests.Modules.Users.Services {
|
||||||
|
[TestFixture]
|
||||||
|
public class UserServiceTests {
|
||||||
|
private IMembershipService _membershipService;
|
||||||
|
private IUserService _userService;
|
||||||
|
private IClock _clock;
|
||||||
|
private MessagingChannelStub _channel;
|
||||||
|
private ISessionFactory _sessionFactory;
|
||||||
|
private ISession _session;
|
||||||
|
private IContainer _container;
|
||||||
|
|
||||||
|
|
||||||
|
public class TestSessionLocator : ISessionLocator {
|
||||||
|
private readonly ISession _session;
|
||||||
|
|
||||||
|
public TestSessionLocator(ISession session) {
|
||||||
|
_session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISession For(Type entityType) {
|
||||||
|
return _session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestFixtureSetUp]
|
||||||
|
public void InitFixture() {
|
||||||
|
var databaseFileName = System.IO.Path.GetTempFileName();
|
||||||
|
_sessionFactory = DataUtility.CreateSessionFactory(
|
||||||
|
databaseFileName,
|
||||||
|
typeof(UserPartRecord),
|
||||||
|
typeof(ContentItemVersionRecord),
|
||||||
|
typeof(ContentItemRecord),
|
||||||
|
typeof(ContentTypeRecord));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestFixtureTearDown]
|
||||||
|
public void TermFixture() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Init() {
|
||||||
|
var builder = new ContainerBuilder();
|
||||||
|
//builder.RegisterModule(new ImplicitCollectionSupportModule());
|
||||||
|
builder.RegisterType<MembershipService>().As<IMembershipService>();
|
||||||
|
builder.RegisterType<UserService>().As<IUserService>();
|
||||||
|
builder.RegisterInstance(_clock = new StubClock()).As<IClock>();
|
||||||
|
builder.RegisterType<DefaultContentQuery>().As<IContentQuery>();
|
||||||
|
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
|
||||||
|
builder.RegisterType(typeof(SettingsFormatter))
|
||||||
|
.As(typeof(IMapper<XElement, SettingsDictionary>))
|
||||||
|
.As(typeof(IMapper<SettingsDictionary, XElement>));
|
||||||
|
builder.RegisterType<ContentDefinitionManager>().As<IContentDefinitionManager>();
|
||||||
|
builder.RegisterType<DefaultContentManagerSession>().As<IContentManagerSession>();
|
||||||
|
builder.RegisterType<UserPartHandler>().As<IContentHandler>();
|
||||||
|
builder.RegisterType<StubWorkContextAccessor>().As<IWorkContextAccessor>();
|
||||||
|
builder.RegisterType<OrchardServices>().As<IOrchardServices>();
|
||||||
|
builder.RegisterAutoMocking(MockBehavior.Loose);
|
||||||
|
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
|
||||||
|
builder.RegisterInstance(new Mock<IMessageEventHandler>().Object);
|
||||||
|
builder.RegisterType<DefaultMessageManager>().As<IMessageManager>();
|
||||||
|
builder.RegisterInstance(_channel = new MessagingChannelStub()).As<IMessagingChannel>();
|
||||||
|
builder.RegisterType<DefaultShapeTableManager>().As<IShapeTableManager>();
|
||||||
|
builder.RegisterType<DefaultShapeFactory>().As<IShapeFactory>();
|
||||||
|
builder.RegisterType<StubExtensionManager>().As<IExtensionManager>();
|
||||||
|
builder.RegisterType<DefaultContentDisplay>().As<IContentDisplay>();
|
||||||
|
builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" });
|
||||||
|
|
||||||
|
_session = _sessionFactory.OpenSession();
|
||||||
|
builder.RegisterInstance(new TestSessionLocator(_session)).As<ISessionLocator>();
|
||||||
|
_container = builder.Build();
|
||||||
|
_membershipService = _container.Resolve<IMembershipService>();
|
||||||
|
_userService = _container.Resolve<IUserService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NonceShouldBeDecryptable() {
|
||||||
|
var user = _membershipService.CreateUser(new CreateUserParams("foo", "66554321", "foo@bar.com", "", "", true));
|
||||||
|
var nonce = _userService.CreateNonce(user, new TimeSpan(1, 0, 0));
|
||||||
|
|
||||||
|
Assert.That(nonce, Is.Not.Empty);
|
||||||
|
|
||||||
|
string username;
|
||||||
|
DateTime validateByUtc;
|
||||||
|
|
||||||
|
var result = _userService.DecryptNonce(nonce, out username, out validateByUtc);
|
||||||
|
|
||||||
|
Assert.That(result, Is.True);
|
||||||
|
Assert.That(username, Is.EqualTo("foo"));
|
||||||
|
Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NonceShouldNotBeUsedOnAnotherTenant() {
|
||||||
|
var user = _membershipService.CreateUser(new CreateUserParams("foo", "66554321", "foo@bar.com", "", "", true));
|
||||||
|
var nonce = _userService.CreateNonce(user, new TimeSpan(1, 0, 0));
|
||||||
|
|
||||||
|
Assert.That(nonce, Is.Not.Empty);
|
||||||
|
|
||||||
|
string username;
|
||||||
|
DateTime validateByUtc;
|
||||||
|
|
||||||
|
_container.Resolve<ShellSettings>().Name = "Beta";
|
||||||
|
|
||||||
|
var result = _userService.DecryptNonce(nonce, out username, out validateByUtc);
|
||||||
|
|
||||||
|
Assert.That(result, Is.False);
|
||||||
|
Assert.That(username, Is.EqualTo("foo"));
|
||||||
|
Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
28
src/Orchard.Tests/Messaging/MessagingChannelStub.cs
Normal file
28
src/Orchard.Tests/Messaging/MessagingChannelStub.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Orchard.Messaging.Services;
|
||||||
|
using Orchard.Messaging.Models;
|
||||||
|
|
||||||
|
namespace Orchard.Tests.Messaging {
|
||||||
|
public class MessagingChannelStub : IMessagingChannel {
|
||||||
|
public List<MessageContext> Messages { get; private set; }
|
||||||
|
|
||||||
|
public MessagingChannelStub() {
|
||||||
|
Messages = new List<MessageContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IMessagingChannel Members
|
||||||
|
|
||||||
|
public void SendMessage(MessageContext message) {
|
||||||
|
Messages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableServices() {
|
||||||
|
yield return "email";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@@ -239,6 +239,7 @@
|
|||||||
<Compile Include="FileSystems\Dependencies\AssemblyProbingFolderTests.cs" />
|
<Compile Include="FileSystems\Dependencies\AssemblyProbingFolderTests.cs" />
|
||||||
<Compile Include="FileSystems\Dependencies\DependenciesFolderTests.cs" />
|
<Compile Include="FileSystems\Dependencies\DependenciesFolderTests.cs" />
|
||||||
<Compile Include="Localization\CultureManagerTests.cs" />
|
<Compile Include="Localization\CultureManagerTests.cs" />
|
||||||
|
<Compile Include="Messaging\MessagingChannelStub.cs" />
|
||||||
<Compile Include="Mvc\Html\HtmlHelperExtensionsTests.cs" />
|
<Compile Include="Mvc\Html\HtmlHelperExtensionsTests.cs" />
|
||||||
<Compile Include="Mvc\Routes\ShellRouteTests.cs" />
|
<Compile Include="Mvc\Routes\ShellRouteTests.cs" />
|
||||||
<Compile Include="Mvc\Routes\UrlPrefixTests.cs" />
|
<Compile Include="Mvc\Routes\UrlPrefixTests.cs" />
|
||||||
|
@@ -120,8 +120,7 @@ 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 = _userService.GetNonce(user.As<UserPart>());
|
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce })));
|
||||||
_userService.SendChallengeEmail(user.As<UserPart>(), Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", token = challengeToken })));
|
|
||||||
|
|
||||||
return RedirectToAction("ChallengeEmailSent");
|
return RedirectToAction("ChallengeEmailSent");
|
||||||
}
|
}
|
||||||
|
@@ -197,8 +197,7 @@ namespace Orchard.Users.Controllers {
|
|||||||
var user = Services.ContentManager.Get(id);
|
var user = Services.ContentManager.Get(id);
|
||||||
|
|
||||||
if ( user != null ) {
|
if ( user != null ) {
|
||||||
string challengeToken = _userService.GetNonce(user.As<UserPart>());
|
_userService.SendChallengeEmail(user.As<UserPart>(), nonce => Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new {Area = "Orchard.Users", nonce = nonce})));
|
||||||
_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"));
|
||||||
|
@@ -5,12 +5,13 @@ namespace Orchard.Users.Services {
|
|||||||
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);
|
void SendChallengeEmail(IUser user, Func<string, string> createUrl);
|
||||||
IUser ValidateChallenge(string challengeToken);
|
IUser ValidateChallenge(string challengeToken);
|
||||||
|
|
||||||
bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl);
|
bool SendLostPasswordEmail(string usernameOrEmail, Func<string, string> createUrl);
|
||||||
IUser ValidateLostPassword(string nonce);
|
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 {
|
namespace Orchard.Users.Services {
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class MembershipService : IMembershipService {
|
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 IOrchardServices _orchardServices;
|
||||||
private readonly IMessageManager _messageManager;
|
private readonly IMessageManager _messageManager;
|
||||||
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
|
||||||
|
@@ -12,22 +12,26 @@ using System.Globalization;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Web.Security;
|
using System.Web.Security;
|
||||||
using Orchard.Messaging.Services;
|
using Orchard.Messaging.Services;
|
||||||
|
using Orchard.Environment.Configuration;
|
||||||
|
|
||||||
namespace Orchard.Users.Services {
|
namespace Orchard.Users.Services {
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class UserService : IUserService {
|
public class UserService : IUserService {
|
||||||
private static readonly TimeSpan DelayToValidate = new TimeSpan(7, 0, 0, 0); // one week to validate email
|
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 IContentManager _contentManager;
|
||||||
private readonly IMembershipService _membershipService;
|
private readonly IMembershipService _membershipService;
|
||||||
private readonly IClock _clock;
|
private readonly IClock _clock;
|
||||||
private readonly IMessageManager _messageManager;
|
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;
|
_contentManager = contentManager;
|
||||||
_membershipService = membershipService;
|
_membershipService = membershipService;
|
||||||
_clock = clock;
|
_clock = clock;
|
||||||
_messageManager = messageManager;
|
_messageManager = messageManager;
|
||||||
|
_shellSettings = shellSettings;
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,23 +67,25 @@ namespace Orchard.Users.Services {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetNonce(IUser user) {
|
public string CreateNonce(IUser user, TimeSpan delay) {
|
||||||
var challengeToken = new XElement("Token", new XAttribute("username", user.UserName), new XAttribute("validate-by-utc", _clock.UtcNow.Add(DelayToValidate).ToString(CultureInfo.InvariantCulture))).ToString();
|
// the tenant's name is added to the token to prevent cross-tenant requests
|
||||||
var data = Encoding.UTF8.GetBytes(challengeToken);
|
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);
|
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;
|
username = null;
|
||||||
validateByUtc = _clock.UtcNow;
|
validateByUtc = _clock.UtcNow;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
|
var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All);
|
||||||
var xml = Encoding.UTF8.GetString(data);
|
var xml = Encoding.Unicode.GetString(data);
|
||||||
var element = XElement.Parse(xml);
|
var element = XElement.Parse(xml);
|
||||||
username = element.Attribute("username").Value;
|
var tenant = element.Attribute("s").Value;
|
||||||
validateByUtc = DateTime.Parse(element.Attribute("validate-by-utc").Value, CultureInfo.InvariantCulture);
|
username = element.Attribute("un").Value;
|
||||||
return true;
|
validateByUtc = DateTime.Parse(element.Attribute("utc").Value, CultureInfo.InvariantCulture);
|
||||||
|
return String.Equals(_shellSettings.Name, tenant, StringComparison.Ordinal) && _clock.UtcNow <= validateByUtc;
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
return false;
|
return false;
|
||||||
@@ -107,7 +113,9 @@ namespace Orchard.Users.Services {
|
|||||||
return user;
|
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 } });
|
_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();
|
var user = _contentManager.Query<UserPart, UserPartRecord>().Where(u => u.NormalizedUserName == lowerName || u.Email == lowerName).List().FirstOrDefault();
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
string nonce = GetNonce(user);
|
string nonce = CreateNonce(user, DelayToResetPassword);
|
||||||
string url = createUrl(nonce);
|
string url = createUrl(nonce);
|
||||||
|
|
||||||
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
|
_messageManager.Send(user.ContentItem.Record, MessageTypes.LostPassword, "email", new Dictionary<string, string> { { "LostPasswordUrl", url } });
|
||||||
|
@@ -18,6 +18,7 @@ namespace Orchard.Messaging.Services {
|
|||||||
IEnumerable<IMessagingChannel> channels) {
|
IEnumerable<IMessagingChannel> channels) {
|
||||||
_messageEventHandler = messageEventHandler;
|
_messageEventHandler = messageEventHandler;
|
||||||
_channels = channels;
|
_channels = channels;
|
||||||
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(ContentItemRecord recipient, string type, string service, Dictionary<string, string> properties = null) {
|
public void Send(ContentItemRecord recipient, string type, string service, Dictionary<string, string> properties = null) {
|
||||||
|
Reference in New Issue
Block a user