diff --git a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj index 7095bac5..9603d6a5 100644 --- a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj +++ b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj @@ -11,9 +11,10 @@ Properties FunnelWeb.Extensions.CommentNotification FunnelWeb.Extensions.CommentNotification - v4.0 + v4.5 512 ..\ + true @@ -23,6 +24,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -31,13 +33,12 @@ TRACE prompt 4 + false - - ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.dll - - - ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.Configuration.dll + + False + ..\packages\Autofac.3.1.3\lib\net40\Autofac.dll diff --git a/src/FunnelWeb.Extensions.CommentNotification/packages.config b/src/FunnelWeb.Extensions.CommentNotification/packages.config index 842884bd..3d28ef54 100644 --- a/src/FunnelWeb.Extensions.CommentNotification/packages.config +++ b/src/FunnelWeb.Extensions.CommentNotification/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/FunnelWeb.Tests/App.config b/src/FunnelWeb.Tests/App.config index c3131a17..afec4dc0 100644 --- a/src/FunnelWeb.Tests/App.config +++ b/src/FunnelWeb.Tests/App.config @@ -1,30 +1,62 @@ - + - - -
- - - - - - - - - - - - - - - - - - - - - - - - + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs b/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs new file mode 100644 index 00000000..fd8706a7 --- /dev/null +++ b/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs @@ -0,0 +1,18 @@ +using System.Diagnostics; +using FunnelWeb.Authentication.Internal; +using NUnit.Framework; + +namespace FunnelWeb.Tests.Authentication.Internal +{ + [TestFixture] + public class SqlFunnelWebMembershipTests + { + [Test] + public void HashPassword() + { + string hashPassword = SqlFunnelWebMembership.HashPassword("ThisIsMyPassword"); + Debug.WriteLine(hashPassword); + Assert.AreEqual("30B8BD5829888900D15D2BBE6270D9BC65B0702F", hashPassword); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs b/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs index 96aceff5..51a824cb 100644 --- a/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs +++ b/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs @@ -10,125 +10,127 @@ namespace FunnelWeb.Tests.DatabaseDeployer { - [TestFixture] - public class DatabaseUpgradeDetectorTests - { - private DatabaseUpgradeDetector detector; - private IConnectionStringSettings connectionString; - private IApplicationDatabase applicationDatabase; - private readonly List extensions = new List(); - private IDatabaseProvider databaseProvider; - - [SetUp] - public void SetUp() - { - connectionString = Substitute.For(); - connectionString.Schema = "dbo"; - connectionString.DatabaseProvider = "sql"; - applicationDatabase = Substitute.For(); - databaseProvider = Substitute.For(); - detector = new DatabaseUpgradeDetector(connectionString, extensions, applicationDatabase, databaseProvider); - } - - [Test] - public void UpdateNeededIfDatabaseIsOffline() - { - DatabaseIsOnline(false); - - var needed = detector.UpdateNeeded(); - Assert.IsTrue(needed); - } - - [Test] - public void UpdateNeededIfMainDatabaseIsOld() - { - DatabaseIsOnline(true); - CurrentSchemaVersionIs(10); - RequiredApplicationVersionIs(20); - - var needed = detector.UpdateNeeded(); - Assert.IsTrue(needed); - } - - [Test] - public void UpdateNotNeededIfUpToDate() - { - DatabaseIsOnline(true); - CurrentSchemaVersionIs(10); - RequiredApplicationVersionIs(10); - - var needed = detector.UpdateNeeded(); - Assert.IsFalse(needed); - } - - [Test] - public void UpdateNeededIfExtensionsOld() - { - extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For())); - - DatabaseIsOnline(true); - CurrentSchemaVersionIs(10); - RequiredApplicationVersionIs(10); - CurrentExtensionVersionIs(1); - RequiredExtensionVersionIs(4); - - var needed = detector.UpdateNeeded(); - Assert.IsTrue(needed); - } - - [Test] - public void UpdateNotNeededIfExtensionsUpToDate() - { - extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For())); - - DatabaseIsOnline(true); - CurrentSchemaVersionIs(10); - RequiredApplicationVersionIs(10); - CurrentExtensionVersionIs(4); - RequiredExtensionVersionIs(4); - - var needed = detector.UpdateNeeded(); - Assert.IsFalse(needed); - } - - #region Helpers - - private void DatabaseIsOnline(bool isItReally) - { - string message; - databaseProvider - .TryConnect(Arg.Any(), out message) - .Returns(isItReally); - } - - private void CurrentSchemaVersionIs(int version) - { - applicationDatabase - .GetCoreExecutedScripts(Arg.Any>()) - .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); - } - - private void RequiredApplicationVersionIs(int version) - { - applicationDatabase - .GetCoreRequiredScripts(Arg.Any>()) - .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); - } - - private void CurrentExtensionVersionIs(int version) - { - applicationDatabase - .GetExtensionExecutedScripts(Arg.Any>(), Arg.Any()) - .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); - } - - private void RequiredExtensionVersionIs(int version) - { - applicationDatabase - .GetExtensionRequiredScripts(Arg.Any>(), Arg.Any()) - .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); - } - - #endregion - } + [TestFixture] + public class DatabaseUpgradeDetectorTests + { + private DatabaseUpgradeDetector detector; + private IConnectionStringSettings connectionString; + private IApplicationDatabase applicationDatabase; + private readonly List extensions = new List(); + private IDatabaseProvider databaseProvider; + private IDatabaseConnectionDetector databaseConnectionDetector; + + [SetUp] + public void SetUp() + { + connectionString = Substitute.For(); + connectionString.Schema = "dbo"; + connectionString.DatabaseProvider = "sql"; + applicationDatabase = Substitute.For(); + databaseProvider = Substitute.For(); + databaseConnectionDetector = Substitute.For(); + detector = new DatabaseUpgradeDetector(connectionString, extensions, applicationDatabase, databaseProvider, databaseConnectionDetector); + } + + [Test] + public void UpdateNeededIfDatabaseIsOffline() + { + DatabaseIsOnline(false); + + var needed = detector.UpdateNeeded(); + Assert.IsTrue(needed); + } + + [Test] + public void UpdateNeededIfMainDatabaseIsOld() + { + DatabaseIsOnline(true); + CurrentSchemaVersionIs(10); + RequiredApplicationVersionIs(20); + + var needed = detector.UpdateNeeded(); + Assert.IsTrue(needed); + } + + [Test] + public void UpdateNotNeededIfUpToDate() + { + DatabaseIsOnline(true); + CurrentSchemaVersionIs(10); + RequiredApplicationVersionIs(10); + + var needed = detector.UpdateNeeded(); + Assert.IsFalse(needed); + } + + [Test] + public void UpdateNeededIfExtensionsOld() + { + extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For())); + + DatabaseIsOnline(true); + CurrentSchemaVersionIs(10); + RequiredApplicationVersionIs(10); + CurrentExtensionVersionIs(1); + RequiredExtensionVersionIs(4); + + var needed = detector.UpdateNeeded(); + Assert.IsTrue(needed); + } + + [Test] + public void UpdateNotNeededIfExtensionsUpToDate() + { + extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For())); + + DatabaseIsOnline(true); + CurrentSchemaVersionIs(10); + RequiredApplicationVersionIs(10); + CurrentExtensionVersionIs(4); + RequiredExtensionVersionIs(4); + + var needed = detector.UpdateNeeded(); + Assert.IsFalse(needed); + } + + #region Helpers + + private void DatabaseIsOnline(bool isItReally) + { + string message; + databaseProvider + .TryConnect(Arg.Any(), out message) + .Returns(isItReally); + } + + private void CurrentSchemaVersionIs(int version) + { + applicationDatabase + .GetCoreExecutedScripts(Arg.Any>()) + .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); + } + + private void RequiredApplicationVersionIs(int version) + { + applicationDatabase + .GetCoreRequiredScripts(Arg.Any>()) + .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); + } + + private void CurrentExtensionVersionIs(int version) + { + applicationDatabase + .GetExtensionExecutedScripts(Arg.Any>(), Arg.Any()) + .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); + } + + private void RequiredExtensionVersionIs(int version) + { + applicationDatabase + .GetExtensionRequiredScripts(Arg.Any>(), Arg.Any()) + .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray()); + } + + #endregion + } } diff --git a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj index 3a1c3a2d..f0016db8 100644 --- a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj +++ b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj @@ -11,9 +11,10 @@ Properties FunnelWeb.Tests FunnelWeb.Tests - v4.0 + v4.5 512 ..\ + true @@ -24,6 +25,7 @@ prompt 4 x86 + false pdbonly @@ -33,13 +35,16 @@ prompt 4 x86 + false - - ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.dll + + False + ..\packages\Autofac.3.1.3\lib\net40\Autofac.dll - - ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.Configuration.dll + + False + ..\packages\Autofac.Mvc4.3.1.0\lib\net40\Autofac.Integration.Mvc.dll False @@ -85,17 +90,13 @@ True ..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll + + ..\packages\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.4.5.1\lib\net45\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.dll + - - True - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll - - - True - ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll - + True ..\packages\Microsoft.AspNet.Razor.2.0.20710.0\lib\net40\System.Web.Razor.dll @@ -138,6 +139,7 @@ Properties\VersionInfo.cs + @@ -178,9 +180,12 @@ + + + diff --git a/src/FunnelWeb.Tests/LifetimeJustifications.cs b/src/FunnelWeb.Tests/LifetimeJustifications.cs index 0bbb18b3..43e15789 100644 --- a/src/FunnelWeb.Tests/LifetimeJustifications.cs +++ b/src/FunnelWeb.Tests/LifetimeJustifications.cs @@ -31,181 +31,185 @@ namespace FunnelWeb.Tests { - /// - /// We often get subtle bugs introduced by changes to lifetime assumptions. The following test fixture - /// is a cross-cutting fixture that explains the assumptions behind why every component has the lifetime - /// it is given. - /// - /// If you want to change any of these lifetimes, please consider it very carefully, and test thoroughly. - /// - [TestFixture] - public class LifetimeJustifications - { - private IContainer container; - - /// - /// We could set up a re-usable class that calls this, but configuration is pretty important - /// and I'd rather make it obvious in Global.asax. - /// - [SetUp] - public void IntializeTheContainerJustLikeInGlobalAsax() - { - var builder = new ContainerBuilder(); - - var routes = new RouteCollection(); - - // FunnelWeb Database - builder.RegisterModule(new DatabaseModule()); - - //// FunnelWeb Core - builder.RegisterModule(new SettingsModule("C:\\Foo\\My.config")); - builder.RegisterModule(new TasksModule()); // HACK: Need a better way to enable the TasksModule to create lifetime scopes from the root - builder.RegisterModule(new RepositoriesModule()); - builder.RegisterModule(new EventingModule()); - builder.RegisterModule(new ExtensionsModule("C:\\Foo", routes)); - builder.RegisterModule(new InternalProviderRegistrationModule()); - - //// FunnelWeb Web - builder.RegisterModule(new WebAbstractionsModule()); - builder.RegisterModule(new AuthenticationModule()); - builder.RegisterModule(new BindersModule(new ModelBinderDictionary())); - builder.RegisterModule(new MimeSupportModule()); - builder.RegisterModule(new ThemesModule()); - builder.RegisterModule(new SpamModule()); - // We don't register the RoutesModule, because all it does is register MVC routes, not components - // with lifetimes that we care about - - container = builder.Build(); - } - - [Test] - public void ComponentsThatShouldBeSingletons() - { - // There is a very strong case for the following lifetimes to never be changed - IsSingleton("There should only be one session factory in the application"); - IsSingleton("This component only executes SQL queries, and it takes the connection string as a parameter - therefore, only one instance should exist"); - IsSingleton("This component helps optimize performance by caching the result of whether the database is up to date. Making this component anything but singleton would make the caching benefit useless."); - - // The following are singletons just because there's no need to have more than one - if you have a good - // reason feel free to change - IsSingleton("This component uses the bootstrap settings to store the connection string. Since the bootstrap settings are opened/closed on the fly, there only needs to be one instance of this type."); - IsSingleton("This component opens/closes the XML file on the fly; there's no need to have more than one."); - IsSingleton("It just calls the registry/a static list - no need for more than one"); - - IsSingleton>("This type extends the contains, no need to create multiple times"); - IsSingleton>("This type extends the contains, no need to create multiple times"); - IsSingleton("Settings provider provides caching, it depends on a factory to connect to the database which will be scoped to http request."); - } - - [Test] - public void ComponentsThatShouldBePerRequest() - { - // You will never have any possible reason to change this. Just don't. I kill you! - PerLifetimeScope("There should be at most one session per request. Repositories depend on an ISession. Since a controller may use many repositories, all repositories need the same session."); - - // Per request just for performance, could otherwise be per dependency - PerLifetimeScope("This component takes a list of handlers; to save finding them all each time we raise an event during a request, we build this once"); - PerLifetimeScope("Could be anything, but authentication should be done per request"); - PerLifetimeScope("Could be anything, but authentication should be done per request."); - PerLifetimeScope("Could be anything, but authentication should be done per request."); - PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); - PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); - PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); - PerLifetimeScope("Uses factories to get as session, so could be anything really."); - PerLifetimeScope("Uses factories to get as session, so could be anything really."); - PerLifetimeScope("Uses factories to get as session, so could be anything really."); - - // HTTP abstractions, therefore obviously per request - PerLifetimeScope("Comes from HTTP context"); - PerLifetimeScope("Comes from HTTP context"); - PerLifetimeScope("Comes from HTTP context"); - - // Repositories - const string repositoriesReason = "Generally we're unlikely to resolve repositories more than once per request, and they are stateless anyway, but just in case let's re-use the same one"; - PerLifetimeScope(repositoriesReason); - PerLifetimeScope(repositoriesReason); - PerLifetimeScope(repositoriesReason); - PerLifetimeScope(repositoriesReason); - } - - [Test] - public void ComponentsThatShouldBePerDependency() - { - // Could be per lifetime or per dependency (but definitely not singletons) - IsPerDependency("Tasks are created in their own lifetime scope, but only one is ever created, therefore they should all be per dependency."); - IsPerDependency("This component just scans files in a directory, lifetime doesn't matter. It does use HttpContext though, so it shouldn't be singleton"); - IsPerDependency("This component depends on settings (which depends on an ISession), so it definitely shouldn't be singleton"); - } - - [Test] - public void AllComponentLifetimesHaveBeenJustified() - { - ComponentsThatShouldBeSingletons(); - ComponentsThatShouldBePerRequest(); - ComponentsThatShouldBePerDependency(); - - var typesNotTested = container.ComponentRegistry.Registrations - .SelectMany(x => x.Services.OfType().Select(t => new { t.ServiceType, Registration = x}) ) - .Where(x => !seenTypes.Any(s => s == x.ServiceType)) - .Where(x => !x.ServiceType.Namespace.StartsWith("Autofac")) // We don't care about internal Autofac types - .Where(x => !x.ServiceType.FullName.Contains("IContainerAwareComponent")) // Some whacky Autofac thing I don't understand - //new version of autofac seems to register a IEnumerable - .Where(x => x.ServiceType.IsGenericType && !x.ServiceType.GetGenericArguments()[0].Namespace.StartsWith("Autofac")) - .ToList(); - - if (typesNotTested.Count == 0) - return; - - Trace.WriteLine("Forgot to test assumptions around the following types:"); - foreach (var item in typesNotTested) - { - Trace.WriteLine(" - " + item.ServiceType.FullName + " (current lifetime: " + GetLifetime(item.Registration) + ")"); - } - - Assert.Fail("One or more types don't have tests regarding their lifetime. Please justify the lifetime of all components. See the trace output of this test to see the types that weren't tested."); - } - - #region Helpers - - private void IsSingleton(string reason) - { - Is(reason, x => GetLifetime(x) == "Singleton"); - } - - private void PerLifetimeScope(string reason) - { - Is(reason, x => GetLifetime(x) == "PerLifetimeScope"); - } - - private void IsPerDependency(string reason) - { - Is(reason, x => GetLifetime(x) == "PerDependency"); - } - - private string GetLifetime(IComponentRegistration rego) - { - if (rego.Lifetime is RootScopeLifetime) - { - return "Singleton"; - } - if (rego.Lifetime is CurrentScopeLifetime && rego.Sharing == InstanceSharing.Shared) - { - return "PerLifetimeScope"; - } - return "PerDependency"; - } - - private void Is(string reason, Func predicate) - { - var regos = container.ComponentRegistry.RegistrationsFor(new TypedService(typeof(TService))); - if (!regos.Any(predicate)) - Assert.Fail(string.Format("Component {0} is not registered correctly: {1}", typeof(TService).Name, reason)); - - seenTypes.Add(typeof (TService)); - } - - private readonly HashSet seenTypes = new HashSet(); - - #endregion - } -} + /// + /// We often get subtle bugs introduced by changes to lifetime assumptions. The following test fixture + /// is a cross-cutting fixture that explains the assumptions behind why every component has the lifetime + /// it is given. + /// + /// If you want to change any of these lifetimes, please consider it very carefully, and test thoroughly. + /// + [TestFixture] + public class LifetimeJustifications + { + private IContainer container; + + /// + /// We could set up a re-usable class that calls this, but configuration is pretty important + /// and I'd rather make it obvious in Global.asax. + /// + [SetUp] + public void IntializeTheContainerJustLikeInGlobalAsax() + { + var builder = new ContainerBuilder(); + + var routes = new RouteCollection(); + + // FunnelWeb Database + builder.RegisterModule(new DatabaseModule()); + + //// FunnelWeb Core + builder.RegisterModule(new SettingsModule("C:\\Foo\\My.config")); + builder.RegisterModule(new TasksModule()); // HACK: Need a better way to enable the TasksModule to create lifetime scopes from the root + builder.RegisterModule(new RepositoriesModule()); + builder.RegisterModule(new EventingModule()); + builder.RegisterModule(new ExtensionsModule("C:\\Foo", routes)); + builder.RegisterModule(new InternalProviderRegistrationModule()); + + //// FunnelWeb Web + builder.RegisterModule(new WebAbstractionsModule()); + builder.RegisterModule(new AuthenticationModule()); + builder.RegisterModule(new BindersModule(new ModelBinderDictionary())); + builder.RegisterModule(new MimeSupportModule()); + builder.RegisterModule(new ThemesModule()); + builder.RegisterModule(new SpamModule()); + // We don't register the RoutesModule, because all it does is register MVC routes, not components + // with lifetimes that we care about + + container = builder.Build(); + } + + [Test] + public void ComponentsThatShouldBeSingletons() + { + // There is a very strong case for the following lifetimes to never be changed + IsSingleton("There should only be one session factory in the application"); + IsSingleton("This component only executes SQL queries, and it takes the connection string as a parameter - therefore, only one instance should exist"); + IsSingleton("This component helps optimize performance by caching the result of whether the database is up to date. Making this component anything but singleton would make the caching benefit useless."); + + // The following are singletons just because there's no need to have more than one - if you have a good + // reason feel free to change + IsSingleton("This component uses the bootstrap settings to store the connection string. Since the bootstrap settings are opened/closed on the fly, there only needs to be one instance of this type."); + IsSingleton("This component opens/closes the XML file on the fly; there's no need to have more than one."); + IsSingleton("It just calls the registry/a static list - no need for more than one"); + + IsSingleton>("This type extends the contains, no need to create multiple times"); + IsSingleton>("This type extends the contains, no need to create multiple times"); + IsSingleton("Settings provider provides caching, it depends on a factory to connect to the database which will be scoped to http request."); + } + + [Test] + public void ComponentsThatShouldBePerRequest() + { + // You will never have any possible reason to change this. Just don't. I kill you! + PerLifetimeScope("There should be at most one session per request. Repositories depend on an ISession. Since a controller may use many repositories, all repositories need the same session."); + + // Per request just for performance, could otherwise be per dependency + PerLifetimeScope("This component takes a list of handlers; to save finding them all each time we raise an event during a request, we build this once"); + PerLifetimeScope("Could be anything, but authentication should be done per request"); + PerLifetimeScope("Could be anything, but authentication should be done per request."); + PerLifetimeScope("Could be anything, but authentication should be done per request."); + PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); + PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); + PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request."); + //PerLifetimeScope("Uses factories to get as session, so could be anything really."); + PerLifetimeScope("Uses factories to get as session, so could be anything really."); + //PerLifetimeScope("Uses factories to get as session, so could be anything really."); + PerLifetimeScope("Uses factories to get as session, so could be anything really."); + PerLifetimeScope("Uses factories to get as session, so could be anything really."); + + // HTTP abstractions, therefore obviously per request + PerLifetimeScope("Comes from HTTP context"); + PerLifetimeScope("Comes from HTTP context"); + PerLifetimeScope("Comes from HTTP context"); + + // Repositories + const string repositoriesReason = "Generally we're unlikely to resolve repositories more than once per request, and they are stateless anyway, but just in case let's re-use the same one"; + PerLifetimeScope(repositoriesReason); + PerLifetimeScope(repositoriesReason); + PerLifetimeScope(repositoriesReason); + PerLifetimeScope(repositoriesReason); + } + + [Test] + public void ComponentsThatShouldBePerDependency() + { + // Could be per lifetime or per dependency (but definitely not singletons) + IsPerDependency("Tasks are created in their own lifetime scope, but only one is ever created, therefore they should all be per dependency."); + IsPerDependency("This component just scans files in a directory, lifetime doesn't matter. It does use HttpContext though, so it shouldn't be singleton"); + IsPerDependency("This component depends on settings (which depends on an ISession), so it definitely shouldn't be singleton"); + } + + [Test] + public void AllComponentLifetimesHaveBeenJustified() + { + ComponentsThatShouldBeSingletons(); + ComponentsThatShouldBePerRequest(); + ComponentsThatShouldBePerDependency(); + + var typesNotTested = container.ComponentRegistry.Registrations + .SelectMany(x => x.Services.OfType().Select(t => new { t.ServiceType, Registration = x })) + .Where(x => seenTypes.All(s => s != x.ServiceType)) + // ReSharper disable once PossibleNullReferenceException + .Where(x => !x.ServiceType.Namespace.StartsWith("Autofac")) // We don't care about internal Autofac types + .Where(x => !x.ServiceType.FullName.Contains("IContainerAwareComponent")) // Some whacky Autofac thing I don't understand + //new version of autofac seems to register a IEnumerable + // ReSharper disable once PossibleNullReferenceException + .Where(x => x.ServiceType.IsGenericType && !x.ServiceType.GetGenericArguments()[0].Namespace.StartsWith("Autofac")) + .ToList(); + + if (typesNotTested.Count == 0) + return; + + Trace.WriteLine("Forgot to test assumptions around the following types:"); + foreach (var item in typesNotTested) + { + Trace.WriteLine(" - " + item.ServiceType.FullName + " (current lifetime: " + GetLifetime(item.Registration) + ")"); + } + + Assert.Fail("One or more types don't have tests regarding their lifetime. Please justify the lifetime of all components. See the trace output of this test to see the types that weren't tested."); + } + + private void IsSingleton(string reason) + { + Is(reason, x => GetLifetime(x) == "Singleton"); + } + + private void PerLifetimeScope(string reason) + { + Is(reason, x => GetLifetime(x) == "PerLifetimeScope"); + } + + private void IsPerDependency(string reason) + { + Is(reason, x => GetLifetime(x) == "PerDependency"); + } + + private string GetLifetime(IComponentRegistration rego) + { + if (rego.Lifetime is RootScopeLifetime) + { + return "Singleton"; + } + if (rego.Lifetime is CurrentScopeLifetime && rego.Sharing == InstanceSharing.Shared) + { + return "PerLifetimeScope"; + } + return "PerDependency"; + } + + //private void Is(string reason, Func predicate) + private void Is(string reason, Func predicate) + { + if (reason == null) throw new ArgumentNullException("reason"); + if (predicate == null) throw new ArgumentNullException("predicate"); + var regos = container.ComponentRegistry.RegistrationsFor(new TypedService(typeof(TService))); + if (!regos.Any(predicate)) + { + Assert.Fail("Component {0} is not registered correctly: {1}", typeof(TService).Name, reason); + } + seenTypes.Add(typeof(TService)); + } + + private readonly HashSet seenTypes = new HashSet(); + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs index 8e6b36c3..fb01e1bc 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs @@ -10,127 +10,130 @@ namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers { - [TestFixture] - public class AdminControllerTests : ControllerTests - { - protected AdminController Controller { get; set; } - protected IAdminRepository AdminRepository { get; set; } - - [SetUp] - public void SetUp() - { - Controller = new AdminController - { - AdminRepository = AdminRepository = Substitute.For(), - ControllerContext = ControllerContext, - Repository = Repository - }; - } - - [Test] - public void Index() - { - var result = (ViewResult)Controller.Index(); - - Assert.That(result.ViewName, Is.EqualTo(string.Empty)); - } - - [Test] - public void DeleteComment() - { - var entry = Substitute.For(); - entry.Id.Returns(3); - Repository.Get(0).Returns(new Comment { Entry = entry }); - var result = (RedirectToRouteResult)Controller.DeleteComment(0); - - Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); - } - - [Test] - public void DeleteAllSpam() - { - var result = (RedirectToRouteResult)Controller.DeleteAllSpam(); - - Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); - } - - [Test] - public void DeletePingback() - { - var result = (RedirectToRouteResult)Controller.DeletePingback(0); - - Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks")); - } - - [Test] - public void ToggleSpam() - { - var result = (RedirectToRouteResult)Controller.ToggleSpam(0); - - Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); - } - - [Test] - public void ToggePingbackSpam() - { - var result = (RedirectToRouteResult)Controller.TogglePingbackSpam(0); - - Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks")); - } - - [Test] - public void DeleteCommentUpdatesCommentCount() - { - // arrange - var entry = Substitute.For(); - entry.Id.Returns(3); - Repository.Get(0).Returns(new Comment{ Entry = entry}); - - // act - Controller.DeleteComment(0); - - // assert - AdminRepository.Received().UpdateCommentCountFor(3); - } - - [Test] - public void ToggleSpamUpdatesCommentCount() - { - // arrange - var entry = Substitute.For(); - entry.Id.Returns(3); - Repository.Get(0).Returns(new Comment { Entry = entry }); - - // act - Controller.ToggleSpam(0); - - // assert - AdminRepository.Received().UpdateCommentCountFor(3); - } - - [Test] - public void DeleteAllSpamUpdatesCommentCount() - { - // arrange - var entry = Substitute.For(); - entry.Id.Returns(3); - var entry2 = Substitute.For(); - entry2.Id.Returns(4); - var comments = new[] - { - new Comment {Entry = entry}, - new Comment {Entry = entry}, - new Comment {Entry = entry2} - }; - Repository.Find(Arg.Any()).Returns(comments); - - // act - Controller.DeleteAllSpam(); - - // assert - Assert.AreEqual(3, Repository.ReceivedCalls().Count(c=>c.GetMethodInfo().Name == "Remove")); - AdminRepository.Received().UpdateCommentCountFor(3); - AdminRepository.Received().UpdateCommentCountFor(4); - } - } -} + [TestFixture] + public class AdminControllerTests : ControllerTests + { + protected AdminController Controller { get; set; } + protected IAdminRepository AdminRepository { get; set; } + + [SetUp] + public void SetUp() + { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + CustomResolver.Initiate(); + + Controller = new AdminController + { + AdminRepository = AdminRepository = Substitute.For(), + ControllerContext = ControllerContext, + Repository = Repository + }; + } + + [Test] + public void Index() + { + var result = (ViewResult)Controller.Index(); + + Assert.That(result.ViewName, Is.EqualTo(string.Empty)); + } + + [Test] + public void DeleteComment() + { + var entry = Substitute.For(); + entry.Id.Returns(3); + Repository.Get(0).Returns(new Comment { Entry = entry }); + var result = (RedirectToRouteResult)Controller.DeleteComment(0); + + Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); + } + + [Test] + public void DeleteAllSpam() + { + var result = (RedirectToRouteResult)Controller.DeleteAllSpam(); + + Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); + } + + [Test] + public void DeletePingback() + { + var result = (RedirectToRouteResult)Controller.DeletePingback(0); + + Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks")); + } + + [Test] + public void ToggleSpam() + { + var result = (RedirectToRouteResult)Controller.ToggleSpam(0); + + Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments")); + } + + [Test] + public void ToggePingbackSpam() + { + var result = (RedirectToRouteResult)Controller.TogglePingbackSpam(0); + + Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks")); + } + + [Test] + public void DeleteCommentUpdatesCommentCount() + { + // arrange + var entry = Substitute.For(); + entry.Id.Returns(3); + Repository.Get(0).Returns(new Comment { Entry = entry }); + + // act + Controller.DeleteComment(0); + + // assert + AdminRepository.Received().UpdateCommentCountFor(3); + } + + [Test] + public void ToggleSpamUpdatesCommentCount() + { + // arrange + var entry = Substitute.For(); + entry.Id.Returns(3); + Repository.Get(0).Returns(new Comment { Entry = entry }); + + // act + Controller.ToggleSpam(0); + + // assert + AdminRepository.Received().UpdateCommentCountFor(3); + } + + [Test] + public void DeleteAllSpamUpdatesCommentCount() + { + // arrange + var entry = Substitute.For(); + entry.Id.Returns(3); + var entry2 = Substitute.For(); + entry2.Id.Returns(4); + var comments = new[] + { + new Comment {Entry = entry}, + new Comment {Entry = entry}, + new Comment {Entry = entry2} + }; + Repository.Find(Arg.Any()).Returns(comments); + + // act + Controller.DeleteAllSpam(); + + // assert + Assert.AreEqual(3, Repository.ReceivedCalls().Count(c => c.GetMethodInfo().Name == "Remove")); + AdminRepository.Received().UpdateCommentCountFor(3); + AdminRepository.Received().UpdateCommentCountFor(4); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs new file mode 100644 index 00000000..2fd328ac --- /dev/null +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using Autofac; +using FunnelWeb.Web; + +namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers +{ + public class CustomResolver : IDependencyResolver + { + private readonly IContainer container; + + public CustomResolver(IContainer container) + { + this.container = container; + } + + public object GetService(Type serviceType) + { + return container.Resolve(serviceType); + } + + public IEnumerable GetServices(Type serviceType) + { + throw new NotSupportedException(); + } + + static bool initiated = false; + public static void Initiate() + { + //if (initiated) return; + //initiated = true; + var dependencyResolver = DependencyResolver.Current as IDisposable; + if (dependencyResolver != null) + { + dependencyResolver.Dispose(); + } + var container = MvcApplication.BuildContainer(); + DependencyResolver.SetResolver(new CustomResolver(container)); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs new file mode 100644 index 00000000..4f1d9975 --- /dev/null +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading; +using FunnelWeb.Authentication.Internal; + +namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers +{ + public static class TestAuthenticationAndAuthorization + { + public static void SetTestUserToCurrentPrincipal(bool isAuthenticated = true) + { + IEnumerable claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "tester"), + new Claim(ClaimTypes.Name, "Test User"), + Authorization.Roles.Admin, + Authorization.Roles.Moderator + }; + + var claimsIdentity = isAuthenticated ? new ClaimsIdentity(claims, "Test") : new NotAuthenticatedClaimsIdentity(); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + public class NotAuthenticatedClaimsIdentity : ClaimsIdentity + { + public override bool IsAuthenticated + { + get { return false; } + } + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs index 44ac09d0..7e4d1554 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs @@ -4,7 +4,6 @@ using FunnelWeb.Providers.File; using FunnelWeb.Settings; using FunnelWeb.Tests.Web.Controllers; -using FunnelWeb.Utilities; using FunnelWeb.Web.Application.Mvc; using FunnelWeb.Web.Areas.Admin.Controllers; using NSubstitute; @@ -12,111 +11,112 @@ namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers { - [TestFixture] - public class UploadControllerTests : ControllerTests - { - protected UploadController Controller { get; set; } - protected IFileRepository FileRepository { get; set; } - protected IMimeTypeLookup MimeTypeLookup { get; set; } + [TestFixture] + public class UploadControllerTests : ControllerTests + { + protected UploadController Controller { get; set; } + protected IFileRepository FileRepository { get; set; } - [SetUp] - public void SetUp() - { - var settingsProvider = Substitute.For(); - settingsProvider.GetSettings().Returns(new FunnelWebSettings()); - Controller = new UploadController - { - FileRepository = FileRepository = Substitute.For(), - MimeHelper = MimeTypeLookup = Substitute.For(), - ControllerContext = ControllerContext, - SettingsProvider = settingsProvider - }; - } + [SetUp] + public void SetUp() + { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + CustomResolver.Initiate(); - [Test] - public void IndexForExistingFile() - { - FileRepository.IsFile(Arg.Any()).Returns(true); + var settingsProvider = Substitute.For(); + settingsProvider.GetSettings().Returns(new FunnelWebSettings()); + Controller = new UploadController + { + FileRepository = FileRepository = Substitute.For(), + ControllerContext = ControllerContext, + SettingsProvider = settingsProvider + }; + } - var result = (RedirectToRouteResult)Controller.Index("test"); + [Test] + public void IndexForExistingFile() + { + FileRepository.IsFile(Arg.Any()).Returns(true); - FileRepository.Received().IsFile(Arg.Any()); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + var result = (RedirectToRouteResult)Controller.Index("test"); - [Test] - public void IndexForMissingFile() - { - FileRepository.IsFile(Arg.Any()).Returns(false); + FileRepository.Received().IsFile(Arg.Any()); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - var result = (ViewResult)Controller.Index("test"); + [Test] + public void IndexForMissingFile() + { + FileRepository.IsFile(Arg.Any()).Returns(false); - FileRepository.Received().IsFile(Arg.Any()); - Assert.That(result.View, Is.EqualTo(null)); - } + var result = (ViewResult)Controller.Index("test"); - [Test] - public void Upload() - { - var stream = new MemoryStream(); + FileRepository.Received().IsFile(Arg.Any()); + Assert.That(result.View, Is.EqualTo(null)); + } - var file = Substitute.For(); - file.InputStream.Returns(stream); + [Test] + public void Upload() + { + var stream = new MemoryStream(); - var upload = new FileUpload(file); + var file = Substitute.For(); + file.InputStream.Returns(stream); - var result = (RedirectToRouteResult)Controller.Upload("path", false, upload); + var upload = new FileUpload(file); - FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(false)); + var result = (RedirectToRouteResult)Controller.Upload("path", false, upload); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(false)); - [Test] - public void UploadAndUnzip() - { - var stream = new MemoryStream(); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - var file = Substitute.For(); - file.InputStream.Returns(stream); + [Test] + public void UploadAndUnzip() + { + var stream = new MemoryStream(); - var upload = new FileUpload(file); + var file = Substitute.For(); + file.InputStream.Returns(stream); - var result = (RedirectToRouteResult)Controller.Upload("path", true, upload); + var upload = new FileUpload(file); - FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(true)); + var result = (RedirectToRouteResult)Controller.Upload("path", true, upload); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(true)); - [Test] - public void CreateDirectory() - { - var result = (RedirectToRouteResult)Controller.CreateDirectory("path", string.Empty); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - FileRepository.Received().CreateDirectory(Arg.Is("path"), Arg.Any()); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - Assert.That(result.RouteValues["path"], Is.EqualTo("path")); - } + [Test] + public void CreateDirectory() + { + var result = (RedirectToRouteResult)Controller.CreateDirectory("path", string.Empty); - [Test] - public void Delete() - { - var result = (RedirectToRouteResult)Controller.Delete("path", "file"); + FileRepository.Received().CreateDirectory(Arg.Is("path"), Arg.Any()); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + Assert.That(result.RouteValues["path"], Is.EqualTo("path")); + } - FileRepository.Received().Delete(Arg.Is("file")); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - Assert.That(result.RouteValues["path"], Is.EqualTo("path")); - } + [Test] + public void Delete() + { + var result = (RedirectToRouteResult)Controller.Delete("path", "file"); - [Test] - public void RenderExistingFile() - { - FileRepository.IsFile(Arg.Is("file")).Returns(true); + FileRepository.Received().Delete(Arg.Is("file")); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + Assert.That(result.RouteValues["path"], Is.EqualTo("path")); + } - Controller.Render("file"); + [Test] + public void RenderExistingFile() + { + FileRepository.IsFile(Arg.Is("file")).Returns(true); - FileRepository.Received().Render("file"); - } - } + Controller.Render("file"); + + FileRepository.Received().Render("file"); + } + } } diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs index b9acdb3d..b1f99cc9 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs @@ -1,75 +1,67 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Security.Principal; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Repositories; using FunnelWeb.Repositories.Queries; using FunnelWeb.Tests.Web.Controllers; -using FunnelWeb.Web.Application.Spam; using FunnelWeb.Web.Areas.Admin.Controllers; using NSubstitute; using NUnit.Framework; namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers { - [TestFixture] - public class WikiAdminControllerTests : ControllerTests - { - protected WikiAdminController AdminController { get; set; } - protected ISpamChecker SpamChecker { get; set; } - - [SetUp] - public void Setup() - { - AdminController = new WikiAdminController - { - Repository = Repository = Substitute.For(), - SpamChecker = SpamChecker = Substitute.For(), - ControllerContext = ControllerContext = ControllerContext - }; + [TestFixture] + public class WikiAdminControllerTests : ControllerTests + { + protected WikiAdminController AdminController { get; set; } - Identity = Substitute.For(); - User = Substitute.For(); - User.Identity.Returns(Identity); - ControllerContext.HttpContext.User.Returns(User); - } + [SetUp] + public void Setup() + { + CustomResolver.Initiate(); - [Test] - public void EditReturnsExistingPageWhenFound() - { - var entry = new EntryRevision { Name = "awesome-post" }; - Repository.FindFirstOrDefault(Arg.Any()).Returns(entry); + AdminController = new WikiAdminController + { + Repository = Repository = Substitute.For(), + ControllerContext = ControllerContext = ControllerContext + }; + } - var feeds = new List().AsQueryable(); - Repository.FindAll().Returns(feeds); + [Test] + public void EditReturnsExistingPageWhenFound() + { + var entry = new EntryRevision { Name = "awesome-post" }; + Repository.FindFirstOrDefault(Arg.Any()).Returns(entry); - var result = (ViewResult)AdminController.Edit(entry.Name, null); + var feeds = new List().AsQueryable(); + Repository.FindAll().Returns(feeds); - Assert.AreEqual("Edit", result.ViewName); - Assert.AreEqual(feeds, ((EntryRevision)result.ViewData.Model).AllTags); - Assert.AreEqual(entry.Name.ToString(), ((EntryRevision)result.ViewData.Model).Name.ToString()); + var result = (ViewResult)AdminController.Edit(entry.Name, null); - Repository.Received().FindFirstOrDefault(Arg.Any()); - Repository.Received().FindAll(); - } + Assert.AreEqual("Edit", result.ViewName); + Assert.AreEqual(feeds, ((EntryRevision)result.ViewData.Model).AllTags); + Assert.AreEqual(entry.Name.ToString(), ((EntryRevision)result.ViewData.Model).Name.ToString()); - [Test] - public void EditReturnsNewModelWhenEntryNotFound() - { - // arrange - const string newPostName = "new-awesome-post"; + Repository.Received().FindFirstOrDefault(Arg.Any()); + Repository.Received().FindAll(); + } - // act - var result = (ViewResult)AdminController.Edit(newPostName, null); + [Test] + public void EditReturnsNewModelWhenEntryNotFound() + { + // arrange + const string newPostName = "new-awesome-post"; - // assert - Assert.IsNotNull(result.ViewData.Model); - Assert.IsInstanceOf(typeof(EntryRevision), result.ViewData.Model); - Assert.NotNull(((EntryRevision)result.ViewData.Model).Tags); - Assert.AreEqual("new-awesome-post", ((EntryRevision)result.ViewData.Model).Name.ToString()); - } + // act + var result = (ViewResult)AdminController.Edit(newPostName, null); - } + // assert + Assert.IsNotNull(result.ViewData.Model); + Assert.IsInstanceOf(typeof(EntryRevision), result.ViewData.Model); + Assert.NotNull(((EntryRevision)result.ViewData.Model).Tags); + Assert.AreEqual("new-awesome-post", ((EntryRevision)result.ViewData.Model).Name.ToString()); + } + + } } diff --git a/src/FunnelWeb.Tests/Web/Controllers/ControllerTests.cs b/src/FunnelWeb.Tests/Web/Controllers/ControllerTests.cs index cb06ea21..62618240 100644 --- a/src/FunnelWeb.Tests/Web/Controllers/ControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Controllers/ControllerTests.cs @@ -1,85 +1,81 @@ using System; using System.IO; -using System.Security.Principal; using System.Web; using System.Web.Mvc; using FunnelWeb.Repositories; using FunnelWeb.Settings; +using FunnelWeb.Tests.Web.Areas.Admin.Controllers; using FunnelWeb.Web.Application.Markup; -using FunnelWeb.Web.Application.Markup.Macros; using NSubstitute; namespace FunnelWeb.Tests.Web.Controllers { - public class ControllerTests - { - protected IIdentity Identity { get; set; } - protected IPrincipal User { get; set; } - protected ISettingsProvider SettingsProvider { get; set; } - protected ControllerContext ControllerContext { get; set; } - protected UrlHelper UrlHelper { get; set; } - protected IRepository Repository { get; set; } - protected IContentRenderer ContentRenderer { get; set; } - protected HtmlHelper Html { get; set; } + public class ControllerTests + { + protected ISettingsProvider SettingsProvider { get; set; } + protected ControllerContext ControllerContext { get; set; } + protected UrlHelper UrlHelper { get; set; } + protected IRepository Repository { get; set; } + protected IContentRenderer ContentRenderer { get; set; } + //protected HtmlHelper Html { get; set; } - public ControllerTests() - { - Identity = Substitute.For(); - User = Substitute.For(); - Repository = Substitute.For(); - SettingsProvider = Substitute.For(); - ContentRenderer = Substitute.For(); - ControllerContext = CreateControllerContext(); - UrlHelper = CreateUrlHelper(); - User.Identity.Returns(Identity); - ControllerContext.HttpContext.User.Returns(User); - SettingsProvider.GetSettings().Returns(new FunnelWebSettings { EnablePublicHistory = true }); - Html = CreateHelper(); - ContentRenderer.RenderTrusted(Arg.Any(), Arg.Any(), Arg.Any()).Returns(c => c.Args()[0]); - ContentRenderer.RenderUntrusted(Arg.Any(), Arg.Any(), Arg.Any()).Returns(c => c.Args()[0]); - } + protected ControllerTests() + { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + Repository = Substitute.For(); + SettingsProvider = Substitute.For(); + ContentRenderer = Substitute.For(); + ControllerContext = CreateControllerContext(); + UrlHelper = CreateUrlHelper(); + SettingsProvider.GetSettings().Returns(new FunnelWebSettings { EnablePublicHistory = true }); + //Html = CreateHelper(); + ContentRenderer.RenderTrusted(Arg.Any(), Arg.Any(), Arg.Any()).Returns(c => c.Args()[0]); + ContentRenderer.RenderUntrusted(Arg.Any(), Arg.Any(), Arg.Any()).Returns(c => c.Args()[0]); + } - private UrlHelper CreateUrlHelper() - { - return new UrlHelper(ControllerContext.RequestContext); - } + private UrlHelper CreateUrlHelper() + { + return new UrlHelper(ControllerContext.RequestContext); + } - private HtmlHelper CreateHelper() - { - return new HtmlHelper(new ViewContext(ControllerContext, new DummyView(), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()), new CustomViewDataContainer()); - } + //private HtmlHelper CreateHelper() + //{ + // return new HtmlHelper(new ViewContext(ControllerContext, new DummyView(), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()), new CustomViewDataContainer()); + //} - private static ControllerContext CreateControllerContext() - { - string host = "www.google.com"; - string proto = "http"; - string userIP = "127.0.0.1"; + private static ControllerContext CreateControllerContext() + { + string host = "www.google.com"; + string proto = "http"; + string userIP = "127.0.0.1"; - var headers = new System.Collections.Specialized.NameValueCollection { - {"Host", host}, - {"X-Forwarded-Proto", proto}, - {"X-Forwarded-For", userIP} - }; + var headers = new System.Collections.Specialized.NameValueCollection { + {"Host", host}, + {"X-Forwarded-Proto", proto}, + {"X-Forwarded-For", userIP} + }; - var httpRequest = Substitute.For(); - httpRequest.Url.Returns(new Uri(proto + "://" + host)); - httpRequest.Headers.Returns(headers); + var httpRequest = Substitute.For(); + httpRequest.Url.Returns(new Uri(proto + "://" + host)); + httpRequest.Headers.Returns(headers); - var httpContext = Substitute.For(); - httpContext.Request.Returns(httpRequest); + var httpContext = Substitute.For(); + httpContext.Request.Returns(httpRequest); - var controllerContext = new ControllerContext(); - controllerContext.HttpContext = httpContext; + var controllerContext = new ControllerContext + { + HttpContext = httpContext + }; - return controllerContext; - } + return controllerContext; + } - internal class DummyView : IView - { - public void Render(ViewContext viewContext, TextWriter writer) - { + internal class DummyView : IView + { + public void Render(ViewContext viewContext, TextWriter writer) + { - } - } - } + } + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Controllers/LoginControllerTests.cs b/src/FunnelWeb.Tests/Web/Controllers/LoginControllerTests.cs new file mode 100644 index 00000000..8e82fcd7 --- /dev/null +++ b/src/FunnelWeb.Tests/Web/Controllers/LoginControllerTests.cs @@ -0,0 +1,22 @@ +using System.Web; +using FunnelWeb.Web.Controllers; +using NUnit.Framework; + +namespace FunnelWeb.Tests.Web.Controllers +{ + [TestFixture] + public class LoginControllerTests + { + [Test] + public void Parse_ReturnUrl() + { + var expectedReturnUrl = "http://foo.com"; + + string contextQuery = HttpUtility.UrlEncode(string.Format("foo=bar&ReturnUrl={0}&baz=cux", expectedReturnUrl)); + + var returnUrl = LoginController.GetReturnUrl(contextQuery); + + Assert.AreEqual(expectedReturnUrl, returnUrl); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/Controllers/WikiControllerTests.cs b/src/FunnelWeb.Tests/Web/Controllers/WikiControllerTests.cs index ce27954c..a9877904 100644 --- a/src/FunnelWeb.Tests/Web/Controllers/WikiControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Controllers/WikiControllerTests.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Repositories; using FunnelWeb.Repositories.Queries; +using FunnelWeb.Tests.Web.Areas.Admin.Controllers; using FunnelWeb.Web.Application.Spam; using FunnelWeb.Web.Controllers; using FunnelWeb.Web.Views.Wiki; @@ -13,86 +13,87 @@ namespace FunnelWeb.Tests.Web.Controllers { - [TestFixture] - public class WikiControllerTests: ControllerTests - { - protected WikiController Controller { get; set; } - protected ISpamChecker SpamChecker { get; set; } - - [SetUp] - public void SetUp() - { - Controller = new WikiController - { - Repository = Repository, - SpamChecker = SpamChecker = Substitute.For(), - ControllerContext = ControllerContext, - SettingsProvider = SettingsProvider - }; - } - - [Test] - public void Search() - { - var entries = new PagedResult(new List(), 0, 0, 50); - Repository.Find(Arg.Is(q=>q.SearchText == "search"), Arg.Any(), Arg.Any()).Returns(entries); - - var result = (ViewResult)Controller.Search("search", false); - - Repository.Received().Find(Arg.Is(q => q.SearchText == "search"), Arg.Any(), Arg.Any()); - Assert.That(result.ViewName, Is.EqualTo("Search")); - Assert.IsInstanceOf(result.ViewData.Model); - Assert.AreEqual(entries, ((SearchModel)result.ViewData.Model).Results); - } - - [Test] - public void PageLoggedInAndRedirectsToNewEntry() - { - Repository.FindFirstOrDefault(Arg.Any()).Returns(x => null); - Identity.IsAuthenticated.Returns(true); - - var result = (RedirectToRouteResult)Controller.Page("page", 0); - - Assert.AreEqual("Edit", result.RouteValues["Action"]); - Repository.Received().FindFirstOrDefault(Arg.Is(q=>q.PageName =="page" && q.Revision == 0)); - } - - [Test] - public void WikiControllerTestsPageReturnsNotFoundForNewPageAndNotLoggedIn() - { - var entries = new List(); - var searchResults = new PagedResult(entries, 0, 0, 50); - Repository.FindFirstOrDefault(Arg.Any()).Returns(x => null); - Repository.Find(Arg.Any(), Arg.Any(), Arg.Any()).Returns(searchResults); - Identity.IsAuthenticated.Returns(false); - - var result = (ViewResult)Controller.Page("page", 0); - - Assert.AreEqual("Search", result.ViewName); - Assert.IsNotNull(result.ViewData.Model); - Assert.IsInstanceOf(typeof(SearchModel), result.ViewData.Model); - - var model = (SearchModel)result.ViewData.Model; - Assert.AreSame(searchResults, model.Results); - - Repository.Received().Find(Arg.Is(q => q.SearchText == "page"), Arg.Any(), Arg.Any()); - Repository.Received().FindFirstOrDefault(Arg.Is(q => q.PageName == "page" && q.Revision == 0)); - } - - [Test] - public void WikiControllerTestsPageReturnsFoundPage() - { - var entry = new EntryRevision { Name = "page" }; - Repository.FindFirstOrDefault(Arg.Any()).Returns(entry); - - Controller.Page(entry.Name, (int?)null); - - Assert.IsNotNull(Controller.ViewData.Model); - Assert.IsInstanceOf(Controller.ViewData.Model); - - var model = (PageModel)Controller.ViewData.Model; - Assert.AreEqual(entry, model.Entry); - Repository.Received().FindFirstOrDefault(Arg.Is(q=>q.PageName == entry.Name)); - } - } -} + [TestFixture] + public class WikiControllerTests : ControllerTests + { + protected WikiController Controller { get; set; } + + [SetUp] + public void SetUp() + { + Controller = new WikiController + { + Repository = Repository, + SpamChecker = Substitute.For(), + ControllerContext = ControllerContext, + SettingsProvider = SettingsProvider + }; + } + + [Test] + public void Search() + { + var entries = new PagedResult(new List(), 0, 0, 50); + Repository.Find(Arg.Is(q => q.SearchText == "search"), Arg.Any(), Arg.Any()).Returns(entries); + + var result = (ViewResult)Controller.Search("search", false); + + Repository.Received().Find(Arg.Is(q => q.SearchText == "search"), Arg.Any(), Arg.Any()); + Assert.That(result.ViewName, Is.EqualTo("Search")); + Assert.IsInstanceOf(result.ViewData.Model); + Assert.AreEqual(entries, ((SearchModel)result.ViewData.Model).Results); + } + + [Test] + public void PageLoggedInAndRedirectsToNewEntry() + { + Repository.FindFirstOrDefault(Arg.Any()).Returns(x => null); + + var result = (RedirectToRouteResult)Controller.Page("page", 0); + + Assert.AreEqual("Edit", result.RouteValues["Action"]); + Repository.Received().FindFirstOrDefault(Arg.Is(q => q.PageName == "page" && q.Revision == 0)); + } + + [Test] + public void WikiControllerTestsPageReturnsNotFoundForNewPageAndNotLoggedIn() + { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(false); + + var entries = new List(); + var searchResults = new PagedResult(entries, 0, 0, 50); + Repository.FindFirstOrDefault(Arg.Any()).Returns(x => null); + Repository.Find(Arg.Any(), Arg.Any(), Arg.Any()).Returns(searchResults); + + var result = (ViewResult)Controller.Page("page", 0); + + Assert.AreEqual("Search", result.ViewName); + Assert.IsNotNull(result.ViewData.Model); + Assert.IsInstanceOf(typeof(SearchModel), result.ViewData.Model); + + var model = (SearchModel)result.ViewData.Model; + Assert.AreSame(searchResults, model.Results); + + Repository.Received().Find(Arg.Is(q => q.SearchText == "page"), Arg.Any(), Arg.Any()); + Repository.Received().FindFirstOrDefault(Arg.Is(q => q.PageName == "page" && q.Revision == 0)); + } + + [Test] + public void WikiControllerTestsPageReturnsFoundPage() + { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + + var entry = new EntryRevision { Name = "page" }; + Repository.FindFirstOrDefault(Arg.Any()).Returns(entry); + + Controller.Page(entry.Name, (int?)null); + + Assert.IsNotNull(Controller.ViewData.Model); + Assert.IsInstanceOf(Controller.ViewData.Model); + + var model = (PageModel)Controller.ViewData.Model; + Assert.AreEqual(entry, model.Entry); + Repository.Received().FindFirstOrDefault(Arg.Is(q => q.PageName == entry.Name)); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Tests/Web/RoutingTests.cs b/src/FunnelWeb.Tests/Web/RoutingTests.cs index 9030f85f..2a708e31 100644 --- a/src/FunnelWeb.Tests/Web/RoutingTests.cs +++ b/src/FunnelWeb.Tests/Web/RoutingTests.cs @@ -3,10 +3,10 @@ using System.Web.Mvc; using System.Web.Routing; using FunnelWeb.Tests.Helpers; -using FunnelWeb.Web.App_Start; using FunnelWeb.Web.Areas.Admin; using NSubstitute; using NUnit.Framework; +using RouteConfig = FunnelWeb.Web.App_Start.RouteConfig; namespace FunnelWeb.Tests.Web { diff --git a/src/FunnelWeb.Tests/packages.config b/src/FunnelWeb.Tests/packages.config index 2ee91525..219bb229 100644 --- a/src/FunnelWeb.Tests/packages.config +++ b/src/FunnelWeb.Tests/packages.config @@ -1,7 +1,7 @@  - - + + @@ -14,4 +14,5 @@ + \ No newline at end of file diff --git a/src/FunnelWeb.Web/App_Start/RouteConfig.cs b/src/FunnelWeb.Web/App_Start/RouteConfig.cs index 4a761cbc..3f37760e 100644 --- a/src/FunnelWeb.Web/App_Start/RouteConfig.cs +++ b/src/FunnelWeb.Web/App_Start/RouteConfig.cs @@ -2,72 +2,87 @@ using System.Web.Routing; using FunnelWeb.Web.Application.MetaWeblog; using FunnelWeb.Web.Application.Mvc; +using FunnelWeb.Web.Controllers; namespace FunnelWeb.Web.App_Start { - public class RouteConfig - { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - routes.IgnoreRoute("{*allaxd}", new { allaxd = @".*\.axd(/.*)?" }); - routes.IgnoreRoute("pingback"); + public class RouteConfig + { + public static void RegisterRoutes(RouteCollection routes) + { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + routes.IgnoreRoute("{*allaxd}", new { allaxd = @".*\.axd(/.*)?" }); + routes.IgnoreRoute("pingback"); - // Feeds - routes.MapLowerCaseRoute("feed", new { controller = "Feed", action = "Feed", feedName = (string)null }); - routes.MapLowerCaseRoute("feeds/{*feedName}", new { controller = "Feed", action = "Feed" }); - routes.MapLowerCaseRoute("commentfeed", new { controller = "Feed", action = "CommentFeed" }); + // Login + routes.MapRoute( + name: "Login", + url: "login", + defaults: new { controller = "Login", action = "Index" }, + namespaces: new[] { typeof(LoginController).Namespace } + ); + routes.MapRoute( + name: "Login.Return", + url: "login/return", + defaults: new { controller = "Login", action = "Return" }, + namespaces: new[] { typeof(LoginController).Namespace } + ); - // Upload - routes.MapLowerCaseRoute("get/{*path}", new { controller = "Upload", action = "Render", area = "Admin" }); + // Feeds + routes.MapLowerCaseRoute("feed", new { controller = "Feed", action = "Feed", feedName = (string)null }); + routes.MapLowerCaseRoute("feeds/{*feedName}", new { controller = "Feed", action = "Feed" }); + routes.MapLowerCaseRoute("commentfeed", new { controller = "Feed", action = "CommentFeed" }); - // Resources - routes.MapLowerCaseRoute("content/theme.css", new { controller = "Resource", action = "RenderThemedFileIfExists", fileToRender = "{Theme}/Content/Styles/Theme.css", contentType = "text/css" }); - routes.MapLowerCaseRoute("robots", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Robots.txt", contentType = "text/plain" }); - routes.MapLowerCaseRoute("robots.txt", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Robots.txt", contentType = "text/plain" }); - routes.MapLowerCaseRoute("humans.txt", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Humans.txt", contentType = "text/plain" }); - routes.MapLowerCaseRoute("humans", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Humans.txt", contentType = "text/plain" }); - routes.MapLowerCaseRoute("favicon", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.ico", fileName2 = "Content/Resources/favicon.ico", contentType = "image/vnd.microsoft.icon" }); - routes.MapLowerCaseRoute("favicon.ico", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.ico", fileName2 = "Content/Resources/favicon.ico", contentType = "image/vnd.microsoft.icon" }); - routes.MapLowerCaseRoute("favicon.png", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.png", fileName2 = "Content/Resources/favicon.png", contentType = "image/png" }); - routes.MapLowerCaseRoute("status", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Status.html", contentType = "text/html" }); + // Upload + routes.MapLowerCaseRoute("get/{*path}", new { controller = "Upload", action = "Render", area = "Admin" }); - // Site Map - routes.MapLowerCaseRoute("sitemap", new { controller = "Wiki", action = "SiteMap" }); - routes.MapLowerCaseRoute("sitemap.xml", new { controller = "Wiki", action = "SiteMap" }); + // Resources + routes.MapLowerCaseRoute("content/theme.css", new { controller = "Resource", action = "RenderThemedFileIfExists", fileToRender = "{Theme}/Content/Styles/Theme.css", contentType = "text/css" }); + routes.MapLowerCaseRoute("robots", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Robots.txt", contentType = "text/plain" }); + routes.MapLowerCaseRoute("robots.txt", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Robots.txt", contentType = "text/plain" }); + routes.MapLowerCaseRoute("humans.txt", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Humans.txt", contentType = "text/plain" }); + routes.MapLowerCaseRoute("humans", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Humans.txt", contentType = "text/plain" }); + routes.MapLowerCaseRoute("favicon", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.ico", fileName2 = "Content/Resources/favicon.ico", contentType = "image/vnd.microsoft.icon" }); + routes.MapLowerCaseRoute("favicon.ico", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.ico", fileName2 = "Content/Resources/favicon.ico", contentType = "image/vnd.microsoft.icon" }); + routes.MapLowerCaseRoute("favicon.png", new { controller = "Resource", action = "Render", fileName = "{Theme}/Content/Images/favicon.png", fileName2 = "Content/Resources/favicon.png", contentType = "image/png" }); + routes.MapLowerCaseRoute("status", new { controller = "Resource", action = "Render", fileName = "Content/Resources/Status.html", contentType = "text/html" }); - // Tags - routes.MapLowerCaseRoute("tag/{*tagName}", new { controller = "Tag", action = "Index" }); + // Site Map + routes.MapLowerCaseRoute("sitemap", new { controller = "Wiki", action = "SiteMap" }); + routes.MapLowerCaseRoute("sitemap.xml", new { controller = "Wiki", action = "SiteMap" }); - // Tagged Pages - routes.MapLowerCaseRoute("tagged/{*tag}", new { controller = "Tagged", action = "Index" }); + // Tags + routes.MapLowerCaseRoute("tag/{*tagName}", new { controller = "Tag", action = "Index" }); - // Wiki - routes.MapLowerCaseRoute("blog", new { controller = "Wiki", action = "Recent", pageNumber = "0" }); - routes.MapLowerCaseRoute("blog/{pageNumber}", new { controller = "Wiki", action = "Recent" }, new { pageNumber = "\\d+" }); - routes.MapLowerCaseRoute("", new { controller = "Wiki", action = "Home", pageNumber = "0" }); - routes.MapLowerCaseRoute("{pageNumber}", new { controller = "Wiki", action = "Home" }, new { pageNumber = "\\d+" }); - routes.MapLowerCaseRoute("search", new { controller = "Wiki", action = "Search" }); + // Tagged Pages + routes.MapLowerCaseRoute("tagged/{*tag}", new { controller = "Tagged", action = "Index" }); - routes.MapLowerCaseRoute("unpublished", new { controller = "WikiAdmin", Area = "Admin", action = "Unpublished" }); - routes.MapLowerCaseRoute("admin/new", new { controller = "WikiAdmin", Area = "Admin", action = "Edit", page = "" }); - routes.MapLowerCaseRoute("edit/{*page}", new { controller = "WikiAdmin", Area = "Admin", action = "Edit", page = UrlParameter.Optional }); - routes.MapLowerCaseRoute("revert/{*page}", new { controller = "WikiAdmin", Area = "Admin", action = "Revert" }); + // Wiki + routes.MapLowerCaseRoute("blog", new { controller = "Wiki", action = "Recent", pageNumber = "0" }); + routes.MapLowerCaseRoute("blog/{pageNumber}", new { controller = "Wiki", action = "Recent" }, new { pageNumber = "\\d+" }); + routes.MapLowerCaseRoute("", new { controller = "Wiki", action = "Home", pageNumber = "0" }); + routes.MapLowerCaseRoute("{pageNumber}", new { controller = "Wiki", action = "Home" }, new { pageNumber = "\\d+" }); + routes.MapLowerCaseRoute("search", new { controller = "Wiki", action = "Search" }); - routes.MapLowerCaseRoute("via-feed/{*page}", new { controller = "Resource", action = "Render", fileName = "Content/Images/transparent.gif", contentType = "image/gif" }); - routes.MapLowerCaseRoute("history-of/{*page}", new { controller = "Wiki", action = "Revisions" }); - routes.MapLowerCaseRoute("trackbacks-for/{*page}", new { controller = "Wiki", action = "Pingbacks" }); + routes.MapLowerCaseRoute("unpublished", new { controller = "WikiAdmin", Area = "Admin", action = "Unpublished" }); + routes.MapLowerCaseRoute("admin/new", new { controller = "WikiAdmin", Area = "Admin", action = "Edit", page = "" }); + routes.MapLowerCaseRoute("edit/{*page}", new { controller = "WikiAdmin", Area = "Admin", action = "Edit", page = UrlParameter.Optional }); + routes.MapLowerCaseRoute("revert/{*page}", new { controller = "WikiAdmin", Area = "Admin", action = "Revert" }); - // Remove .aspx - routes.Add(new RedirectRoute("(?[a-zA-Z0-9/\\-\\._\\+ ]+)\\.aspx", new MvcRouteHandler()) { ReplacePattern = "/$1" }); - routes.Add(new RedirectRoute("(?rss)$", new MvcRouteHandler()) { ReplacePattern = "feed", ResponseCode = 302 }); + routes.MapLowerCaseRoute("via-feed/{*page}", new { controller = "Resource", action = "Render", fileName = "Content/Images/transparent.gif", contentType = "image/gif" }); + routes.MapLowerCaseRoute("history-of/{*page}", new { controller = "Wiki", action = "Revisions" }); + routes.MapLowerCaseRoute("trackbacks-for/{*page}", new { controller = "Wiki", action = "Pingbacks" }); - // http://www.cookcomputing.com/blog/archives/xml-rpc-and-asp-net-mvc - routes.MapLowerCaseRoute("wlwmanifest.xml", new { controller = "MetaWeblog", action = "WlwManifest" }); - routes.MapLowerCaseRoute("rsd.xml", new { controller = "MetaWeblog", action = "Rsd" }); - routes.Add(new Route("{weblog}", null, new RouteValueDictionary(new { weblog = "blogapi" }), new MetaWeblogRouteHandler())); + // Remove .aspx + routes.Add(new RedirectRoute("(?[a-zA-Z0-9/\\-\\._\\+ ]+)\\.aspx", new MvcRouteHandler()) { ReplacePattern = "/$1" }); + routes.Add(new RedirectRoute("(?rss)$", new MvcRouteHandler()) { ReplacePattern = "feed", ResponseCode = 302 }); - routes.MapLowerCaseRoute("{*page}", new { controller = "Wiki", action = "Page" }); - } - } + // http://www.cookcomputing.com/blog/archives/xml-rpc-and-asp-net-mvc + routes.MapLowerCaseRoute("wlwmanifest.xml", new { controller = "MetaWeblog", action = "WlwManifest" }); + routes.MapLowerCaseRoute("rsd.xml", new { controller = "MetaWeblog", action = "Rsd" }); + routes.Add(new Route("{weblog}", null, new RouteValueDictionary(new { weblog = "blogapi" }), new MetaWeblogRouteHandler())); + + routes.MapLowerCaseRoute("{*page}", new { controller = "Wiki", action = "Page" }); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs index ba03b306..6c263c08 100644 --- a/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs +++ b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs @@ -4,52 +4,58 @@ namespace FunnelWeb.Web.Application.Authentication { - public class AuthenticationModule : Module - { - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - - //Sql Authentication should be registered last as it will fall back to Forms if it is not enabled - // or if there is a database issue - - //Authenticators - builder - .RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - - builder - .RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - - //Role Providers - builder.RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - - builder - .RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - - //Membership - builder - .RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - - builder - .RegisterType() - .As() - .AsSelf() - .InstancePerLifetimeScope(); - } - } + public class AuthenticationModule : Module + { + protected override void Load(ContainerBuilder builder) + { + base.Load(builder); + + //Sql Authentication should be registered last as it will fall back to Forms if it is not enabled + // or if there is a database issue + + //Authenticators + builder + .RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + builder + //.RegisterType() + .RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + //Role Providers + builder.RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + builder + //.RegisterType() + .RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + //Membership + builder + .RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + builder + .RegisterType() + .As() + .AsSelf() + .InstancePerLifetimeScope(); + + builder.RegisterType().As(); + + builder.RegisterType().As(); + } + } } diff --git a/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs b/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs deleted file mode 100644 index 30082bb2..00000000 --- a/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Web.Mvc; -using System.Web.Security; -using FunnelWeb.Authentication; - -namespace FunnelWeb.Web.Application.Authentication -{ - public class FunnelWebRoleProvider : RoleProvider - { - public FunnelWebRoleProvider() - { - ApplicationName = "FunnelWeb"; - } - - public override bool IsUserInRole(string username, string roleName) - { - return DependencyResolver.Current.GetService().IsUserInRole(username, roleName); - } - - public override string[] GetRolesForUser(string username) - { - return DependencyResolver.Current.GetService().GetRolesForUser(username); - } - - public override void CreateRole(string roleName) - { - throw new NotSupportedException(); - } - - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) - { - throw new NotSupportedException(); - } - - public override bool RoleExists(string roleName) - { - throw new NotSupportedException(); - } - - public override void AddUsersToRoles(string[] usernames, string[] roleNames) - { - throw new NotSupportedException(); - } - - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) - { - throw new NotSupportedException(); - } - - public override string[] GetUsersInRole(string roleName) - { - throw new NotSupportedException(); - } - - public override string[] GetAllRoles() - { - throw new NotSupportedException(); - } - - public override string[] FindUsersInRole(string roleName, string usernameToMatch) - { - throw new NotSupportedException(); - } - - public override string ApplicationName { get; set; } - } -} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs b/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs index 90c8d64e..e21422f2 100644 --- a/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs +++ b/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs @@ -23,292 +23,313 @@ namespace FunnelWeb.Web.Application.Extensions { - public static class MarkupExtensions - { - public static MvcHtmlString Version(this HtmlHelper html) - { - return MvcHtmlString.Create(Assembly.GetExecutingAssembly().GetName().Version.ToString()); - } - - #region URL's - - /// - /// Fixes up any relative links so they work in all scenarios - /// - /// - /// - /// - public static MvcHtmlString QualifyLinks(this HtmlHelper html, string htmlToProcess) - { - foreach (Match match in Regex.Matches(htmlToProcess, "href=['\"](?.*?)['\"]")) - { - var urlGroup = match.Groups["url"]; - var url = urlGroup.Value; - - if (url[0] == '~') - url = url.Substring(1); - - if (!url.StartsWith("/")) continue; - - var mvcHtmlString = html.Qualify(url); - string newValue = mvcHtmlString.ToString(); - htmlToProcess = htmlToProcess.Replace(urlGroup.Value, newValue); - } - - return MvcHtmlString.Create(htmlToProcess); - } - - public static MvcHtmlString Qualify(this HtmlHelper html, MvcHtmlString url) - { - return Qualify(html, url.ToHtmlString()); - } - - public static MvcHtmlString Qualify(this HtmlHelper html, string url) - { - var prefix = html.ViewContext.HttpContext.Request.GetBaseUrl().ToString().TrimEnd('/'); - - if (url.StartsWith("{1}", - @class, - html.Encode(text ?? string.Empty).Trim()) - ); - } - if (!url.StartsWith("http")) - { - url = "http://" + url; - } - return MvcHtmlString.Create(string.Format("{2}", - html.AttributeEncode(url), - @class, - html.Encode(text ?? string.Empty).Trim() - )); - } - - public static IHtmlString CommentedAtRevision(this HtmlHelper html, Comment comment, EntryRevision revision) - { - if (Settings(html).EnablePublicHistory == false) - return MvcHtmlString.Empty; - - if (comment.EntryRevisionNumber != revision.RevisionNumber) - { - if(comment.EntryRevisionNumber == comment.Entry.LatestRevision.RevisionNumber) - return MvcHtmlString.Create("@ " + - html.ActionLink("latest", - "Page", "Wiki", - new { page = comment.Entry.Name }, - new { })); - return MvcHtmlString.Create("@ " + - html.ActionLink(string.Format("version {0}", comment.EntryRevisionNumber), - "Page", "Wiki", - new { page = comment.Entry.Name, revision = comment.EntryRevisionNumber }, - new {})); - } - return MvcHtmlString.Empty; - } - - #endregion - - #region Text - - public static IHtmlString Date(this HtmlHelper html, object value) - { - var date = (DateTime)value; - return MvcHtmlString.Create(string.Format("{1}", - date.ToString("dd MMM, yyyy HH:mm"), - date.ToString("dd MMM, yyyy hh:mm tt"))); - } - - public static IHtmlString DateWithoutTime(this HtmlHelper html, object value) - { - var date = (DateTime)value; - return MvcHtmlString.Create(string.Format("{1}", - date.ToString("dd MMM, yyyy"), - date.ToString("dd MMM, yyyy"))); - } - - public static MvcHtmlString RenderTrusted(this HtmlHelper html, object content, string format) - { - var renderer = DependencyResolver.Current.GetService(); - var rendered = renderer.RenderTrusted((content ?? string.Empty).ToString(), format, html); - return MvcHtmlString.Create(rendered); - } - public static MvcHtmlString RenderUntrusted(this HtmlHelper html, object content, string format) - { - var renderer = DependencyResolver.Current.GetService(); - var rendered = renderer.RenderUntrusted((content ?? string.Empty).ToString(), format, html); - return MvcHtmlString.Create(rendered); - } - - public static MvcHtmlString TextilizeList(this HtmlHelper html, object content) - { - var text = (content ?? string.Empty).ToString(); - text = string.Join("\n", - text.Split('\n') - .Select(x => x.Trim()) - .Where(x => !string.IsNullOrEmpty(x)) - .Select(x => x.StartsWith("- ") ? x : "- " + x) - .ToArray() - ); - text = text.Replace("\n", "
\n"); - return MvcHtmlString.Create(text); - } - - static readonly Regex keyword = new Regex("^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$", RegexOptions.Compiled); - static readonly Regex keywordReplace = new Regex(@"[ &\.#]+", RegexOptions.Compiled); - public static IEnumerable CssKeywordsFor(this HtmlHelper html, EntrySummary entry) - { - var tags = entry.TagsCommaSeparated.Split(new[]{","}, StringSplitOptions.RemoveEmptyEntries); - return from tagName in tags - select CssKeywordsForTag(html, tagName); - } - - public static IEnumerable CssKeywordsFor(this HtmlHelper html, EntryRevision entry) - { - return html.CssKeywordsFor(entry.Tags); - } - - private static IEnumerable CssKeywordsFor(this HtmlHelper html, IEnumerable tags) - { - return from tagName in tags.Select(x => x.Name) - select CssKeywordsForTag(html, tagName); - } - - public static MvcHtmlString CssKeywordsForTag(this HtmlHelper html, string tag) - { - var fixedTag = keywordReplace.Replace(tag, "-"); - return keyword.IsMatch(fixedTag) - ? MvcHtmlString.Create("keyword-" + fixedTag) - : MvcHtmlString.Empty; - } - - #endregion - - #region Hint - - public static IHtmlString HintFor(this HtmlHelper helper, Expression> property) - { - var message = ""; - WhenEncountering(property, d => message = d.Description); - - message = (message ?? string.Empty).Trim(); - if (message.Length == 0) - { - return new HtmlString(string.Empty); - } - - return new HtmlString("" + message + ""); - } - - #endregion - - #region Settings - - public static FunnelWebSettings Settings(this HtmlHelper helper) - { - var settingsProvider = DependencyResolver.Current.GetService(); - - // A database upgrade is required, lets just use the default settings - if (DependencyResolver.Current.GetService().UpdateNeeded()) - return settingsProvider.GetDefaultSettings(); - - return settingsProvider.GetSettings(); - } - - #endregion - - private static void WhenEncountering(LambdaExpression expression, Action callback) - { - var member = expression.Body as MemberExpression; - if (member == null) - { - var unary = expression.Body as UnaryExpression; - if (unary == null) - return; - - member = unary.Operand as MemberExpression; - } - foreach (var instance in member.Member.GetCustomAttributes(true).OfType()) - { - callback(instance); - } - } - - public static IDictionary AttributesFor(this HtmlHelper htmlHelper, Expression> expression) - { - var attributes = new RouteValueDictionary {{"class", ""}}; - - WhenEncountering(expression, att => attributes["maxlength"] = att.MaximumLength); - WhenEncountering(expression, att => - { - attributes["class"] += att.Size.ToString().ToLowerInvariant() + " "; - }); - - attributes["class"] = attributes["class"].ToString().Trim(); - return attributes; - } - - /// - /// Create an action link to an action in the Admin area. - /// - public static MvcHtmlString AdminActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string adminControllerName) - { - // http://stackoverflow.com/questions/2036305/how-to-specify-an-area-name-in-an-action-link - return htmlHelper.ActionLink(linkText, actionName, adminControllerName, new {Area = "Admin"}, new {}); - } - - public static string ThemePath(this HtmlHelper helper) - { - return "~/Themes/" + helper.Settings().Theme; - } - } -} + public static class MarkupExtensions + { + public static MvcHtmlString Version(this HtmlHelper html) + { + return MvcHtmlString.Create(Assembly.GetExecutingAssembly().GetName().Version.ToString()); + } + + #region URL's + + /// + /// Fixes up any relative links so they work in all scenarios + /// + /// + /// + /// + public static MvcHtmlString QualifyLinks(this HtmlHelper html, string htmlToProcess) + { + foreach (Match match in Regex.Matches(htmlToProcess, "href=['\"](?.*?)['\"]")) + { + var urlGroup = match.Groups["url"]; + var url = urlGroup.Value; + + if (url[0] == '~') + url = url.Substring(1); + + if (!url.StartsWith("/")) continue; + + var mvcHtmlString = html.Qualify(url); + string newValue = mvcHtmlString.ToString(); + htmlToProcess = htmlToProcess.Replace(urlGroup.Value, newValue); + } + + return MvcHtmlString.Create(htmlToProcess); + } + + public static MvcHtmlString Qualify(this HtmlHelper html, MvcHtmlString url) + { + return Qualify(html, url.ToHtmlString()); + } + + public static MvcHtmlString Qualify(this HtmlHelper html, string url) + { + var prefix = html.ViewContext.HttpContext.Request.GetBaseUrl().ToString().TrimEnd('/'); + + if (url.StartsWith("{1}", + @class, + html.Encode(text ?? string.Empty).Trim()) + ); + } + if (!url.StartsWith("http")) + { + url = "http://" + url; + } + return MvcHtmlString.Create(string.Format("{2}", + html.AttributeEncode(url), + @class, + html.Encode(text ?? string.Empty).Trim() + )); + } + + public static IHtmlString CommentedAtRevision(this HtmlHelper html, Comment comment, EntryRevision revision) + { + if (Settings(html).EnablePublicHistory == false) + return MvcHtmlString.Empty; + + if (comment.EntryRevisionNumber != revision.RevisionNumber) + { + if (comment.EntryRevisionNumber == comment.Entry.LatestRevision.RevisionNumber) + return MvcHtmlString.Create("@ " + + html.ActionLink("latest", + "Page", "Wiki", + new { page = comment.Entry.Name }, + new { })); + return MvcHtmlString.Create("@ " + + html.ActionLink(string.Format("version {0}", comment.EntryRevisionNumber), + "Page", "Wiki", + new { page = comment.Entry.Name, revision = comment.EntryRevisionNumber }, + new { })); + } + return MvcHtmlString.Empty; + } + + #endregion + + #region Text + + public static IHtmlString Date(this HtmlHelper html, DateTime value) + { + return MvcHtmlString.Create(value.ToString("yyyy-MM-dd hh:mm")); + } + + public static IHtmlString DateWithoutTime(this HtmlHelper html, DateTime value) + { + return MvcHtmlString.Create(value.ToString("yyyy-MM-dd")); + } + + //public static IHtmlString Date(this HtmlHelper html, object value) + //{ + // var date = (DateTime)value; + // return MvcHtmlString.Create(string.Format("{1}", + // date.ToString("dd MMM, yyyy HH:mm"), + // date.ToString("dd MMM, yyyy hh:mm tt"))); + //} + + //public static IHtmlString DateWithoutTime(this HtmlHelper html, object value) + //{ + // var date = (DateTime)value; + // return MvcHtmlString.Create(string.Format("{1}", + // date.ToString("dd MMM, yyyy"), + // date.ToString("dd MMM, yyyy"))); + //} + + public static MvcHtmlString RenderTrusted(this HtmlHelper html, object content, string format) + { + var renderer = DependencyResolver.Current.GetService(); + var rendered = renderer.RenderTrusted((content ?? string.Empty).ToString(), format, html); + return MvcHtmlString.Create(rendered); + } + public static MvcHtmlString RenderUntrusted(this HtmlHelper html, object content, string format) + { + var renderer = DependencyResolver.Current.GetService(); + var rendered = renderer.RenderUntrusted((content ?? string.Empty).ToString(), format, html); + return MvcHtmlString.Create(rendered); + } + + public static MvcHtmlString TextilizeList(this HtmlHelper html, object content) + { + var text = (content ?? string.Empty).ToString(); + text = string.Join("\n", + text.Split('\n') + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => x.StartsWith("- ") ? x : "- " + x) + .ToArray() + ); + text = text.Replace("\n", "
\n"); + return MvcHtmlString.Create(text); + } + + static readonly Regex keyword = new Regex("^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$", RegexOptions.Compiled); + static readonly Regex keywordReplace = new Regex(@"[ &\.#]+", RegexOptions.Compiled); + public static IEnumerable CssKeywordsFor(this HtmlHelper html, EntrySummary entry) + { + var tags = entry.TagsCommaSeparated.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return from tagName in tags + select CssKeywordsForTag(html, tagName); + } + + public static IEnumerable CssKeywordsFor(this HtmlHelper html, EntryRevision entry) + { + return html.CssKeywordsFor(entry.Tags); + } + + private static IEnumerable CssKeywordsFor(this HtmlHelper html, IEnumerable tags) + { + return from tagName in tags.Select(x => x.Name) + select CssKeywordsForTag(html, tagName); + } + + public static MvcHtmlString CssKeywordsForTag(this HtmlHelper html, string tag) + { + var fixedTag = keywordReplace.Replace(tag, "-"); + return keyword.IsMatch(fixedTag) + ? MvcHtmlString.Create("keyword-" + fixedTag) + : MvcHtmlString.Empty; + } + + #endregion + + #region Hint + + public static IHtmlString HintFor(this HtmlHelper helper, Expression> property) + { + var message = ""; + WhenEncountering(property, d => message = d.Description); + + message = (message ?? string.Empty).Trim(); + if (message.Length == 0) + { + return new HtmlString(string.Empty); + } + + return new HtmlString("" + message + ""); + } + + #endregion + + #region Settings + + public static FunnelWebSettings Settings(this HtmlHelper helper) + { + var settingsProvider = DependencyResolver.Current.GetService(); + + // A database upgrade is required, lets just use the default settings + if (DependencyResolver.Current.GetService().UpdateNeeded()) + return settingsProvider.GetDefaultSettings(); + + return settingsProvider.GetSettings(); + } + + public static AccessControlServiceSettings AcsSettings(this HtmlHelper helper) + { + var settingsProvider = DependencyResolver.Current.GetService(); + + return + DependencyResolver.Current.GetService().UpdateNeeded() ? + // A database upgrade is required, lets just use the default settings + settingsProvider.GetDefaultSettings() : + settingsProvider.GetSettings(); + } + + #endregion + + private static void WhenEncountering(LambdaExpression expression, Action callback) + { + var member = expression.Body as MemberExpression; + if (member == null) + { + var unary = expression.Body as UnaryExpression; + if (unary == null) + return; + + member = unary.Operand as MemberExpression; + } + foreach (var instance in member.Member.GetCustomAttributes(true).OfType()) + { + callback(instance); + } + } + + public static IDictionary AttributesFor(this HtmlHelper htmlHelper, Expression> expression) + { + var attributes = new RouteValueDictionary { { "class", "" } }; + + WhenEncountering(expression, att => attributes["maxlength"] = att.MaximumLength); + WhenEncountering(expression, att => + { + attributes["class"] += att.Size.ToString().ToLowerInvariant() + " "; + }); + + attributes["class"] = attributes["class"].ToString().Trim(); + return attributes; + } + + /// + /// Create an action link to an action in the Admin area. + /// + public static MvcHtmlString AdminActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string adminControllerName) + { + // http://stackoverflow.com/questions/2036305/how-to-specify-an-area-name-in-an-action-link + return htmlHelper.ActionLink(linkText, actionName, adminControllerName, new { Area = "Admin" }, new { }); + } + + public static string ThemePath(this HtmlHelper helper) + { + return "~/Themes/" + helper.Settings().Theme; + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Application/Extensions/ViewDataDictionaryExtensions.cs b/src/FunnelWeb.Web/Application/Extensions/ViewDataDictionaryExtensions.cs deleted file mode 100644 index acbc7014..00000000 --- a/src/FunnelWeb.Web/Application/Extensions/ViewDataDictionaryExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Web; -using System.Web.Mvc; - -namespace FunnelWeb.Web.Application.Extensions -{ - public static class ViewDataDictionaryExtensions - { - public static bool IsLoggedIn(this ViewDataDictionary viewData) - { - return HttpContext.Current.User.Identity.IsAuthenticated; - } - } -} diff --git a/src/FunnelWeb.Web/Application/MetaWeblog/MetaWeblog.cs b/src/FunnelWeb.Web/Application/MetaWeblog/MetaWeblog.cs index 4dd27d1a..ef089283 100644 --- a/src/FunnelWeb.Web/Application/MetaWeblog/MetaWeblog.cs +++ b/src/FunnelWeb.Web/Application/MetaWeblog/MetaWeblog.cs @@ -1,8 +1,10 @@ using System; using System.Data; -using System.IO; +using System.Globalization; +using System.IO; using System.Linq; -using System.Web; +using System.Net; +using System.Web; using CookComputing.XmlRpc; using FunnelWeb.Authentication; using FunnelWeb.Model; @@ -15,266 +17,304 @@ namespace FunnelWeb.Web.Application.MetaWeblog { - public class MetaWeblog : XmlRpcService, IMetaWeblog - { - private readonly FunnelWebSettings funnelWebSettings; - private readonly ISettingsProvider settingsProvider; - private readonly IRepository repository; - private readonly ISession session; - private readonly IFileRepository fileRepository; - private readonly IAuthenticator authenticator; - - public MetaWeblog( - ISettingsProvider settingsProvider, - IRepository repository, - ISession session, - IFileRepository fileRepository, - IAuthenticator authenticator) - { - this.settingsProvider = settingsProvider; - this.repository = repository; - this.session = session; - this.fileRepository = fileRepository; - this.authenticator = authenticator; - funnelWebSettings = this.settingsProvider.GetSettings(); - } - - public string AddPost(string blogid, string username, string password, Post post, bool publish) - { - var entry = CreateUpdatePost(username, password, "-1", post, publish); - if (entry != null) - return entry.Id.ToString(); - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public bool UpdatePost(string postid, string username, string password, Post post, bool publish) - { - var entry = CreateUpdatePost(username, password, postid, post, publish); - if (entry != null) - return true; - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - private Entry CreateUpdatePost(string username, string password, string postid, Post post, bool publish) - { - if (ValidateUser(username, password)) - { - using (var transaction = session.BeginTransaction(IsolationLevel.Serializable)) - { - var isOldPost = true; - var author = authenticator.GetName(); - var entry = repository.Get(Int32.Parse(postid)); - - if (entry == null) - { - entry = new Entry { Author = author }; - isOldPost = false; - } - - entry.Name = post.permalink; - entry.Title = post.title ?? string.Empty; - entry.Summary = string.IsNullOrEmpty(post.mt_excerpt) ? entry.Summary : post.mt_excerpt; - entry.MetaTitle = post.title; - entry.Published = (post.dateCreated < DateTime.Today.AddYears(10) ? DateTime.Today : post.dateCreated).ToUniversalTime(); - entry.Status = publish ? EntryStatus.PublicBlog : EntryStatus.Private; - - var revision = entry.Revise(); - revision.Author = author; - revision.Body = post.description; - revision.Reason = "API"; - revision.Format = string.Equals(post.format, Formats.Markdown, StringComparison.InvariantCultureIgnoreCase) ? Formats.Markdown : Formats.Html; - - if (string.IsNullOrWhiteSpace(entry.Name)) - entry.Name = post.title.Replace(" ", "-"); - - // support for slug - if (!string.IsNullOrEmpty(post.wp_slug)) - entry.Name = post.wp_slug; - - entry.MetaDescription = entry.MetaDescription ?? post.mt_excerpt; - - var editTags = post.categories; - var toDelete = entry.Tags.Where(t => !editTags.Contains(t.Name)).ToList(); - var toAdd = editTags.Where(t => !entry.Tags.Any(tag => tag.Name == t)).ToList(); - - foreach (var tag in toDelete) - tag.Remove(entry); - foreach (var tag in toAdd) - { - var existingTag = repository.FindFirstOrDefault(new SearchTagsByNameQuery(tag)); - if (existingTag == null) - { - existingTag = new Tag { Name = tag }; - repository.Add(existingTag); - } - existingTag.Add(entry); - } - - //Does it need to be added? - if (!isOldPost) - { - repository.Add(entry); - } - - session.Flush(); - transaction.Commit(); - - return entry; - } - } - return null; - } - - public Post GetPost(string postid, string username, string password) - { - if (ValidateUser(username, password)) - { - var entry = repository.Get(Int32.Parse(postid)); - - return entry != null ? ConvertToPost(entry) : new Post(); - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public CategoryInfo[] GetCategories(string blogid, string username, string password) - { - if (ValidateUser(username, password)) - { - //TODO implement a tagged rss feed, and update url - - return repository.FindAll() - .Select(t => new CategoryInfo - { - categoryid = t.Id.ToString(), - title = t.Name, - description = t.Name, - htmlUrl = - new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/tagged/" + t.Name).ToString(), - rssUrl = - new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/tagged/" + t.Name).ToString() - }) - .ToArray(); - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public Post[] GetRecentPosts(string blogid, string username, string password, int numberOfPosts) - { - if (ValidateUser(username, password)) - { - var entries = repository.Find(new GetFullEntriesQuery(), 0, numberOfPosts) - .Select(ConvertToPost) - .ToArray(); - - return entries; - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - private Post ConvertToPost(Entry entry) - { - return new Post - { - dateCreated = entry.LatestRevision.Revised, - categories = entry.Tags.Select(t => t.Name).ToArray(), - description = entry.LatestRevision.Body, - permalink = entry.Name.ToString(), - postid = entry.Id, - title = entry.Title, - userid = "FunnelWeb", - wp_slug = entry.Name.ToString(), - mt_excerpt = entry.MetaDescription - }; - } - - private static Post ConvertToPost(EntryRevision entry) - { - return new Post - { - dateCreated = entry.Revised, - categories = entry.TagsCommaSeparated.Split(',').ToArray(), - description = entry.Body, - permalink = entry.Name.ToString(), - postid = entry.Id, - title = entry.Title, - userid = "FunnelWeb", - wp_slug = entry.Name.ToString(), - mt_excerpt = entry.MetaDescription - }; - } - - public MediaObjectInfo NewMediaObject(string blogid, string username, string password, MediaObject mediaObject) - { - if (ValidateUser(username, password)) - { - // WLR sends images with the name "imge.png". Very resourcefull - var objectInfo = new MediaObjectInfo(); - - // image - using (var memoryStream = new MemoryStream(mediaObject.bits)) - { - var fileName = Path.GetFileNameWithoutExtension(mediaObject.name) + "_" + DateTime.Now.Ticks + Path.GetExtension(mediaObject.name); - - fileRepository.Save(memoryStream, fileName, false); - objectInfo.url = new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/get/" + fileName.TrimStart('/')).ToString(); - } - - return objectInfo; - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public bool DeletePost(string key, string postid, string username, string password, bool publish) - { - if (ValidateUser(username, password)) - { - using (var transaction = session.BeginTransaction(IsolationLevel.Serializable)) - { - repository.Remove(repository.Get(Int32.Parse(postid))); - - session.Flush(); - transaction.Commit(); - } - return true; - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public BlogInfo[] GetUsersBlogs(string key, string username, string password) - { - if (ValidateUser(username, password)) - { - // FunnelWeb only has a single blog - var homepageUri = new Uri(Context.Request.GetOriginalUrl(), "/"); - - var blogInfo = new BlogInfo - { - blogName = funnelWebSettings.SiteTitle, - url = homepageUri.ToString(), - blogid = "FunnelWeb" - }; - - return new[] { blogInfo }; - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - public UserInfo GetUserInfo(string key, string username, string password) - { - if (ValidateUser(username, password)) - { - var info = new UserInfo(); - - // TODO: Implement your own logic to get user info objects and set the info - - return info; - } - throw new XmlRpcFaultException(0, "User is not valid!"); - } - - private bool ValidateUser(string username, string password) - { - return authenticator.AuthenticateAndLogin(username, password); - } - } + public class MetaWeblog : XmlRpcService, IMetaWeblog + { + private readonly FunnelWebSettings funnelWebSettings; + private readonly IRepository repository; + private readonly ISession session; + private readonly IFileRepository fileRepository; + private readonly IAuthenticator authenticator; + + public MetaWeblog( + ISettingsProvider settingsProvider, + IRepository repository, + ISession session, + IFileRepository fileRepository, + IAuthenticator authenticator) + { + this.repository = repository; + this.session = session; + this.fileRepository = fileRepository; + this.authenticator = authenticator; + funnelWebSettings = settingsProvider.GetSettings(); + } + + public string AddPost(string blogid, string username, string password, Post post, bool publish) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + var entry = CreateUpdatePost("-1", post, publish); + if (entry != null) + { + return entry.Id.ToString(CultureInfo.InvariantCulture); + } + + return null; + } + + public bool UpdatePost(string postid, string username, string password, Post post, bool publish) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + var entry = CreateUpdatePost(postid, post, publish); + if (entry != null) + return true; + throw new XmlRpcFaultException(0, "User is not valid!"); + } + + public Post GetPost(string postid, string username, string password) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + var entry = repository.Get(Int32.Parse(postid)); + + return entry != null ? ConvertToPost(entry) : new Post(); + } + + public CategoryInfo[] GetCategories(string blogid, string username, string password) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + //TODO implement a tagged rss feed, and update url + + return repository.FindAll() + .Select(t => new CategoryInfo + { + categoryid = t.Id.ToString(), + title = t.Name, + description = t.Name, + htmlUrl = + new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/tagged/" + t.Name).ToString(), + rssUrl = + new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/tagged/" + t.Name).ToString() + }) + .ToArray(); + } + + public Post[] GetRecentPosts(string blogid, string username, string password, int numberOfPosts) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + var entries = repository + .Find(new GetFullEntriesQuery(), 0, numberOfPosts) + .Select(ConvertToPost) + .ToArray(); + + return entries; + } + + public MediaObjectInfo NewMediaObject(string blogid, string username, string password, MediaObject mediaObject) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + // WLR sends images with the name "imge.png". Very resourcefull + var objectInfo = new MediaObjectInfo(); + + // image + using (var memoryStream = new MemoryStream(mediaObject.bits)) + { + var fileName = Path.GetFileNameWithoutExtension(mediaObject.name) + "_" + DateTime.Now.Ticks + + Path.GetExtension(mediaObject.name); + + fileRepository.Save(memoryStream, fileName, false); + objectInfo.url = new Uri(HttpContext.Current.Request.GetOriginalUrl(), "/get/" + fileName.TrimStart('/')).ToString(); + } + + return objectInfo; + } + + public bool DeletePost(string key, string postid, string username, string password, bool publish) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + using (var transaction = session.BeginTransaction(IsolationLevel.Serializable)) + { + repository.Remove(repository.Get(Int32.Parse(postid))); + + session.Flush(); + transaction.Commit(); + } + + return true; + } + + public BlogInfo[] GetUsersBlogs(string key, string username, string password) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + // FunnelWeb only has a single blog + var homepageUri = new Uri(Context.Request.GetOriginalUrl(), "/"); + + var blogInfo = new BlogInfo + { + blogName = funnelWebSettings.SiteTitle, + url = homepageUri.ToString(), + blogid = "FunnelWeb" + }; + + return new[] { blogInfo }; + } + + public UserInfo GetUserInfo(string key, string username, string password) + { + Exception exception; + if (!IsValidUser(username, password, out exception)) + { + throw exception; + } + + var info = new UserInfo(); + + // TODO: Implement your own logic to get user info objects and set the info + + return info; + } + + private Post ConvertToPost(Entry entry) + { + return new Post + { + dateCreated = entry.LatestRevision.Revised, + categories = entry.Tags.Select(t => t.Name).ToArray(), + description = entry.LatestRevision.Body, + permalink = entry.Name.ToString(), + postid = entry.Id, + title = entry.Title, + userid = "FunnelWeb", + wp_slug = entry.Name.ToString(), + mt_excerpt = entry.MetaDescription + }; + } + + private static Post ConvertToPost(EntryRevision entry) + { + return new Post + { + dateCreated = entry.Revised, + categories = entry.TagsCommaSeparated.Split(',').ToArray(), + description = entry.Body, + permalink = entry.Name.ToString(), + postid = entry.Id, + title = entry.Title, + userid = "FunnelWeb", + wp_slug = entry.Name.ToString(), + mt_excerpt = entry.MetaDescription + }; + } + + private Entry CreateUpdatePost(string postid, Post post, bool publish) + { + using (var transaction = session.BeginTransaction(IsolationLevel.Serializable)) + { + var isOldPost = true; + var author = authenticator.GetName(); + var entry = repository.Get(Int32.Parse(postid)); + + if (entry == null) + { + entry = new Entry { Author = author }; + isOldPost = false; + } + + entry.Name = post.permalink; + entry.Title = post.title ?? string.Empty; + entry.Summary = string.IsNullOrEmpty(post.mt_excerpt) ? entry.Summary : post.mt_excerpt; + entry.MetaTitle = post.title; + entry.Published = + (post.dateCreated < DateTime.Today.AddYears(10) ? DateTime.Today : post.dateCreated).ToUniversalTime(); + entry.Status = publish ? EntryStatus.PublicBlog : EntryStatus.Private; + + var revision = entry.Revise(); + revision.Author = author; + revision.Body = post.description; + revision.Reason = "API"; + revision.Format = string.Equals(post.format, Formats.Markdown, StringComparison.InvariantCultureIgnoreCase) + ? Formats.Markdown + : Formats.Html; + + if (string.IsNullOrWhiteSpace(entry.Name)) + entry.Name = post.title.Replace(" ", "-"); + + // support for slug + if (!string.IsNullOrEmpty(post.wp_slug)) + entry.Name = post.wp_slug; + + entry.MetaDescription = entry.MetaDescription ?? post.mt_excerpt; + + var editTags = post.categories; + var toDelete = entry.Tags.Where(t => !editTags.Contains(t.Name)).ToList(); + var toAdd = editTags.Where(t => entry.Tags.All(tag => tag.Name != t)).ToList(); + + foreach (var tag in toDelete) + { + tag.Remove(entry); + } + + foreach (var tag in toAdd) + { + var existingTag = repository.FindFirstOrDefault(new SearchTagsByNameQuery(tag)); + if (existingTag == null) + { + existingTag = new Tag { Name = tag }; + repository.Add(existingTag); + } + existingTag.Add(entry); + } + + //Does it need to be added? + if (!isOldPost) + { + repository.Add(entry); + } + + session.Flush(); + transaction.Commit(); + + return entry; + } + } + + private bool IsValidUser(string username, string password, out Exception exception) + { + if (!authenticator.AuthenticateAndLogin(username, password)) + { + exception = new XmlRpcFaultException(Convert.ToInt32(HttpStatusCode.Unauthorized), "Unauthorized user!"); + return false; + } + + exception = null; + return true; + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Application/Mvc/Binders/BindersModule.cs b/src/FunnelWeb.Web/Application/Mvc/Binders/BindersModule.cs index 6051941e..44418801 100644 --- a/src/FunnelWeb.Web/Application/Mvc/Binders/BindersModule.cs +++ b/src/FunnelWeb.Web/Application/Mvc/Binders/BindersModule.cs @@ -5,21 +5,21 @@ namespace FunnelWeb.Web.Application.Mvc.Binders { - public class BindersModule : Module - { - private readonly ModelBinderDictionary binders; + public class BindersModule : Module + { + private readonly ModelBinderDictionary binders; - public BindersModule(ModelBinderDictionary binders) - { - this.binders = binders; - } + public BindersModule(ModelBinderDictionary binders) + { + this.binders = binders; + } - protected override void Load(ContainerBuilder builder) - { - binders.Add(typeof(PageName), new ImplicitAssignmentBinder()); - binders.Add(typeof(int[]), new ArrayBinder()); - binders.Add(typeof(FileUpload), new UploadBinder()); - binders.Add(typeof (EntryRevision), new EntryRevisionBinder()); - } - } + protected override void Load(ContainerBuilder builder) + { + if (!binders.ContainsKey(typeof(PageName))) binders.Add(typeof(PageName), new ImplicitAssignmentBinder()); + if (!binders.ContainsKey(typeof(int[]))) binders.Add(typeof(int[]), new ArrayBinder()); + if (!binders.ContainsKey(typeof(FileUpload))) binders.Add(typeof(FileUpload), new UploadBinder()); + if (!binders.ContainsKey(typeof(EntryRevision))) binders.Add(typeof(EntryRevision), new EntryRevisionBinder()); + } + } } diff --git a/src/FunnelWeb.Web/Application/Mvc/MvcExtensions.cs b/src/FunnelWeb.Web/Application/Mvc/MvcExtensions.cs index 6a1b28f6..108088a8 100644 --- a/src/FunnelWeb.Web/Application/Mvc/MvcExtensions.cs +++ b/src/FunnelWeb.Web/Application/Mvc/MvcExtensions.cs @@ -2,29 +2,29 @@ namespace FunnelWeb.Web.Application.Mvc { - public static class MvcExtensions - { - public static FlashResult AndFlash(this TResult result, string messageFormat, params object[] args) where TResult : ActionResult - { - return new FlashResult(result, string.Format(messageFormat, args)); - } + public static class MvcExtensions + { + public static FlashResult AndFlash(this TResult result, string messageFormat, params object[] args) where TResult : ActionResult + { + return new FlashResult(result, string.Format(messageFormat, args)); + } - public class FlashResult : ActionResult where TResult : ActionResult - { - private readonly TResult result; - private readonly string message; + public class FlashResult : ActionResult where TResult : ActionResult + { + private readonly TResult result; + private readonly string message; - public FlashResult(TResult result, string message) - { - this.result = result; - this.message = message; - } + public FlashResult(TResult result, string message) + { + this.result = result; + this.message = message; + } - public override void ExecuteResult(ControllerContext context) - { - context.Controller.TempData["Flash"] = message; - result.ExecuteResult(context); - } - } - } + public override void ExecuteResult(ControllerContext context) + { + context.Controller.TempData["Flash"] = message; + result.ExecuteResult(context); + } + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Application/RequireUpdatedDatabaseHttpModule.cs b/src/FunnelWeb.Web/Application/RequireUpdatedDatabaseHttpModule.cs index 55f08443..4d13f111 100644 --- a/src/FunnelWeb.Web/Application/RequireUpdatedDatabaseHttpModule.cs +++ b/src/FunnelWeb.Web/Application/RequireUpdatedDatabaseHttpModule.cs @@ -5,35 +5,40 @@ namespace FunnelWeb.Web.Application { - public class RequireUpdatedDatabaseHttpModule : IHttpModule - { - public void Init(HttpApplication context) - { - context.BeginRequest += ApplicationBeginRequest; - context.Error += ApplicationError; - } + public class RequireUpdatedDatabaseHttpModule : IHttpModule + { + private HttpApplication httpApplication; - private static void ApplicationBeginRequest(object sender, EventArgs e) - { - if (!DependencyResolver.Current.GetService().UpdateNeeded()) return; + public void Init(HttpApplication httpApplication) + { + this.httpApplication = httpApplication; + httpApplication.BeginRequest += ApplicationBeginRequest; + httpApplication.Error += ApplicationError; + } - var path = HttpContext.Current.Request.Path; - path = path.ToLowerInvariant(); - if (path.Contains("/login") || path.Contains("/install") || path.Contains("/content")) - { - return; - } + private static void ApplicationBeginRequest(object sender, EventArgs e) + { + if (!DependencyResolver.Current.GetService().UpdateNeeded()) return; - HttpContext.Current.Response.Redirect("~/admin/login?databaseIssue=true"); - } + var path = HttpContext.Current.Request.Path; + path = path.ToLowerInvariant(); + if (path.EndsWith(".js") || + path.EndsWith(".css") || + path.Contains("/login") || + path.Contains("/install") || + path.Contains("/content")) + { + return; + } - private static void ApplicationError(object sender, EventArgs e) - { - DependencyResolver.Current.GetService().Reset(); - } + HttpContext.Current.Response.Redirect("~/admin/login?databaseIssue=true"); + } - public void Dispose() - { - } - } + private static void ApplicationError(object sender, EventArgs e) + { + DependencyResolver.Current.GetService().Reset(); + } + + public void Dispose() { } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs index 5b402d87..c2115434 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs @@ -1,7 +1,10 @@ using System; +using System.IdentityModel.Services; using System.IO; using System.Linq; +using System.Security.Permissions; using System.Web.Mvc; +using FunnelWeb.Authentication.Internal; using FunnelWeb.Filters; using FunnelWeb.Model; using FunnelWeb.Model.Repositories; @@ -14,174 +17,211 @@ using FunnelWeb.Web.Application.Mvc; using FunnelWeb.Web.Application.Themes; using FunnelWeb.Web.Areas.Admin.Views.Admin; -using NHibernate; namespace FunnelWeb.Web.Areas.Admin.Controllers { - [FunnelWebRequest] - [ValidateInput(false)] - [Authorize(Roles="Admin")] - public class AdminController : Controller - { - public IAdminRepository AdminRepository { get; set; } - public ISettingsProvider SettingsProvider { get; set; } - public IThemeProvider ThemeProvider { get; set; } - public IRepository Repository { get; set; } - public ITaskStateRepository TaskRepository { get; set; } - public ISession DatabaseSession { get; set; } - public ITaskExecutor ImportTask { get; set; } - public Func> FileRepositoriesInfo { get; set; } - - public virtual ActionResult Index() - { - return View(new IndexModel()); - } - - public virtual ActionResult Settings() - { - var settings = SettingsProvider.GetSettings(); - ViewBag.Themes = ThemeProvider.GetThemes(); - ViewBag.FileRepositories = FileRepositoriesInfo().Keys; - return View(settings); - } - - [HttpPost] - public virtual ActionResult Settings(FunnelWebSettings settings) - { - ViewBag.Themes = ThemeProvider.GetThemes(); - ViewBag.FileRepositories = FileRepositoriesInfo().Keys; - if (!ModelState.IsValid) - { - ModelState.AddModelError("", "Your settings could not be saved. Please fix the errors shown below."); - return View(settings); - } - - SettingsProvider.SaveSettings(settings); - - return RedirectToAction("Settings", "Admin") - .AndFlash("Your changes have been saved"); - } - - #region Comments - - public virtual ActionResult Comments(int? pageNumber) - { - var page = pageNumber ?? 0; - - var comments = Repository.Find(new GetAllCommentsQuery(), page, 20); - return View(new CommentsModel(page, comments)); - } - - public virtual ActionResult DeleteComment(int id) - { - var item = Repository.Get(id); - Repository.Remove(item); - AdminRepository.UpdateCommentCountFor(item.Entry.Id); - return RedirectToAction("Comments", "Admin"); - } - - public virtual ActionResult DeleteAllSpam() - { - var comments = Repository.Find(new GetSpamQuery()).ToList(); - foreach (var comment in comments) - Repository.Remove(comment); - foreach (var entryToUpdate in comments.Select(c=>c.Entry.Id).GroupBy(id=>id)) - { - AdminRepository.UpdateCommentCountFor(entryToUpdate.Key); - } - return RedirectToAction("Comments", "Admin"); - } - - public virtual ActionResult ToggleSpam(int id) - { - var item = Repository.Get(id); - if (item != null) - { - item.IsSpam = !item.IsSpam; - AdminRepository.UpdateCommentCountFor(item.Entry.Id); - } - return RedirectToAction("Comments", "Admin"); - } - - #endregion - - #region Pingbacks - - public virtual ActionResult Pingbacks() - { - var pingbacks = Repository.FindAll(); - return View(new PingbacksModel(pingbacks)); - } - - public virtual ActionResult DeletePingback(int id) - { - var item = Repository.Get(id); - Repository.Remove(item); - return RedirectToAction("Pingbacks", "Admin"); - } - - public virtual ActionResult TogglePingbackSpam(int id) - { - var item = Repository.Get(id); - if (item != null) - { - item.IsSpam = !item.IsSpam; - } - return RedirectToAction("Pingbacks", "Admin"); - } - - #endregion - - #region Tasks - - public virtual ActionResult Tasks() - { - var tasks = TaskRepository.GetAll().OrderByDescending(x => x.Started); - return View("Tasks", new TasksModel(tasks.ToList())); - } - - public virtual ActionResult Task(int id) - { - var task = TaskRepository.Get(id); - return View("Task", new TaskModel(task)); - } - - #endregion - - #region Import - - public virtual ActionResult BlogMLImport() - { - return View(new BlogMLImportModel()); - } - - [HttpPost] - public virtual ActionResult BlogMLImport(FileUpload upload) - { - if (upload == null || string.IsNullOrWhiteSpace(upload.FileName)) - { - ModelState.AddModelError("File", "Please select a file to upload."); - return View(); - } - - var fullPath = Server.MapPath(SettingsProvider.GetSettings().UploadPath); - fullPath = Path.Combine(fullPath, upload.FileName); - upload.SaveTo(fullPath); - - var id = ImportTask.Execute(new { inputFile = fullPath }); - return RedirectToAction("Task", new { id }); - } - - #endregion - - public virtual ActionResult PageList(EntriesSortColumn? sort, bool? asc) - { - if (sort == null) - sort = EntriesSortColumn.Slug; - - var entries = Repository.Find(new GetEntriesQuery(EntryStatus.All, sort.Value, asc ?? true), 0, 500); - - return View(new PageListModel(entries) { SortAscending = asc.GetValueOrDefault() }); - } - } -} + [Authorize] + [FunnelWebRequest] + [ValidateInput(false)] + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class AdminController : Controller + { + // ReSharper disable UnusedAutoPropertyAccessor.Global + public IFederatedAuthenticationConfigurator FederatedAuthenticationConfigurator { get; set; } + public IAdminRepository AdminRepository { get; set; } + public ISettingsProvider SettingsProvider { get; set; } + public IThemeProvider ThemeProvider { get; set; } + public IRepository Repository { get; set; } + public ITaskStateRepository TaskRepository { get; set; } + public ITaskExecutor ImportTask { get; set; } + public Func> FileRepositoriesInfo { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Global + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Index)] + public virtual ActionResult Index() + { + return View(new IndexModel()); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Settings)] + public virtual ActionResult Settings() + { + var settings = SettingsProvider.GetSettings(); + ViewBag.Themes = ThemeProvider.GetThemes(); + ViewBag.FileRepositories = FileRepositoriesInfo().Keys; + return View(settings); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Settings)] + public virtual ActionResult Settings(FunnelWebSettings settings) + { + ViewBag.Themes = ThemeProvider.GetThemes(); + ViewBag.FileRepositories = FileRepositoriesInfo().Keys; + if (!ModelState.IsValid) + { + ModelState.AddModelError("", "Your settings could not be saved. Please fix the errors shown below."); + return View(settings); + } + + SettingsProvider.SaveSettings(settings); + + return RedirectToAction("Settings", "Admin").AndFlash("Your changes have been saved"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.AcsSettings)] + public virtual ActionResult AcsSettings() + { + var settings = SettingsProvider.GetSettings(); + ViewBag.Themes = ThemeProvider.GetThemes(); + ViewBag.FileRepositories = FileRepositoriesInfo().Keys; + return View(settings); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.AcsSettings)] + public virtual ActionResult AcsSettings(AccessControlServiceSettings acsSettings) + { + ViewBag.Themes = ThemeProvider.GetThemes(); + ViewBag.FileRepositories = FileRepositoriesInfo().Keys; + if (!ModelState.IsValid) + { + ModelState.AddModelError("", "Your settings could not be saved. Please fix the errors shown below."); + return View(acsSettings); + } + + // Special validation that the AudienceUris are indeed Uris. Only save the ones that are. + acsSettings.AudienceUris = string.Join( + Constants.Strings.NewLine, + acsSettings + .AudienceUris + .Split(Constants.Chars.NewLine, Constants.Chars.Space) + .Where(a => { Uri uri; return Uri.TryCreate(a, UriKind.Absolute, out uri); }) + .Select(a => new Uri(a))); + + SettingsProvider.SaveSettings(acsSettings); + + FederatedAuthenticationConfigurator.InitiateFederatedAuthentication(acsSettings); + + return RedirectToAction("AcsSettings").AndFlash("Your changes have been saved"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Comments)] + public virtual ActionResult Comments(int? pageNumber) + { + var page = pageNumber ?? 0; + + var comments = Repository.Find(new GetAllCommentsQuery(), page, 20); + return View(new CommentsModel(page, comments)); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.Comment)] + public virtual ActionResult DeleteComment(int id) + { + var item = Repository.Get(id); + Repository.Remove(item); + AdminRepository.UpdateCommentCountFor(item.Entry.Id); + return RedirectToAction("Comments", "Admin"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.AllSpam)] + public virtual ActionResult DeleteAllSpam() + { + var comments = Repository.Find(new GetSpamQuery()).ToList(); + foreach (var comment in comments) + Repository.Remove(comment); + foreach (var entryToUpdate in comments.Select(c => c.Entry.Id).GroupBy(id => id)) + { + AdminRepository.UpdateCommentCountFor(entryToUpdate.Key); + } + return RedirectToAction("Comments", "Admin"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Spam)] + public virtual ActionResult ToggleSpam(int id) + { + var item = Repository.Get(id); + if (item != null) + { + item.IsSpam = !item.IsSpam; + AdminRepository.UpdateCommentCountFor(item.Entry.Id); + } + return RedirectToAction("Comments", "Admin"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Pingbacks)] + public virtual ActionResult Pingbacks() + { + var pingbacks = Repository.FindAll(); + return View(new PingbacksModel(pingbacks)); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.Pingback)] + public virtual ActionResult DeletePingback(int id) + { + var item = Repository.Get(id); + Repository.Remove(item); + return RedirectToAction("Pingbacks", "Admin"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Pingback)] + public virtual ActionResult TogglePingbackSpam(int id) + { + var item = Repository.Get(id); + if (item != null) + { + item.IsSpam = !item.IsSpam; + } + return RedirectToAction("Pingbacks", "Admin"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Tasks)] + public virtual ActionResult Tasks() + { + var tasks = TaskRepository.GetAll().OrderByDescending(x => x.Started); + return View("Tasks", new TasksModel(tasks.ToList())); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Task)] + public virtual ActionResult Task(int id) + { + var task = TaskRepository.Get(id); + return View("Task", new TaskModel(task)); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.BlogMl)] + public virtual ActionResult BlogMlImport() + { + return View(new BlogMLImportModel()); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.BlogMl)] + public virtual ActionResult BlogMlImport(FileUpload upload) + { + if (upload == null || string.IsNullOrWhiteSpace(upload.FileName)) + { + ModelState.AddModelError("File", "Please select a file to upload."); + return View(); + } + + var fullPath = Server.MapPath(SettingsProvider.GetSettings().UploadPath); + fullPath = Path.Combine(fullPath, upload.FileName); + upload.SaveTo(fullPath); + + var id = ImportTask.Execute(new { inputFile = fullPath }); + return RedirectToAction("Task", new { id }); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Pages)] + public virtual ActionResult PageList(EntriesSortColumn? sort, bool? asc) + { + if (sort == null) + sort = EntriesSortColumn.Slug; + + var entries = Repository.Find(new GetEntriesQuery(EntryStatus.All, sort.Value, asc ?? true), 0, 500); + + return View(new PageListModel(entries) { SortAscending = asc.GetValueOrDefault() }); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/ClaimsController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/ClaimsController.cs new file mode 100644 index 00000000..b2833e35 --- /dev/null +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/ClaimsController.cs @@ -0,0 +1,12 @@ +using System.Web.Mvc; + +namespace FunnelWeb.Web.Areas.Admin.Controllers +{ + public class ClaimsController : Controller + { + public ActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs index b8d93932..e161259b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs @@ -1,131 +1,161 @@ using System; using System.Collections.Generic; +using System.IdentityModel.Services; using System.IO; using System.Linq; +using System.Security.Permissions; using System.Web.Mvc; using DbUp.Engine.Output; +using FunnelWeb.Authentication.Internal; using FunnelWeb.DatabaseDeployer; using FunnelWeb.Providers; using FunnelWeb.Providers.Database; +using FunnelWeb.Settings; using FunnelWeb.Web.Areas.Admin.Views.Install; namespace FunnelWeb.Web.Areas.Admin.Controllers { - [ValidateInput(false)] - [Authorize(Roles = "Admin")] - public class InstallController : Controller - { - private readonly Func> databaseProvidersInfo; - public IApplicationDatabase Database { get; set; } - public IConnectionStringSettings ConnectionStringSettings { get; set; } - public IDatabaseUpgradeDetector UpgradeDetector { get; set; } - public IEnumerable Extensions { get; set; } - - public InstallController(Func> databaseProvidersInfo) - { - this.databaseProvidersInfo = databaseProvidersInfo; - } - - public virtual ActionResult Index() - { - var connectionString = ConnectionStringSettings.ConnectionString; - var schema = ConnectionStringSettings.Schema; - var databaseProviderName = ConnectionStringSettings.DatabaseProvider; - var providerInfo = databaseProvidersInfo(); - var databaseProvider = providerInfo.GetProviderByName(databaseProviderName); - - string error; - var model = new IndexModel - { - DatabaseProviders = providerInfo.Keys, - DatabaseProvider = databaseProviderName, - CanConnect = databaseProvider.TryConnect(connectionString, out error), - ConnectionError = error, - ConnectionString = connectionString, - Schema = databaseProvider.SupportSchema ? schema : null, - DatabaseProviderSupportsSchema = databaseProvider.SupportSchema, - IsSettingsReadOnly = ConnectionStringSettings.ReadOnlyReason != null, - ReadOnlyReason = ConnectionStringSettings.ReadOnlyReason - }; - - if (model.CanConnect) - { - var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); - var required = Database - .GetCoreRequiredScripts(connectionFactory) - .Union(Extensions.SelectMany(x => Database.GetExtensionRequiredScripts(connectionFactory, x))) - .ToArray(); - - var executedAlready = Database - .GetCoreExecutedScripts(connectionFactory) - .Union(Extensions.SelectMany(x => Database.GetExtensionExecutedScripts(connectionFactory, x))) - .ToArray(); - - model.ScriptsToRun = required.Except(executedAlready).ToArray(); - model.IsInstall = executedAlready.Length > 0; - } - - return View("Index", model); - } - - public ActionResult ChangeProvider(string databaseProvider) - { - var provider = databaseProvidersInfo().GetProviderByName(databaseProvider); - - ConnectionStringSettings.ConnectionString = provider.DefaultConnectionString; - ConnectionStringSettings.DatabaseProvider = databaseProvider; - if (!provider.SupportSchema) - ConnectionStringSettings.Schema = null; - UpgradeDetector.Reset(); - - return RedirectToAction("Index"); - } - - [HttpPost] - [ActionName("test")] - public virtual ActionResult Test(string connectionString, string schema) - { - ConnectionStringSettings.ConnectionString = connectionString; - ConnectionStringSettings.Schema = schema; - UpgradeDetector.Reset(); - - return RedirectToAction("Index"); - } - - [HttpPost] - public virtual ActionResult Upgrade() - { - var writer = new StringWriter(); - var log = new TextLog(writer); - var result = Database.PerformUpgrade(Extensions, log); - UpgradeDetector.Reset(); - - return View("UpgradeReport", new UpgradeModel(result, writer.ToString())); - } - - private class TextLog : IUpgradeLog - { - private readonly StringWriter writer; - - public TextLog(StringWriter writer) - { - this.writer = writer; - } - - public void WriteInformation(string format, params object[] args) - { - writer.WriteLine("INFO: " + string.Format(format, args)); - } - - public void WriteError(string format, params object[] args) - { - writer.WriteLine("ERROR: " + string.Format(format, args)); - } - - public void WriteWarning(string format, params object[] args) - { - writer.WriteLine("WARN: " + string.Format(format, args)); - } - } - } + [ValidateInput(false)] + [Authorize] + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class InstallController : Controller + { + private readonly IDatabaseProvider databaseProvider; + private readonly Func> databaseProvidersInfo; + private readonly ISettingsProvider settingsProvider; + private readonly IFederatedAuthenticationService federatedAuthenticationService; + + // ReSharper disable UnusedAutoPropertyAccessor.Global + public IApplicationDatabase Database { get; set; } + public IConnectionStringSettings ConnectionStringSettings { get; set; } + public IDatabaseUpgradeDetector UpgradeDetector { get; set; } + public IEnumerable Extensions { get; set; } + + // ReSharper restore UnusedAutoPropertyAccessor.Global + + //public InstallController(Func> databaseProvidersInfo) + public InstallController(IDatabaseProvider databaseProvider, Func> databaseProvidersInfo, ISettingsProvider settingsProvider, IFederatedAuthenticationService federatedAuthenticationService) + { + this.databaseProvider = databaseProvider; + this.databaseProvidersInfo = databaseProvidersInfo; + this.settingsProvider = settingsProvider; + this.federatedAuthenticationService = federatedAuthenticationService; + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Install.Index)] + public virtual ActionResult Index() + { + var connectionString = ConnectionStringSettings.ConnectionString; + var schema = ConnectionStringSettings.Schema; + var databaseProviderName = ConnectionStringSettings.DatabaseProvider; + + string error; + var model = new IndexModel + { + DatabaseProviders = new[] { "sql", "sqlce" }, + //DatabaseProviders = providerInfo.Keys, + DatabaseProvider = databaseProviderName, + CanConnect = databaseProvider.TryConnect(connectionString, out error), + ConnectionError = error, + ConnectionString = connectionString, + Schema = databaseProvider.SupportSchema ? schema : null, + DatabaseProviderSupportsSchema = databaseProvider.SupportSchema, + IsSettingsReadOnly = ConnectionStringSettings.ReadOnlyReason != null, + ReadOnlyReason = ConnectionStringSettings.ReadOnlyReason + }; + + if (model.CanConnect) + { + var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); + var required = Database + .GetCoreRequiredScripts(connectionFactory) + .Union(Extensions.SelectMany(x => Database.GetExtensionRequiredScripts(connectionFactory, x))) + .ToArray(); + + var executedAlready = Database + .GetCoreExecutedScripts(connectionFactory) + .Union(Extensions.SelectMany(x => Database.GetExtensionExecutedScripts(connectionFactory, x))) + .ToArray(); + + model.ScriptsToRun = required.Except(executedAlready).ToArray(); + model.IsInstall = executedAlready.Length > 0; + } + + return View("Index", model); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Install.ChangeProvider)] + public ActionResult ChangeProvider(string newDatabaseProvider) + { + var provider = databaseProvidersInfo().GetProviderByName(newDatabaseProvider); + + ConnectionStringSettings.ConnectionString = provider.DefaultConnectionString; + ConnectionStringSettings.DatabaseProvider = newDatabaseProvider; + if (!provider.SupportSchema) + { + ConnectionStringSettings.Schema = null; + } + + UpgradeDetector.Reset(); + + return RedirectToAction("Index"); + } + + [HttpPost] + [ActionName("test")] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Install.Test)] + public virtual ActionResult Test(string connectionString, string schema) + { + ConnectionStringSettings.ConnectionString = connectionString; + ConnectionStringSettings.Schema = schema; + UpgradeDetector.Reset(); + + return RedirectToAction("Index"); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Install.Upgrade)] + public virtual ActionResult Upgrade() + { + var writer = new StringWriter(); + var log = new TextLog(writer); + var result = Database.PerformUpgrade(Extensions, log); + UpgradeDetector.Reset(); + + var upgradeModel = new UpgradeModel(result, writer.ToString()); + + if (settingsProvider.GetSettings().SqlAuthenticationEnabled && upgradeModel.Results.All(x => x.Successful)) + { + // We have upgraded and now we must sign back in as as sql user! + federatedAuthenticationService.Logout(); + } + + return View("UpgradeReport", upgradeModel); + } + + private class TextLog : IUpgradeLog + { + private readonly StringWriter writer; + + public TextLog(StringWriter writer) + { + this.writer = writer; + } + + public void WriteInformation(string format, params object[] args) + { + writer.WriteLine("INFO: " + string.Format(format, args)); + } + + public void WriteError(string format, params object[] args) + { + writer.WriteLine("ERROR: " + string.Format(format, args)); + } + + public void WriteWarning(string format, params object[] args) + { + writer.WriteLine("WARN: " + string.Format(format, args)); + } + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs index a02c340c..b4039bf9 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs @@ -1,61 +1,73 @@ using System; using System.Web.Mvc; using FunnelWeb.Authentication; +using FunnelWeb.Authentication.Internal; +using FunnelWeb.DatabaseDeployer; using FunnelWeb.Settings; using FunnelWeb.Web.Areas.Admin.Views.Login; namespace FunnelWeb.Web.Areas.Admin.Controllers { - [ValidateInput(false)] - public class LoginController : Controller - { - readonly Lazy bootstrapSettings; - - public LoginController(Lazy bootstrapSettings) - { - this.bootstrapSettings = bootstrapSettings; - } - - public IAuthenticator Authenticator { get; set; } - - [HttpGet] - public virtual ActionResult Login(LoginModel model) - { - ModelState.Clear(); - - if (model.DatabaseIssue == true) - { - model.ConfigFileMissing = bootstrapSettings.Value.ConfigFileMissing(); - } - - return View(model); - } - - [ActionName("Login")] - [HttpPost] - public virtual ActionResult LoginPost(LoginModel model) - { - if (!ModelState.IsValid) - { - return View(model); - } - - var authenticated = Authenticator.AuthenticateAndLogin(model.Username, model.Password); - if (authenticated) - { - return (model.DatabaseIssue ?? false) - ? (ActionResult)RedirectToAction("Index", "Install") - : Redirect(string.IsNullOrWhiteSpace(model.ReturnUrl) ? "~/" : model.ReturnUrl); - } - - ModelState.AddModelError("", "Invalid username or password. Please try again."); - return View(model); - } - - public virtual ActionResult Logout() - { - Authenticator.Logout(); - return Redirect(Url.Content("~/")); - } - } + [ValidateInput(false)] + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class LoginController : Controller + { + readonly Lazy bootstrapSettings; + private readonly Lazy lazyDatabaseConnectionDetector; + + public IAuthenticator Authenticator { get; set; } + + public LoginController(Lazy bootstrapSettings, Lazy lazyDatabaseConnectionDetector) + { + this.bootstrapSettings = bootstrapSettings; + this.lazyDatabaseConnectionDetector = lazyDatabaseConnectionDetector; + } + + [HttpGet] + public virtual ActionResult Login(LoginModel model) + { + ModelState.Clear(); + + if (model.DatabaseIssue == true) + { + model.ConfigFileMissing = bootstrapSettings.Value.ConfigFileMissing(); + } + + string error; + if (!lazyDatabaseConnectionDetector.Value.CanConnect(out error)) + { + model.DatabaseConnectionIssue = true; + model.DatabaseError = error; + } + + return View(model); + } + + [ActionName("Login")] + [HttpPost] + public virtual ActionResult LoginPost(LoginModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var authenticated = Authenticator.AuthenticateAndLogin(model.Username, model.Password); + if (authenticated) + { + return (model.DatabaseIssue ?? false) + ? (ActionResult)RedirectToAction("Index", "Install") + : Redirect(string.IsNullOrWhiteSpace(model.ReturnUrl) ? "~/" : model.ReturnUrl); + } + + ModelState.AddModelError("", "Invalid username or password. Please try again."); + return View(model); + } + + public virtual ActionResult Logout() + { + Authenticator.Logout(); + return Redirect(Url.Content("~/")); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs index bea66930..1b926c18 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs @@ -1,5 +1,7 @@ using System; +using System.IdentityModel.Services; using System.Linq; +using System.Security.Permissions; using System.Web.Mvc; using FunnelWeb.Authentication.Internal; using FunnelWeb.Filters; @@ -10,185 +12,196 @@ namespace FunnelWeb.Web.Areas.Admin.Controllers { - [FunnelWebRequest] - [Authorize(Roles="Admin")] - public class SqlAuthenticationController : Controller - { - private readonly Func sessionFactory; - private readonly SqlFunnelWebMembership sqlMembership; - private readonly ISettingsProvider settingsProvider; - private readonly SqlRoleProvider sqlRoleProvider; - private readonly FormsAuthenticator formsAuthenticator; - private readonly SqlAuthenticator sqlAuthenticator; - private readonly SqlAuthSettings sqlAuthSettings; - - public SqlAuthenticationController( - Func sessionFactory, - SqlFunnelWebMembership sqlMembership, - ISettingsProvider settingsProvider, - SqlRoleProvider sqlRoleProvider, - FormsAuthenticator formsAuthenticator, - SqlAuthenticator sqlAuthenticator) - { - this.sessionFactory = sessionFactory; - this.sqlMembership = sqlMembership; - this.settingsProvider = settingsProvider; - this.sqlRoleProvider = sqlRoleProvider; - this.formsAuthenticator = formsAuthenticator; - this.sqlAuthenticator = sqlAuthenticator; - sqlAuthSettings = this.settingsProvider.GetSettings(); - } - - public ActionResult Index() - { - var users = sqlAuthSettings.SqlAuthenticationEnabled ? sqlMembership.GetUsers() : Enumerable.Empty(); - - var indexModel = new IndexModel - { - IsUsingSqlAuthentication = sqlAuthSettings.SqlAuthenticationEnabled, - Users = users - }; - return View(indexModel); - } - - public ActionResult EnableSqlAuthentication() - { - if (!sqlMembership.HasAdminAccount()) - { - return RedirectToAction("Setup"); - } - - sqlAuthSettings.SqlAuthenticationEnabled = true; - settingsProvider.SaveSettings(sqlAuthSettings); - - formsAuthenticator.Logout(); - return RedirectToAction("Index"); - } - - public ActionResult DisableSqlAuthentication() - { - if (!sqlMembership.HasAdminAccount()) - { - return RedirectToAction("Setup"); - } - - sqlAuthSettings.SqlAuthenticationEnabled = false; - settingsProvider.SaveSettings(sqlAuthSettings); - sqlAuthenticator.Logout(); - return RedirectToAction("Index"); - } - - [HttpGet] - public ActionResult NewAccount() - { - return View(new NewUser()); - } - - [HttpPost] - public ActionResult NewAccount(NewUser user) - { - if (user.Password != user.RepeatPassword) - { - ModelState.AddModelError("Password", "Passwords must match"); - ModelState.AddModelError("RepeatPassword", "Passwords must match"); - return RedirectToAction("Setup", new { user }); - } - - sqlMembership.CreateAccount(user.Name, user.Email, user.Username, user.Password); - - return RedirectToAction("Index"); - } - - [HttpGet] - public ActionResult Setup() - { - var setupModel = new SetupModel - { - HasAdminAccount = sqlMembership.HasAdminAccount() - }; - - return View(setupModel); - } - - [HttpPost] - public ActionResult Setup(SetupModel setupModel) - { - return View(setupModel); - } - - public ActionResult CreateAdminAccount(SetupModel setupModel) - { - if (!sqlMembership.HasAdminAccount()) - { - if (setupModel.Password != setupModel.RepeatPassword) - { - ModelState.AddModelError("Password", "Passwords must match"); - ModelState.AddModelError("RepeatPassword", "Passwords must match"); - return RedirectToAction("Setup", new{setupModel}); - } - - var user = sqlMembership.CreateAccount(setupModel.Name, setupModel.Email, setupModel.Username, setupModel.Password); - sqlRoleProvider.AddUserToRoles(user, "Admin", "Moderator"); - } - - sqlAuthSettings.SqlAuthenticationEnabled = true; - settingsProvider.SaveSettings(sqlAuthSettings); - - formsAuthenticator.Logout(); - return RedirectToAction("Index"); - } - - public ActionResult RemoveRole(int userId, int roleId) - { - var user = sessionFactory().Get(userId); - - var role = user.Roles.SingleOrDefault(r => r.Id == roleId); - - if (role == null) - return RedirectToAction("Index"); - - user.Roles.Remove(role); - role.Users.Remove(user); - - return RedirectToAction("Index"); - } - - public ActionResult AddRole(int userId) - { - var session = sessionFactory(); - var user = session.QueryOver() - .Where(u => u.Id == userId) - .Left.JoinQueryOver(u => u.Roles) - .SingleOrDefault(); - - var roles = session - .QueryOver() - .List() - .Except(user.Roles) - .ToList(); - - return View(new AddRoleModel { User = user, Roles = roles }); - } - - public ActionResult AddUserToRole(int userId, int roleId) - { - var session = sessionFactory(); - - var user = session.QueryOver() - .Where(u => u.Id == userId) - .Left.JoinQueryOver(u => u.Roles) - .SingleOrDefault(); - - if (user.Roles.SingleOrDefault(r => r.Id == roleId) == null) - { - var role = session - .Get(roleId); - user.Roles.Add(role); - role.Users.Add(user); - } - - - return RedirectToAction("AddRole", new { userId }); - } - } -} + [FunnelWebRequest] + [Authorize] + public class SqlAuthenticationController : Controller + { + private readonly Func sessionFactory; + private readonly SqlFunnelWebMembership sqlMembership; + private readonly ISettingsProvider settingsProvider; + private readonly ClaimsRoleProvider claimsRoleProvider; + private readonly FormsAuthenticator formsAuthenticator; + private readonly SqlClaimsAuthenticator sqlClaimsAuthenticator; + private readonly SqlAuthSettings sqlAuthSettings; + + public SqlAuthenticationController( + Func sessionFactory, + SqlFunnelWebMembership sqlMembership, + ISettingsProvider settingsProvider, + ClaimsRoleProvider claimsRoleProvider, + FormsAuthenticator formsAuthenticator, + SqlClaimsAuthenticator sqlClaimsAuthenticator) + { + this.sessionFactory = sessionFactory; + this.sqlMembership = sqlMembership; + this.settingsProvider = settingsProvider; + this.claimsRoleProvider = claimsRoleProvider; + this.formsAuthenticator = formsAuthenticator; + this.sqlClaimsAuthenticator = sqlClaimsAuthenticator; + sqlAuthSettings = this.settingsProvider.GetSettings(); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] + public ActionResult Index() + { + var users = sqlAuthSettings.SqlAuthenticationEnabled ? sqlMembership.GetUsers() : Enumerable.Empty(); + + var indexModel = new IndexModel + { + IsUsingSqlAuthentication = sqlAuthSettings.SqlAuthenticationEnabled, + Users = users + }; + return View(indexModel); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] + public ActionResult EnableSqlAuthentication() + { + if (!sqlMembership.HasAdminAccount()) + { + return RedirectToAction("Setup"); + } + + sqlAuthSettings.SqlAuthenticationEnabled = true; + settingsProvider.SaveSettings(sqlAuthSettings); + + formsAuthenticator.Logout(); + return RedirectToAction("Index"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] + public ActionResult DisableSqlAuthentication() + { + if (!sqlMembership.HasAdminAccount()) + { + return RedirectToAction("Setup"); + } + + sqlAuthSettings.SqlAuthenticationEnabled = false; + settingsProvider.SaveSettings(sqlAuthSettings); + sqlClaimsAuthenticator.Logout(); + return RedirectToAction("Index"); + } + + [HttpGet] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.NewAccount)] + public ActionResult NewAccount() + { + return View(new NewUser()); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.NewAccount)] + public ActionResult NewAccount(NewUser user) + { + if (user.Password != user.RepeatPassword) + { + ModelState.AddModelError("Password", "Passwords must match"); + ModelState.AddModelError("RepeatPassword", "Passwords must match"); + return RedirectToAction("Setup", new { user }); + } + + sqlMembership.CreateAccount(user.Name, user.Email, user.Username, user.Password); + + return RedirectToAction("Index"); + } + + [HttpGet] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.Setup)] + public ActionResult Setup() + { + var setupModel = new SetupModel + { + HasAdminAccount = sqlMembership.HasAdminAccount() + }; + + return View(setupModel); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.Setup)] + public ActionResult Setup(SetupModel setupModel) + { + return View(setupModel); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Insert, Resource = Authorization.Resources.SqlAuthentications.AdminAccount)] + public ActionResult CreateAdminAccount(SetupModel setupModel) + { + if (!sqlMembership.HasAdminAccount()) + { + if (setupModel.Password != setupModel.RepeatPassword) + { + ModelState.AddModelError("Password", "Passwords must match"); + ModelState.AddModelError("RepeatPassword", "Passwords must match"); + return RedirectToAction("Setup", new { setupModel }); + } + + var user = sqlMembership.CreateAccount(setupModel.Name, setupModel.Email, setupModel.Username, setupModel.Password); + claimsRoleProvider.AddUserToRoles(user, Authorization.Roles.Admin, Authorization.Roles.Moderator, Authorization.Roles.Guest); + } + + sqlAuthSettings.SqlAuthenticationEnabled = true; + settingsProvider.SaveSettings(sqlAuthSettings); + + formsAuthenticator.Logout(); + return RedirectToAction("Index"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.SqlAuthentications.RemoveRole)] + public ActionResult RemoveRole(int userId, int roleId) + { + var user = sessionFactory().Get(userId); + + var role = user.Roles.SingleOrDefault(r => r.Id == roleId); + + if (role == null) + return RedirectToAction("Index"); + + user.Roles.Remove(role); + role.Users.Remove(user); + + return RedirectToAction("Index"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.RoleAdd)] + public ActionResult AddRole(int userId) + { + var session = sessionFactory(); + var user = session.QueryOver() + .Where(u => u.Id == userId) + .Left.JoinQueryOver(u => u.Roles) + .SingleOrDefault(); + + var roles = session + .QueryOver() + .List() + .Except(user.Roles) + .ToList(); + + return View(new AddRoleModel { User = user, Roles = roles }); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.RoleAdd)] + public ActionResult AddUserToRole(int userId, int roleId) + { + var session = sessionFactory(); + + var user = session.QueryOver() + .Where(u => u.Id == userId) + .Left.JoinQueryOver(u => u.Roles) + .SingleOrDefault(); + + if (user.Roles.SingleOrDefault(r => r.Id == roleId) == null) + { + var role = session + .Get(roleId); + user.Roles.Add(role); + role.Users.Add(user); + } + + + return RedirectToAction("AddRole", new { userId }); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs index 18c35449..0ccbbb5b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs @@ -1,68 +1,70 @@ -using System.IO; +using System.IdentityModel.Services; +using System.IO; +using System.Security.Permissions; using System.Web.Mvc; +using FunnelWeb.Authentication.Internal; using FunnelWeb.Providers.File; using FunnelWeb.Settings; -using FunnelWeb.Utilities; using FunnelWeb.Web.Application.Mvc; using FunnelWeb.Web.Areas.Admin.Views.Upload; namespace FunnelWeb.Web.Areas.Admin.Controllers { - [ValidateInput(false)] - public class UploadController : Controller - { - public IFileRepository FileRepository { get; set; } - public IMimeTypeLookup MimeHelper { get; set; } - public ISettingsProvider SettingsProvider { get; set; } + [ValidateInput(false)] + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class UploadController : Controller + { + public IFileRepository FileRepository { get; set; } + public ISettingsProvider SettingsProvider { get; set; } - [Authorize(Roles = "Moderator")] - public virtual ActionResult Index(string path) - { - path = path ?? string.Empty; - if (FileRepository.IsFile(path)) - { - return RedirectToAction("Index", "Upload", new {path = Path.GetDirectoryName(path)}); - } + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Upload.Index)] + public virtual ActionResult Index(string path) + { + path = path ?? string.Empty; + if (FileRepository.IsFile(path)) + { + return RedirectToAction("Index", "Upload", new { path = Path.GetDirectoryName(path) }); + } - ViewData.Model = new IndexModel(path, FileRepository.GetItems(path)) - { - StorageProvider = SettingsProvider.GetSettings().StorageProvider - }; - return View(); - } + ViewData.Model = new IndexModel(path, FileRepository.GetItems(path)) + { + StorageProvider = SettingsProvider.GetSettings().StorageProvider + }; + return View(); + } - [Authorize(Roles = "Moderator")] - public virtual ActionResult Upload(string path, bool? unzip, FileUpload upload) - { - var filePath = Path.Combine(path, upload.FileName); - FileRepository.Save(upload.Stream, filePath, unzip ?? false); - return RedirectToAction("Index", "Upload", new {path}); - } + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Upload.Index)] + public virtual ActionResult Upload(string path, bool? unzip, FileUpload upload) + { + var filePath = Path.Combine(path, upload.FileName); + FileRepository.Save(upload.Stream, filePath, unzip ?? false); + return RedirectToAction("Index", "Upload", new { path }); + } - [Authorize(Roles = "Moderator")] - public virtual ActionResult CreateDirectory(string path, string name) - { - FileRepository.CreateDirectory(path, name); - return RedirectToAction("Index", "Upload", new {path}); - } + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Upload.CreateDirectory)] + public virtual ActionResult CreateDirectory(string path, string name) + { + FileRepository.CreateDirectory(path, name); + return RedirectToAction("Index", "Upload", new { path }); + } - [Authorize(Roles = "Moderator")] - public virtual ActionResult Move(string path, string oldPath, string newPath) - { - FileRepository.Move(oldPath, newPath); - return RedirectToAction("Index", "Upload", new {path}); - } + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Upload.Move)] + public virtual ActionResult Move(string path, string oldPath, string newPath) + { + FileRepository.Move(oldPath, newPath); + return RedirectToAction("Index", "Upload", new { path }); + } - [Authorize(Roles = "Moderator")] - public virtual ActionResult Delete(string path, string filePath) - { - FileRepository.Delete(filePath); - return RedirectToAction("Index", "Upload", new {path}); - } + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Upload.Delete)] + public virtual ActionResult Delete(string path, string filePath) + { + FileRepository.Delete(filePath); + return RedirectToAction("Index", "Upload", new { path }); + } - public virtual ActionResult Render(string path) - { - return FileRepository.Render(path); - } - } + public virtual ActionResult Render(string path) + { + return FileRepository.Render(path); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs index 29749a00..c93f1e38 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs @@ -1,164 +1,169 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IdentityModel.Services; using System.Linq; +using System.Security.Permissions; using System.Web.Mvc; using FunnelWeb.Authentication; +using FunnelWeb.Authentication.Internal; using FunnelWeb.Eventing; using FunnelWeb.Filters; using FunnelWeb.Model; using FunnelWeb.Model.Strings; using FunnelWeb.Repositories; using FunnelWeb.Repositories.Queries; -using FunnelWeb.Settings; using FunnelWeb.Utilities; using FunnelWeb.Web.Application.Mvc; -using FunnelWeb.Web.Application.Spam; namespace FunnelWeb.Web.Areas.Admin.Controllers { - [FunnelWebRequest] - [HandleError] - [ValidateInput(false)] - public class WikiAdminController : Controller - { - public IAuthenticator Authenticator { get; set; } - public IRepository Repository { get; set; } - public ISpamChecker SpamChecker { get; set; } - public IEventPublisher EventPublisher { get; set; } - public ISettingsProvider SettingsProvider { get; set; } - - [Authorize(Roles = "Moderator")] - public virtual ActionResult Edit(PageName page, int? revertToRevision) - { - var allTags = Repository.FindAll(); - - var entry = - revertToRevision == null - ? Repository.FindFirstOrDefault(new EntryByNameQuery(page)) - : Repository.FindFirstOrDefault(new EntryByNameAndRevisionQuery(page, revertToRevision.Value)); - - if (entry == null) - { - entry = new EntryRevision - { - Title = "New post", - MetaTitle = "New post", - Name = page - }; - } - - entry.ChangeSummary = entry.Id == 0 - ? "Initial create" - : (revertToRevision == null ? "" : "Reverted to version " + revertToRevision); - entry.AllTags = allTags; - - if (revertToRevision != null) - { - return View("Edit", entry).AndFlash("You are editing an old version of this page. This will become the current version when you save."); - } - return View("Edit", entry); - } - - [HttpPost] - [Authorize(Roles = "Moderator")] - public virtual ActionResult Edit(EntryRevision model) - { - model.AllTags = Repository.FindAll(); - - if (!ModelState.IsValid) - { - model.SelectedTags = GetEditTags(model); - return View(model); - } - - var author = Authenticator.GetName(); - var entry = Repository.Get(model.Id); - if (entry == null && CurrentEntryExistsWithName(model.Name)) - { - model.SelectedTags = GetEditTags(model); - ModelState.AddModelError("PageExists", string.Format("A page with SLUG '{0}' already exists. You should edit that page instead", model.Name)); - return View(model); - } - - if (entry == null && CurrentEntryExistsWithName(model.Title) && model.Name == "") - { - model.SelectedTags = GetEditTags(model); - ModelState.AddModelError("PageExists", string.Format("A page with SLUG '{0}' already exists. Please add a unique SLUG here.", model.Title)); - return View(model); - } - - entry = entry ?? new Entry {Author = author}; - entry.Name = string.IsNullOrWhiteSpace(model.Name) ? model.Title.Slugify() : model.Name.ToString(); - entry.PageTemplate = string.IsNullOrEmpty(model.PageTemplate) ? null : model.PageTemplate; - entry.Title = model.Title ?? string.Empty; - entry.Summary = model.Summary ?? string.Empty; - entry.MetaTitle = string.IsNullOrWhiteSpace(model.MetaTitle) ? model.Title : model.MetaTitle; - entry.IsDiscussionEnabled = !model.DisableComments; - entry.MetaDescription = model.MetaDescription ?? string.Empty; - entry.HideChrome = model.HideChrome; - - //Only change the publish date if the dates no longer match, this means that - //time changes wont be tracked. - var published = DateTime.Parse(model.PublishDate + " " + DateTime.Now.ToShortTimeString(), CultureInfo.CurrentCulture).ToUniversalTime(); - if(entry.Published.Date != published.Date) - entry.Published = published; - - entry.Status = model.Status; - - var revision = entry.Revise(); - revision.Author = author; - revision.Body = model.Body; - revision.Reason = model.ChangeSummary ?? string.Empty; - revision.Format = model.Format; - - var editTags = GetEditTags(model); - var toDelete = entry.Tags.Where(t => !editTags.Contains(t)).ToList(); - var toAdd = editTags.Where(t => !entry.Tags.Contains(t)).ToList(); - - foreach (var tag in toDelete) - tag.Remove(entry); - foreach (var tag in toAdd) - { - if (tag.Id == 0) - Repository.Add(tag); - tag.Add(entry); - } - - EventPublisher.Publish(new EntrySavedEvent(entry)); - - if (model.IsNew) - { - Repository.Add(entry); - } - - return RedirectToAction("Page", "Wiki", new { Area = "", page = entry.Name}); - } - - private bool CurrentEntryExistsWithName(string name) - { - return Repository.FindFirstOrDefault(new EntryByNameQuery(name)) != null; - } - - private List GetEditTags(EntryRevision model) - { - var tagList = new List(); - foreach (var tagName in model.TagsCommaSeparated.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(s => s != "0")) - { - int id; - var tag = int.TryParse(tagName, out id) ? Repository.Get(id) : new Tag {Name = tagName}; - tagList.Add(tag); - } - - return tagList; - } - - [Authorize(Roles = "Moderator")] - [HttpPost] - public virtual ActionResult DeletePage(int id) - { - Repository.Remove(Repository.Get(id)); - return RedirectToAction("PageList", "Admin"); - } - } -} + [FunnelWebRequest] + [HandleError] + [ValidateInput(false)] + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class WikiAdminController : Controller + { + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public IAuthenticator Authenticator { get; set; } + public IRepository Repository { get; set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public IEventPublisher EventPublisher { get; set; } + + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.WikiAdmin.Edit)] + public virtual ActionResult Edit(PageName page, int? revertToRevision) + { + var allTags = Repository.FindAll(); + + var entry = + revertToRevision == null + ? Repository.FindFirstOrDefault(new EntryByNameQuery(page)) + : Repository.FindFirstOrDefault(new EntryByNameAndRevisionQuery(page, revertToRevision.Value)); + + if (entry == null) + { + entry = new EntryRevision + { + Title = "New post", + MetaTitle = "New post", + Name = page + }; + } + + entry.ChangeSummary = + entry.Id == 0 ? + "Initial create" : + (revertToRevision == null ? "" : "Reverted to version " + revertToRevision); + + entry.AllTags = allTags; + + if (revertToRevision != null) + { + return View("Edit", entry).AndFlash("You are editing an old version of this page. This will become the current version when you save."); + } + + return View("Edit", entry); + } + + [HttpPost] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.WikiAdmin.Edit)] + public virtual ActionResult Edit(EntryRevision model) + { + model.AllTags = Repository.FindAll(); + + if (!ModelState.IsValid) + { + model.SelectedTags = GetEditTags(model); + return View(model); + } + + var author = Authenticator.GetName(); + var entry = Repository.Get(model.Id); + if (entry == null && CurrentEntryExistsWithName(model.Name)) + { + model.SelectedTags = GetEditTags(model); + ModelState.AddModelError("PageExists", string.Format("A page with SLUG '{0}' already exists. You should edit that page instead", model.Name)); + return View(model); + } + + if (entry == null && CurrentEntryExistsWithName(model.Title) && model.Name == "") + { + model.SelectedTags = GetEditTags(model); + ModelState.AddModelError("PageExists", string.Format("A page with SLUG '{0}' already exists. Please add a unique SLUG here.", model.Title)); + return View(model); + } + + entry = entry ?? new Entry { Author = author }; + entry.Name = string.IsNullOrWhiteSpace(model.Name) ? model.Title.Slugify() : model.Name.ToString(); + entry.PageTemplate = string.IsNullOrEmpty(model.PageTemplate) ? null : model.PageTemplate; + entry.Title = model.Title ?? string.Empty; + entry.Summary = model.Summary ?? string.Empty; + entry.MetaTitle = string.IsNullOrWhiteSpace(model.MetaTitle) ? model.Title : model.MetaTitle; + entry.IsDiscussionEnabled = !model.DisableComments; + entry.MetaDescription = model.MetaDescription ?? string.Empty; + entry.HideChrome = model.HideChrome; + + //Only change the publish date if the dates no longer match, this means that + //time changes wont be tracked. + var published = DateTime.Parse(model.PublishDate + " " + DateTime.Now.ToShortTimeString(), CultureInfo.CurrentCulture).ToUniversalTime(); + if (entry.Published.Date != published.Date) + entry.Published = published; + + entry.Status = model.Status; + + var revision = entry.Revise(); + revision.Author = author; + revision.Body = model.Body; + revision.Reason = model.ChangeSummary ?? string.Empty; + revision.Format = model.Format; + + var editTags = GetEditTags(model); + var toDelete = entry.Tags.Where(t => !editTags.Contains(t)).ToList(); + var toAdd = editTags.Where(t => !entry.Tags.Contains(t)).ToList(); + + foreach (var tag in toDelete) + tag.Remove(entry); + foreach (var tag in toAdd) + { + if (tag.Id == 0) + Repository.Add(tag); + tag.Add(entry); + } + + EventPublisher.Publish(new EntrySavedEvent(entry)); + + if (model.IsNew) + { + Repository.Add(entry); + } + + return RedirectToAction("Page", "Wiki", new { Area = "", page = entry.Name }); + } + + private bool CurrentEntryExistsWithName(string name) + { + return Repository.FindFirstOrDefault(new EntryByNameQuery(name)) != null; + } + + private List GetEditTags(EntryRevision model) + { + var tagList = new List(); + foreach (var tagName in model.TagsCommaSeparated.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(s => s != "0")) + { + int id; + var tag = int.TryParse(tagName, out id) ? Repository.Get(id) : new Tag { Name = tagName }; + tagList.Add(tag); + } + + return tagList; + } + + [HttpPost] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.WikiAdmin.Page)] + public virtual ActionResult DeletePage(int id) + { + Repository.Remove(Repository.Get(id)); + return RedirectToAction("PageList", "Admin"); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs index bb1f8a8f..1978f0c4 100644 --- a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs +++ b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs @@ -1,67 +1,72 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; +using FunnelWeb.Web.Areas.Admin.Controllers; namespace FunnelWeb.Web.Areas.Admin { - internal static class RouteConfig - { - internal static void RegisterRoutes(AreaRegistrationContext context) - { - context.MapRoute( - "Admin_Logout", - "admin/logout", - new { controller = "Login", action = "Logout" } - ); + internal static class RouteConfig + { + internal static void RegisterRoutes(AreaRegistrationContext context) + { + context.MapRoute( + "Admin_Logout", + "admin/logout", + new { controller = "Login", action = "Logout" }, + new[] { typeof(LoginController).Namespace } + ); - context.MapRoute( - "Admin_Login", - "admin/login/{action}", - new { controller = "Login", action = "Login" } - ); + context.MapRoute( + "Admin_Login", + "admin/login/{action}", + new { controller = "Login", action = "Login" }, + new[] { typeof(LoginController).Namespace } + ); - context.MapRoute( - "Admin_Install", - "admin/install/{action}", - new { controller = "Install", action = "Index" } - ); + context.MapRoute( + "Admin_Install", + "admin/install/{action}", + new { controller = "Install", action = "Index" } + ); - context.MapRoute( - "Admin_SqlAuthentication", - "admin/sqlauthentication/{action}", - new { controller = "SqlAuthentication", action = "Index" } - ); + context.MapRoute( + "Admin_SqlAuthentication", + "admin/sqlauthentication/{action}", + new { controller = "SqlAuthentication", action = "Index" } + ); - context.MapRoute("Admin_Get", "get/{*path}", new { controller = "Upload", action = "Render" }); + context.MapRoute("Admin_Get", "get/{*path}", new { controller = "Upload", action = "Render" }); - //need to do this route explicitly otherwise we can't handle nice URLs when directory browsing - context.MapRoute( - "Admin_Upload", - "upload/{action}/{*path}", - new { controller = "Upload", action = "Index", path = "/" } - ); + //need to do this route explicitly otherwise we can't handle nice URLs when directory browsing + context.MapRoute( + "Admin_Upload", + "upload/{action}/{*path}", + new { controller = "Upload", action = "Index", path = "/" } + ); - //an explicit route to the edit page so that it can be done well - context.MapRoute( - "Admin_Wiki_Edit", - "admin/{controller}/edit/{*page}", - new { controller = "WikiAdmin", action = "Edit", page = UrlParameter.Optional } - ); + //an explicit route to the edit page so that it can be done well + context.MapRoute( + "Admin_Wiki_Edit", + "admin/{controller}/edit/{*page}", + new { controller = "WikiAdmin", action = "Edit", page = UrlParameter.Optional } + ); - context.MapRoute( - "Admin_Wiki_Delete", - "admin/delete/{id}", - new { controller = "WikiAdmin", action = "DeletePage" } - ); + context.MapRoute( + "Admin_Wiki_Delete", + "admin/delete/{id}", + new { controller = "WikiAdmin", action = "DeletePage" } + ); - //anything else we expect is on the admin controller, so just route to it - context.MapRoute( - "Admin_default", - "admin/{action}/{id}", - new { controller = "Admin", action = "Index", id = UrlParameter.Optional } - ); - } - } + context.MapRoute( + "Claims_List", + "admin/claims", + new { controller = "Claims", action = "Index" } + ); + + //anything else we expect is on the admin controller, so just route to it + context.MapRoute( + "Admin_default", + "admin/{action}/{id}", + new { controller = "Admin", action = "Index", id = UrlParameter.Optional } + ); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml new file mode 100644 index 00000000..93ff4e67 --- /dev/null +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml @@ -0,0 +1,67 @@ +@model FunnelWeb.Settings.AccessControlServiceSettings +@{ + ViewBag.Title = "FunnelWeb Administration - Settings"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; +} + +

Settings

+ +@using (Html.BeginForm("AcsSettings", "Admin", FormMethod.Post, new { Area = "Admin", @class = "promptBeforeUnload" })) +{ +
+

Access Control Service configuration

+ +
+ @Html.LabelFor(m => m.Enabled) +
+
+ @Html.CheckBoxFor(m => m.Enabled) + @Html.ValidationMessageFor(m => m.Enabled) + @Html.HintFor(m => m.Enabled) +
+ +
+ @Html.LabelFor(m => m.RequireAuthenticationForComments) +
+
+ @Html.CheckBoxFor(m => m.RequireAuthenticationForComments) + @Html.ValidationMessageFor(m => m.RequireAuthenticationForComments) + @Html.HintFor(m => m.RequireAuthenticationForComments) +
+ +
+ @Html.LabelFor(m => m.Namespace) +
+
+ @Html.TextBoxFor(m => m.Namespace) + @Html.ValidationMessageFor(m => m.Namespace) + @Html.HintFor(m => m.Namespace) +
+
+ @Html.TextBoxFor(m => m.Realm) + @Html.ValidationMessageFor(m => m.Realm) + @Html.HintFor(m => m.Realm) +
+
+ @Html.TextAreaFor(m => m.AudienceUris, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.AudienceUris) + @Html.HintFor(m => m.AudienceUris) +
+ +
+ @Html.LabelFor(m => m.IssuerThumbprint) +
+
+ @Html.TextBoxFor(m => m.IssuerThumbprint) + @Html.ValidationMessageFor(m => m.IssuerThumbprint) + @Html.HintFor(m => m.IssuerThumbprint) +
+
+ @Html.LabelFor(m => m.Realm) +
+ +
+ +
+
+} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/BlogMLImport.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/BlogMLImport.cshtml index e1307118..8a4b43e0 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/BlogMLImport.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/BlogMLImport.cshtml @@ -14,7 +14,7 @@ @Html.ValidationSummary("Please correct the errors below.") -@using (Html.BeginForm("BlogMLImport", "Admin", FormMethod.Post, new { Area = "Admin", @class = "promptBeforeUnload", enctype = "multipart/form-data" })) +@using (Html.BeginForm("BlogMlImport", "Admin", FormMethod.Post, new { Area = "Admin", @class = "promptBeforeUnload", enctype = "multipart/form-data" })) {
diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Comments.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Comments.cshtml index cdb98aa2..5482e5c6 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Comments.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Comments.cshtml @@ -1,47 +1,61 @@ @model FunnelWeb.Web.Areas.Admin.Views.Admin.CommentsModel @{ - ViewBag.Title = "FunnelWeb Administration - Comments"; - Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; + ViewBag.Title = "FunnelWeb Administration - Comments"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; } +

Comments

@if (Html.Settings().EnableDisqusCommenting) { -

Disqus comments are enabled. The comments listed here are only those stored in the FunnelWeb database. For your Disqus comments you need to log into Disqus.

+

Disqus comments are enabled. The comments listed here are only those stored in the FunnelWeb database. For your Disqus comments you need to log into Disqus.

}

- FunnelWeb comments are automatically sent to Akismet for spam filtering. - You can also add your own blacklist of naughty words considered as spam on the @Html.AdminActionLink("general settings", "Settings", "Admin") page. + FunnelWeb comments are automatically sent to Akismet for spam filtering. + You can also add your own blacklist of naughty words considered as spam on the @Html.AdminActionLink("general settings", "Settings", "Admin") page.

- @Html.AdminActionLink("Delete all Spam", "DeleteAllSpam", "Admin") + @Html.AdminActionLink("Delete all Spam", "DeleteAllSpam", "Admin")

-@foreach (var comment in Model.Comments) { -
- - - - - - - - -
- @comment.Entry.Title
- @Html.RenderUntrusted(comment.Body, Formats.Markdown) -
- Author: @comment.AuthorName
- URL: @comment.AuthorUrl
- Email: @comment.AuthorEmail -
- @Html.ActionLink("Delete", "DeleteComment", "Admin", new { Area = "Admin", id = comment.Id }, null) - @Html.ActionLink(comment.IsSpam ? "Unspam" : "Spam", "ToggleSpam", "Admin", new { Area = "Admin", id = comment.Id }, null) -
-
+@foreach (var comment in Model.Comments) +{ +
+ + + + + + + + +
+ @comment.Entry.Title
+ @Html.RenderUntrusted(comment.Body, Formats.Markdown) +
+ Author: @comment.AuthorName +
+ URL: @comment.AuthorUrl +
+ Email: @comment.AuthorEmail +
+ @Html.ActionLink("Delete", "DeleteComment", "Admin", new { Area = "Admin", id = comment.Id }, null) + @Html.ActionLink(comment.IsSpam ? "Unspam" : "Spam", "ToggleSpam", "Admin", new { Area = "Admin", id = comment.Id }, null) +
+
} @Html.DisplayFor(x => x.Paginator) diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml index ade1293a..c19af99b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml @@ -56,7 +56,7 @@
+ diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Pingbacks.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Pingbacks.cshtml index faa46868..553e3e2c 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Pingbacks.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Pingbacks.cshtml @@ -1,23 +1,34 @@ @model FunnelWeb.Web.Areas.Admin.Views.Admin.PingbacksModel @{ - ViewBag.Title = "FunnelWeb Administration - Pingbacks"; - Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; + ViewBag.Title = "FunnelWeb Administration - Pingbacks"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; } +

Pingbacks

- + @foreach (var pingback in Model.Pingbacks) { - +
- - + +
- @Html.Encode(pingback.Entry.Title)
- @Html.Encode(pingback.TargetTitle) | - @Html.Encode(pingback.TargetUri) -
- @Html.ActionLink("Delete", "DeletePingback", "Admin", new { Area = "Admin", pingback.Id }, null) - @Html.ActionLink(pingback.IsSpam ? "Unspam" : "Spam", "TogglePingbackSpam", "Admin", new { Area = "Admin", pingback.Id }, null) - + @Html.Encode(pingback.Entry.Title)
+ @Html.Encode(pingback.TargetTitle) | + @Html.Encode(pingback.TargetUri) +
+ @Html.ActionLink("Delete", "DeletePingback", "Admin", new { Area = "Admin", pingback.Id }, null) + @Html.ActionLink(pingback.IsSpam ? "Unspam" : "Spam", "TogglePingbackSpam", "Admin", new { Area = "Admin", pingback.Id }, null) +
} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml index a0a1e05d..6358e52d 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml @@ -1,345 +1,355 @@ @using FunnelWeb.Providers.File @model FunnelWeb.Settings.FunnelWebSettings @{ - ViewBag.Title = "FunnelWeb Administration - Settings"; - Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; + ViewBag.Title = "FunnelWeb Administration - Settings"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; }

Settings

-@using (Html.BeginForm("Settings", "Admin", FormMethod.Post, new {Area = "Admin", @class = "promptBeforeUnload"})) +@using (Html.BeginForm("Settings", "Admin", FormMethod.Post, new { Area = "Admin", @class = "promptBeforeUnload" })) { -
-

Site Information

- -
- @Html.LabelFor(m => m.SiteTitle) -
-
- @Html.TextBoxFor(m => m.SiteTitle, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.SiteTitle) - @Html.HintFor(m => m.SiteTitle) -
- -
- @Html.LabelFor(m => m.Introduction) -
-
- @Html.TextAreaFor(m => m.Introduction, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.Introduction) - @Html.HintFor(m => m.Introduction) -
- -

Home Page

- -
- @Html.LabelFor(m => m.CustomHomePage) -
-
- @Html.TextBoxFor(m => m.CustomHomePage) - @Html.ValidationMessageFor(m => m.CustomHomePage) - @Html.HintFor(m => m.CustomHomePage) -
- -

Appearance

- -
- @Html.LabelFor(m => m.MainLinks) -
-
- @Html.TextAreaFor(m => m.MainLinks, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.MainLinks) - @Html.HintFor(m => m.MainLinks) -
- -
- @Html.LabelFor(m => m.Footer) -
-
- @Html.TextAreaFor(m => m.Footer, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.Footer) - @Html.HintFor(m => m.Footer) -
- -
- @Html.LabelFor(m => m.Theme) -
-
- @Html.DropDownList("Theme", ((string[]) ViewBag.Themes).Select(x => new SelectListItem {Text = x, Selected = Model.Theme == x})) - @Html.ValidationMessageFor(m => m.Theme) - @Html.HintFor(m => m.Theme) -
- -

Metadata

- -
- @Html.LabelFor(m => m.Author) -
-
- @Html.TextBoxFor(m => m.Author, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.Author) - @Html.HintFor(m => m.Author) -
- -
- @Html.LabelFor(m => m.SearchKeywords) -
-
- @Html.TextBoxFor(m => m.SearchKeywords, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.SearchKeywords) - @Html.HintFor(m => m.SearchKeywords) -
- -
- @Html.LabelFor(m => m.SearchDescription) -
-
- @Html.TextBoxFor(m => m.SearchDescription, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.SearchDescription) - @Html.HintFor(m => m.SearchDescription) -
- -

File Uploads

-
- @Html.LabelFor(m => m.StorageProvider) -
-
- @{ - var storageProviders = (IEnumerable) ViewBag.FileRepositories; - } - @Html.DropDownListFor( - m => m.StorageProvider, - storageProviders.Select(provider => new SelectListItem - { - Text = provider, - Value = provider, - Selected = provider == Model.StorageProvider - }), - new { id = "storageProviders"}) - @Html.ValidationMessageFor(m => m.StorageProvider) - @Html.HintFor(m => m.StorageProvider) -
-
-
- @Html.LabelFor(m => m.UploadPath) -
-
- @Html.TextBoxFor(m => m.UploadPath, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.UploadPath) - @Html.HintFor(m => m.UploadPath) -
-
-
-
- @Html.LabelFor(m => m.StorageConnectionString) -
-
- @Html.TextAreaFor(m => m.StorageConnectionString, new {@class = "large"}) - @Html.ValidationMessageFor(m => m.StorageConnectionString) - @Html.HintFor(m => m.StorageConnectionString) - -
-
Connection String Template
-
DefaultEndpointsProtocol=[http|https];AccountName=myAccountName;AccountKey=myAccountKey
-
-
- - -
- @Html.LabelFor(m => m.BlobContainerName) -
-
- @Html.TextBoxFor(m => m.BlobContainerName) - @Html.ValidationMessageFor(m => m.BlobContainerName) - @Html.HintFor(m => m.BlobContainerName) -
-
- - - -

Comments

- -
- @Html.LabelFor(m => m.DisableCommentsOlderThan) -
-
- @Html.TextBoxFor(m => m.DisableCommentsOlderThan, new { @class = "small" }) - @Html.ValidationMessageFor(m => m.DisableCommentsOlderThan) - @Html.HintFor(m => m.DisableCommentsOlderThan) -
- -
- @Html.LabelFor(m => m.AkismetApiKey) -
-
- @Html.TextBoxFor(m => m.AkismetApiKey, new { @class = "medium" }) - @Html.ValidationMessageFor(m => m.AkismetApiKey) - @Html.HintFor(m => m.AkismetApiKey) -
- -
- @Html.LabelFor(m => m.SpamWords) -
-
- @Html.TextAreaFor(m => m.SpamWords, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.SpamWords) - @Html.HintFor(m => m.SpamWords) -
- -
- @Html.LabelFor(m => m.EnableDisqusCommenting) -
-
- @Html.CheckBoxFor(m => m.EnableDisqusCommenting) - @Html.ValidationMessageFor(m => m.EnableDisqusCommenting) - @Html.HintFor(m => m.EnableDisqusCommenting) -
- -
- @Html.LabelFor(m => m.DisqusShortname) -
-
- @Html.TextBoxFor(m => m.DisqusShortname, new { @class = "medium" }) - @Html.ValidationMessageFor(m => m.DisqusShortname) - @Html.HintFor(m => m.DisqusShortname) -
- -

History

-
- @Html.LabelFor(m => m.EnablePublicHistory) -
-
- @Html.CheckBoxFor(m => m.EnablePublicHistory) - @Html.ValidationMessageFor(m => m.EnablePublicHistory) - @Html.HintFor(m => m.EnablePublicHistory) -
- -

HTML

-
- @Html.LabelFor(m => m.HtmlHead) -
-
- @Html.TextAreaFor(m => m.HtmlHead, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.HtmlHead) - @Html.HintFor(m => m.HtmlHead) -
- -
- @Html.LabelFor(m => m.HtmlFooter) -
-
- @Html.TextAreaFor(m => m.HtmlFooter, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.HtmlFooter) - @Html.HintFor(m => m.HtmlFooter) -
- - -

Social

-
- @Html.LabelFor(m => m.FacebookLike) -
-
- @Html.CheckBoxFor(m => m.FacebookLike) - @Html.ValidationMessageFor(m => m.FacebookLike) - @Html.HintFor(m => m.FacebookLike) -
- -

Email notifications

- -
- @Html.LabelFor(m => m.CommentNotification) -
-
- @Html.CheckBoxFor(m => m.CommentNotification) - @Html.ValidationMessageFor(m => m.CommentNotification) - @Html.HintFor(m => m.CommentNotification) -
- -
- @Html.LabelFor(m => m.SmtpServer) -
-
- @Html.TextBoxFor(m => m.SmtpServer, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.SmtpServer) - @Html.HintFor(m => m.SmtpServer) -
- -
- @Html.LabelFor(m => m.SmtpPort) -
-
- @Html.TextBoxFor(m => m.SmtpPort, new { @class = "small" }) - @Html.ValidationMessageFor(m => m.SmtpPort) - @Html.HintFor(m => m.SmtpPort) -
- -
- @Html.LabelFor(m => m.SmtpUseSsl) -
-
- @Html.CheckBoxFor(m => m.SmtpUseSsl) - @Html.ValidationMessageFor(m => m.SmtpUseSsl) - @Html.HintFor(m => m.SmtpUseSsl) -
- -
- @Html.LabelFor(m => m.SmtpFromEmailAddress) -
-
- @Html.TextBoxFor(m => m.SmtpFromEmailAddress, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.SmtpFromEmailAddress) - @Html.HintFor(m => m.SmtpFromEmailAddress) -
- -
- @Html.LabelFor(m => m.SmtpToEmailAddress) -
-
- @Html.TextBoxFor(m => m.SmtpToEmailAddress, new { @class = "large" }) - @Html.ValidationMessageFor(m => m.SmtpToEmailAddress) - @Html.HintFor(m => m.SmtpToEmailAddress) -
- -
- @Html.LabelFor(m => m.SmtpUsername) -
-
- @Html.TextBoxFor(m => m.SmtpUsername, new { @class = "medium" }) - @Html.ValidationMessageFor(m => m.SmtpUsername) - @Html.HintFor(m => m.SmtpUsername) -
- -
- @Html.LabelFor(m => m.SmtpPassword) -
-
- @Html.TextBoxFor(m => m.SmtpPassword, new { @class = "medium" }) - @Html.ValidationMessageFor(m => m.SmtpPassword) - @Html.HintFor(m => m.SmtpPassword) -
- -

Save

-

- -

-
+
+

Site Information

+ +
+ @Html.LabelFor(m => m.SiteTitle) +
+
+ @Html.TextBoxFor(m => m.SiteTitle, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SiteTitle) + @Html.HintFor(m => m.SiteTitle) +
+ +
+ @Html.LabelFor(m => m.Introduction) +
+
+ @Html.TextAreaFor(m => m.Introduction, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.Introduction) + @Html.HintFor(m => m.Introduction) +
+ +

Home Page

+ +
+ @Html.LabelFor(m => m.CustomHomePage) +
+
+ @Html.TextBoxFor(m => m.CustomHomePage) + @Html.ValidationMessageFor(m => m.CustomHomePage) + @Html.HintFor(m => m.CustomHomePage) +
+ +

Appearance

+ +
+ @Html.LabelFor(m => m.MainLinks) +
+
+ @Html.TextAreaFor(m => m.MainLinks, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.MainLinks) + @Html.HintFor(m => m.MainLinks) +
+ +
+ @Html.LabelFor(m => m.Footer) +
+
+ @Html.TextAreaFor(m => m.Footer, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.Footer) + @Html.HintFor(m => m.Footer) +
+ +
+ @Html.LabelFor(m => m.Theme) +
+
+ @Html.DropDownList("Theme", ((string[])ViewBag.Themes).Select(x => new SelectListItem { Text = x, Selected = Model.Theme == x })) + @Html.ValidationMessageFor(m => m.Theme) + @Html.HintFor(m => m.Theme) +
+ +

Metadata

+ +
+ @Html.LabelFor(m => m.Author) +
+
+ @Html.TextBoxFor(m => m.Author, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.Author) + @Html.HintFor(m => m.Author) +
+ +
+ @Html.LabelFor(m => m.SearchKeywords) +
+
+ @Html.TextBoxFor(m => m.SearchKeywords, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SearchKeywords) + @Html.HintFor(m => m.SearchKeywords) +
+ +
+ @Html.LabelFor(m => m.SearchDescription) +
+
+ @Html.TextBoxFor(m => m.SearchDescription, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SearchDescription) + @Html.HintFor(m => m.SearchDescription) +
+ +

File Uploads

+
+ @Html.LabelFor(m => m.StorageProvider) +
+
+ @{ + var storageProviders = (IEnumerable)ViewBag.FileRepositories; + } + @Html.DropDownListFor( + m => m.StorageProvider, + storageProviders.Select(provider => new SelectListItem + { + Text = provider, + Value = provider, + Selected = provider == Model.StorageProvider + }), + new { id = "storageProviders" }) + @Html.ValidationMessageFor(m => m.StorageProvider) + @Html.HintFor(m => m.StorageProvider) +
+
+
+ @Html.LabelFor(m => m.UploadPath) +
+
+ @Html.TextBoxFor(m => m.UploadPath, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.UploadPath) + @Html.HintFor(m => m.UploadPath) +
+
+
+
+ @Html.LabelFor(m => m.StorageConnectionString) +
+
+ @Html.TextAreaFor(m => m.StorageConnectionString, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.StorageConnectionString) + @Html.HintFor(m => m.StorageConnectionString) + +
+
Connection String Template:
+
DefaultEndpointsProtocol=[http|https];AccountName=myAccountName;AccountKey=myAccountKey
+
Local Development Only:
+
@AzureBlobFileRepository.DevelopmentStorageAccountConnectionString
+
+
+ +
+ @Html.LabelFor(m => m.BlobContainerName) +
+
+ @Html.TextBoxFor(m => m.BlobContainerName) + @Html.ValidationMessageFor(m => m.BlobContainerName) + @Html.HintFor(m => m.BlobContainerName) +
+
+ + + +

Comments

+ +
+ @Html.LabelFor(m => m.HideCommentCountOnRecentPage) +
+
+ @Html.CheckBoxFor(m => m.HideCommentCountOnRecentPage, new { @class = "small" }) + @Html.ValidationMessageFor(m => m.HideCommentCountOnRecentPage) + @Html.HintFor(m => m.HideCommentCountOnRecentPage) +
+ +
+ @Html.LabelFor(m => m.DisableCommentsOlderThan) +
+
+ @Html.TextBoxFor(m => m.DisableCommentsOlderThan, new { @class = "small" }) + @Html.ValidationMessageFor(m => m.DisableCommentsOlderThan) + @Html.HintFor(m => m.DisableCommentsOlderThan) +
+ +
+ @Html.LabelFor(m => m.AkismetApiKey) +
+
+ @Html.TextBoxFor(m => m.AkismetApiKey, new { @class = "medium" }) + @Html.ValidationMessageFor(m => m.AkismetApiKey) + @Html.HintFor(m => m.AkismetApiKey) +
+ +
+ @Html.LabelFor(m => m.SpamWords) +
+
+ @Html.TextAreaFor(m => m.SpamWords, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SpamWords) + @Html.HintFor(m => m.SpamWords) +
+ +
+ @Html.LabelFor(m => m.EnableDisqusCommenting) +
+
+ @Html.CheckBoxFor(m => m.EnableDisqusCommenting) + @Html.ValidationMessageFor(m => m.EnableDisqusCommenting) + @Html.HintFor(m => m.EnableDisqusCommenting) +
+ +
+ @Html.LabelFor(m => m.DisqusShortname) +
+
+ @Html.TextBoxFor(m => m.DisqusShortname, new { @class = "medium" }) + @Html.ValidationMessageFor(m => m.DisqusShortname) + @Html.HintFor(m => m.DisqusShortname) +
+ +

History

+
+ @Html.LabelFor(m => m.EnablePublicHistory) +
+
+ @Html.CheckBoxFor(m => m.EnablePublicHistory) + @Html.ValidationMessageFor(m => m.EnablePublicHistory) + @Html.HintFor(m => m.EnablePublicHistory) +
+ +

HTML

+
+ @Html.LabelFor(m => m.HtmlHead) +
+
+ @Html.TextAreaFor(m => m.HtmlHead, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.HtmlHead) + @Html.HintFor(m => m.HtmlHead) +
+ +
+ @Html.LabelFor(m => m.HtmlFooter) +
+
+ @Html.TextAreaFor(m => m.HtmlFooter, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.HtmlFooter) + @Html.HintFor(m => m.HtmlFooter) +
+ + +

Social

+
+ @Html.LabelFor(m => m.FacebookLike) +
+
+ @Html.CheckBoxFor(m => m.FacebookLike) + @Html.ValidationMessageFor(m => m.FacebookLike) + @Html.HintFor(m => m.FacebookLike) +
+ +

Email notifications

+ +
+ @Html.LabelFor(m => m.CommentNotification) +
+
+ @Html.CheckBoxFor(m => m.CommentNotification) + @Html.ValidationMessageFor(m => m.CommentNotification) + @Html.HintFor(m => m.CommentNotification) +
+ +
+ @Html.LabelFor(m => m.SmtpServer) +
+
+ @Html.TextBoxFor(m => m.SmtpServer, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SmtpServer) + @Html.HintFor(m => m.SmtpServer) +
+ +
+ @Html.LabelFor(m => m.SmtpPort) +
+
+ @Html.TextBoxFor(m => m.SmtpPort, new { @class = "small" }) + @Html.ValidationMessageFor(m => m.SmtpPort) + @Html.HintFor(m => m.SmtpPort) +
+ +
+ @Html.LabelFor(m => m.SmtpUseSsl) +
+
+ @Html.CheckBoxFor(m => m.SmtpUseSsl) + @Html.ValidationMessageFor(m => m.SmtpUseSsl) + @Html.HintFor(m => m.SmtpUseSsl) +
+ +
+ @Html.LabelFor(m => m.SmtpFromEmailAddress) +
+
+ @Html.TextBoxFor(m => m.SmtpFromEmailAddress, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SmtpFromEmailAddress) + @Html.HintFor(m => m.SmtpFromEmailAddress) +
+ +
+ @Html.LabelFor(m => m.SmtpToEmailAddress) +
+
+ @Html.TextBoxFor(m => m.SmtpToEmailAddress, new { @class = "large" }) + @Html.ValidationMessageFor(m => m.SmtpToEmailAddress) + @Html.HintFor(m => m.SmtpToEmailAddress) +
+ +
+ @Html.LabelFor(m => m.SmtpUsername) +
+
+ @Html.TextBoxFor(m => m.SmtpUsername, new { @class = "medium" }) + @Html.ValidationMessageFor(m => m.SmtpUsername) + @Html.HintFor(m => m.SmtpUsername) +
+ +
+ @Html.LabelFor(m => m.SmtpPassword) +
+
+ @Html.TextBoxFor(m => m.SmtpPassword, new { @class = "medium" }) + @Html.ValidationMessageFor(m => m.SmtpPassword) + @Html.HintFor(m => m.SmtpPassword) +
+ +

Save

+

+ +

+
} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Claims/Index.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Claims/Index.cshtml new file mode 100644 index 00000000..c11e9c3e --- /dev/null +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Claims/Index.cshtml @@ -0,0 +1,67 @@ +@using System.Security.Claims +@using System.Security.Principal +@using System.Threading +@model dynamic + +@{ + IPrincipal currentPrincipal = Thread.CurrentPrincipal; + IEnumerable claims = Enumerable.Empty(); + + var claimsPrincipal = currentPrincipal as ClaimsPrincipal; + if (claimsPrincipal != null) + { + claims = claimsPrincipal.Claims; + } + + Layout = null; +} + + + + + + Show current claims + + + +
+

Principal: @currentPrincipal.GetType().FullName

+

Identity: @currentPrincipal.Identity.GetType().FullName

+ + @if (claims.Any()) + { +
User Claims:
+
+ + + + + + + + + + @foreach (var claim in claims) + { + + + + + + } + +
Claim TypeClaim ValueClaim ValueType
@claim.Type@claim.Value@claim.ValueType
+
+ } + +
+ + diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Install/UpgradeModel.cs b/src/FunnelWeb.Web/Areas/Admin/Views/Install/UpgradeModel.cs index 2ea83653..94f95ead 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Install/UpgradeModel.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Install/UpgradeModel.cs @@ -2,15 +2,16 @@ namespace FunnelWeb.Web.Areas.Admin.Views.Install { - public class UpgradeModel - { - public UpgradeModel(DatabaseUpgradeResult[] results, string log) - { - Results = results; - Log = log; - } + public class UpgradeModel + { + public UpgradeModel(DatabaseUpgradeResult[] results, string log) + { + Results = results; + Log = log; + } - public string Log { get; set; } - public DatabaseUpgradeResult[] Results { get; set; } - } + public string Log { get; set; } + + public DatabaseUpgradeResult[] Results { get; set; } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Login/Login.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Login/Login.cshtml index b6341453..1cc2ccda 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Login/Login.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Login/Login.cshtml @@ -1,66 +1,58 @@ @model FunnelWeb.Web.Areas.Admin.Views.Login.LoginModel @{ - ViewBag.Title = "Login"; - Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; + ViewBag.Title = "Login"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; } -@if (Model.ConfigFileMissing ?? false) +@if (Model.DatabaseConnectionIssue) { -

- Config File Missing

-

- FunnelWeb uses the my.config file to store the administration username and password. - Rename my.config.sample to my.config and set your username and password. -

+

Unable to connect to the database

+

There is an error connecting to the database at the current time. Please try again later.

+} +else if (Model.ConfigFileMissing ?? false) +{ +

Config File Missing

+

FunnelWeb uses the my.config file to store the administration username and password.

} else { - if (Model.DatabaseIssue ?? false) - { -

- Database Issue

-

- The database used by your FunnelWeb installation is either offline, out of date - or has not been configured correctly. To resolve this issue, you will need to log - in with the username and password from your my.config file. -

- } - else - { -

- Login

-

- To administer this site, log in using the form below. The username and password - are stored in your web.config file. -

- } + if (Model.DatabaseIssue ?? false) + { +

Database Issue

+

The database used by your FunnelWeb installation is either offline, out of date or has not been configured correctly. To resolve this issue, you will need to log in with the credentials in your my.config file.

+ } + else + { +

Login

+

To administer this site, log in using the form below.

+ } - @Html.ValidationSummary("Login unsuccessful") - using (Html.BeginForm("Login", "Login", FormMethod.Post, new { Area = "Admin" })) - { - -
-
- @Html.LabelFor(m => m.Username) -
-
- @Html.TextBoxFor(m => m.Username, Html.AttributesFor(m => m.Username)) - @Html.ValidationMessageFor(m => m.Username) -
-
- @Html.LabelFor(m => m.Password) -
-
- @Html.PasswordFor(m => m.Password, Html.AttributesFor(m => m.Password)) - @Html.ValidationMessageFor(m => m.Password) -
- @Html.HiddenFor(m => m.DatabaseIssue) - @Html.HiddenFor(m => m.ReturnUrl) -
-
-
- -
-
-
- } + @Html.ValidationSummary("Login unsuccessful") + using (Html.BeginForm("Login", "Login", FormMethod.Post, new { Area = "Admin" })) + { + +
+
+ @Html.LabelFor(m => m.Username) +
+
+ @Html.TextBoxFor(m => m.Username, Html.AttributesFor(m => m.Username)) + @Html.ValidationMessageFor(m => m.Username) +
+
+ @Html.LabelFor(m => m.Password) +
+
+ @Html.PasswordFor(m => m.Password, Html.AttributesFor(m => m.Password)) + @Html.ValidationMessageFor(m => m.Password) +
+ @Html.HiddenFor(m => m.DatabaseIssue) + @Html.HiddenFor(m => m.ReturnUrl) +
+
+
+ +
+
+
+ } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Login/LoginModel.cs b/src/FunnelWeb.Web/Areas/Admin/Views/Login/LoginModel.cs index ddf841fa..84da8e82 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Login/LoginModel.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Login/LoginModel.cs @@ -3,20 +3,22 @@ namespace FunnelWeb.Web.Areas.Admin.Views.Login { - public class LoginModel - { - public bool? DatabaseIssue { get; set; } - public bool? ConfigFileMissing { get; set; } - public string ReturnUrl { get; set; } + public class LoginModel + { + public bool? DatabaseIssue { get; set; } + public bool? ConfigFileMissing { get; set; } + public string ReturnUrl { get; set; } + public bool DatabaseConnectionIssue { get; set; } + public string DatabaseError { get; set; } - [Required] - [StringLength(100)] - [HintSize(HintSize.Medium)] - public string Username { get; set; } + [Required] + [StringLength(100)] + [HintSize(HintSize.Medium)] + public string Username { get; set; } - [Required] - [StringLength(100)] - [HintSize(HintSize.Medium)] - public string Password { get; set; } - } + [Required] + [StringLength(100)] + [HintSize(HintSize.Medium)] + public string Password { get; set; } + } } \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Shared/_Private.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Shared/_Private.cshtml index b7de07e9..329e16e0 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Shared/_Private.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Shared/_Private.cshtml @@ -1,4 +1,5 @@ - +@using FunnelWeb.Authentication.Internal + @@ -21,7 +22,7 @@