From 148265ea5a670fa8cb17a64842bd47b2a930e1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Sun, 13 Oct 2013 16:57:44 +0200 Subject: [PATCH 01/13] Fixed a few resharper error messages. --- .../Content/themes/base/Base.css | 5 ++ src/FunnelWeb.Web/Macros/TwitterStream.cshtml | 75 ++++++++++--------- src/FunnelWeb.Web/My.config | 2 +- src/FunnelWeb.Web/Scripts/site.js | 1 + .../RainbowAfro/Content/Styles/Theme.css | 2 + .../Views/Shared/PageTemplates/Default.cshtml | 9 ++- 6 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/FunnelWeb.Web/Content/themes/base/Base.css b/src/FunnelWeb.Web/Content/themes/base/Base.css index 8584b5ad..ab6b63c2 100644 --- a/src/FunnelWeb.Web/Content/themes/base/Base.css +++ b/src/FunnelWeb.Web/Content/themes/base/Base.css @@ -138,15 +138,20 @@ header div.search width: 200px; -moz-background-clip: border; -moz-background-inline-policy: continuous; + /* ReSharper disable once InvalidValue */ -moz-background-origin: padding; -moz-border-radius: 5px; -webkit-border-radius: 5px; -webkit-background-clip: border; -webkit-background-inline-policy: continuous; + /* ReSharper disable once InvalidValue */ -webkit-background-origin: padding; border-radius: 5px; + /* ReSharper disable once InvalidValue */ background-clip: border; + /* ReSharper disable once CssNotResolved */ background-inline-policy: continuous; + /* ReSharper disable once InvalidValue */ background-origin: padding; } .intro h1 diff --git a/src/FunnelWeb.Web/Macros/TwitterStream.cshtml b/src/FunnelWeb.Web/Macros/TwitterStream.cshtml index b0b99a97..81b4dd16 100644 --- a/src/FunnelWeb.Web/Macros/TwitterStream.cshtml +++ b/src/FunnelWeb.Web/Macros/TwitterStream.cshtml @@ -1,39 +1,40 @@ -
- @Html.TextBox("hello") +@*/* ReSharper disable all */ *@ +
+ @System.Web.Mvc.Html.InputExtensions.TextBox(Html, "hello") -

@Args.title

- - +

@Args.title

+ +
\ No newline at end of file diff --git a/src/FunnelWeb.Web/My.config b/src/FunnelWeb.Web/My.config index 32db9056..2aef27cd 100644 --- a/src/FunnelWeb.Web/My.config +++ b/src/FunnelWeb.Web/My.config @@ -1,6 +1,6 @@ - + diff --git a/src/FunnelWeb.Web/Scripts/site.js b/src/FunnelWeb.Web/Scripts/site.js index 2559ffb6..294f8d31 100644 --- a/src/FunnelWeb.Web/Scripts/site.js +++ b/src/FunnelWeb.Web/Scripts/site.js @@ -58,6 +58,7 @@ }; function enableNewWindowLinks() { +// ReSharper disable once CssNotResolved $(".content a:external") .not(".comment-author a") .addClass("new-window"); diff --git a/src/FunnelWeb.Web/Themes/RainbowAfro/Content/Styles/Theme.css b/src/FunnelWeb.Web/Themes/RainbowAfro/Content/Styles/Theme.css index a7259ee5..37a15769 100644 --- a/src/FunnelWeb.Web/Themes/RainbowAfro/Content/Styles/Theme.css +++ b/src/FunnelWeb.Web/Themes/RainbowAfro/Content/Styles/Theme.css @@ -128,12 +128,14 @@ header nav ul li border: 1px solid #333; -moz-background-clip: border; -moz-background-inline-policy: continuous; + /* ReSharper disable once InvalidValue */ -moz-background-origin: padding; -moz-border-radius: 5px; border-radius: 5px; -webkit-border-radius: 5px; -webkit-background-clip: border; -webkit-background-inline-policy: continuous; + /* ReSharper disable once InvalidValue */ -webkit-background-origin: padding; margin-left: 10px; } diff --git a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml index 95197538..df16b9ad 100644 --- a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml @@ -8,7 +8,7 @@ - + @if (Model.Entry.IsPriorVersion) { @@ -39,9 +39,10 @@ {
- - You are looking at revision @Model.Entry.RevisionNumber of this page, which may be out of date. @Html.ActionLink("View the latest version.", "Page", "Wiki", new { page = Model.Entry.Name }, null) - + + You are looking at revision @Model.Entry.RevisionNumber of this page, which may be out of date. @Html.ActionLink("View the latest version.", "Page", "Wiki", new { page = Model.Entry.Name }, null) + + @* ReSharper disable once Mvc.ActionNotResolved *@ @if (ViewData.IsLoggedIn()) { @Html.ActionLink("Revert to this version.", "Revert", "WikiAdmin", new { Area = "Admin", page = Model.Page, revision = Model.Entry.RevisionNumber }, new { }) From 2ab9828a35681abb4814f9e288c0c898153cd116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Sun, 13 Oct 2013 21:15:50 +0200 Subject: [PATCH 02/13] Upgraded to .NET 4.5 to get System.Security.Claims. --- ...lWeb.Extensions.CommentNotification.csproj | 5 +- src/FunnelWeb.Tests/App.config | 22 ++-- src/FunnelWeb.Tests/FunnelWeb.Tests.csproj | 5 +- src/FunnelWeb.Tests/packages.config | 4 +- .../Admin/Controllers/ClaimsController.cs | 12 ++ src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs | 6 + .../Areas/Admin/Views/Claims/Index.cshtml | 67 ++++++++++ src/FunnelWeb.Web/FunnelWeb.Web.csproj | 8 +- src/FunnelWeb.Web/Web.config | 120 +++++++++--------- src/FunnelWeb/FunnelWeb.csproj | 6 +- .../Providers/File/AzureBlobFileRepository.cs | 34 +++-- src/FunnelWeb/app.config | 8 +- src/FunnelWeb/packages.config | 2 +- 13 files changed, 200 insertions(+), 99 deletions(-) create mode 100644 src/FunnelWeb.Web/Areas/Admin/Controllers/ClaimsController.cs create mode 100644 src/FunnelWeb.Web/Areas/Admin/Views/Claims/Index.cshtml diff --git a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj index 7095bac5..8702aa9b 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,6 +33,7 @@ TRACE prompt 4 + false diff --git a/src/FunnelWeb.Tests/App.config b/src/FunnelWeb.Tests/App.config index c3131a17..96a36981 100644 --- a/src/FunnelWeb.Tests/App.config +++ b/src/FunnelWeb.Tests/App.config @@ -1,30 +1,30 @@ - + -
+
- + - + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj index 3a1c3a2d..f9a66d54 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,6 +35,7 @@ prompt 4 x86 + false diff --git a/src/FunnelWeb.Tests/packages.config b/src/FunnelWeb.Tests/packages.config index 2ee91525..a7a3fcf0 100644 --- a/src/FunnelWeb.Tests/packages.config +++ b/src/FunnelWeb.Tests/packages.config @@ -1,7 +1,7 @@  - - + + 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/RouteConfig.cs b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs index bb1f8a8f..e81287b5 100644 --- a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs +++ b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs @@ -56,6 +56,12 @@ internal static void RegisterRoutes(AreaRegistrationContext context) new { controller = "WikiAdmin", action = "DeletePage" } ); + 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", 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/FunnelWeb.Web.csproj b/src/FunnelWeb.Web/FunnelWeb.Web.csproj index d3312f36..90f9ca70 100644 --- a/src/FunnelWeb.Web/FunnelWeb.Web.csproj +++ b/src/FunnelWeb.Web/FunnelWeb.Web.csproj @@ -13,7 +13,7 @@ Properties FunnelWeb.Web FunnelWeb.Web - v4.0 + v4.5 false true @@ -32,6 +32,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -40,6 +41,7 @@ TRACE prompt 4 + false @@ -108,9 +110,9 @@ - + ..\packages\Microsoft.AspNet.Web.Optimization.1.0.0\lib\net40\System.Web.Optimization.dll @@ -251,6 +253,7 @@ + @@ -483,6 +486,7 @@ Designer + Web.config diff --git a/src/FunnelWeb.Web/Web.config b/src/FunnelWeb.Web/Web.config index bce0f4d8..c80a4a8b 100644 --- a/src/FunnelWeb.Web/Web.config +++ b/src/FunnelWeb.Web/Web.config @@ -1,59 +1,67 @@ - + - - - - - + + + + + + - + - - + + - - - + + + - - + + - - - + + + - - - - - - - - - - + + + + + + + + + + - + - - - + + + @@ -61,50 +69,48 @@ - - - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + \ No newline at end of file diff --git a/src/FunnelWeb/FunnelWeb.csproj b/src/FunnelWeb/FunnelWeb.csproj index 1e5f1bfe..b401af2e 100644 --- a/src/FunnelWeb/FunnelWeb.csproj +++ b/src/FunnelWeb/FunnelWeb.csproj @@ -11,9 +11,10 @@ Properties FunnelWeb FunnelWeb - v4.0 + v4.5 512 ..\ + true @@ -23,6 +24,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -31,6 +33,7 @@ TRACE prompt 4 + false @@ -68,7 +71,6 @@ False ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.7.0.3\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll - ..\packages\WindowsAzure.Storage.1.7.0.0\lib\net35-full\Microsoft.WindowsAzure.StorageClient.dll diff --git a/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs b/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs index e46af811..75d014ac 100644 --- a/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs +++ b/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs @@ -1,12 +1,10 @@ using System; using System.IO; using System.Linq; -using System.Web; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Settings; using FunnelWeb.Utilities; -using ICSharpCode.SharpZipLib.Zip; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.StorageClient; @@ -198,22 +196,22 @@ public override void Save(Stream inputStream, string fullPath, bool unzip) } } - public void Extract(Stream stream, string fullPath) - { - var strings = fullPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); - var basePath = string.Join("/", strings.Take(strings.Length - 1)); - - using (var input = stream) - using (var zipInput = new ZipInputStream(input)) - { - ZipEntry entry; - while ((entry = zipInput.GetNextEntry()) != null) - { - var blob = Container.GetBlobReference(Path.Combine(basePath, entry.Name)); - blob.UploadFromStream(zipInput); - } - } - } + //public void Extract(Stream stream, string fullPath) + //{ + // var strings = fullPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); + // var basePath = string.Join("/", strings.Take(strings.Length - 1)); + + // using (var input = stream) + // using (var zipInput = new ZipInputStream(input)) + // { + // ZipEntry entry; + // while ((entry = zipInput.GetNextEntry()) != null) + // { + // var blob = Container.GetBlobReference(Path.Combine(basePath, entry.Name)); + // blob.UploadFromStream(zipInput); + // } + // } + //} public override ActionResult Render(string path) { diff --git a/src/FunnelWeb/app.config b/src/FunnelWeb/app.config index 085a4254..3ff3e80b 100644 --- a/src/FunnelWeb/app.config +++ b/src/FunnelWeb/app.config @@ -1,11 +1,11 @@ - + - - + + - \ No newline at end of file + diff --git a/src/FunnelWeb/packages.config b/src/FunnelWeb/packages.config index 4a994afe..93d2e7be 100644 --- a/src/FunnelWeb/packages.config +++ b/src/FunnelWeb/packages.config @@ -12,7 +12,7 @@ - +
\ No newline at end of file From 145e443bf9d173f42e99edbb7f25f05c88955460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Mon, 14 Oct 2013 14:33:16 +0200 Subject: [PATCH 03/13] The application is now using Claims rather than roles. The database still stores roles but they are mapped to claims in the app which does not use roles at all. --- src/FunnelWeb.Tests/LifetimeJustifications.cs | 6 +- .../Controllers/UploadControllerTests.cs | 161 +++-- .../Controllers/WikiAdminControllerTests.cs | 98 ++- .../Authentication/AuthenticationModule.cs | 98 +-- .../Authentication/FunnelWebRoleProvider.cs | 101 +-- .../Extensions/MarkupExtensions.cs | 578 +++++++-------- .../Application/Mvc/MvcExtensions.cs | 44 +- .../Admin/Controllers/AdminController.cs | 338 ++++----- .../Admin/Controllers/InstallController.cs | 244 ++++--- .../SqlAuthenticationController.cs | 377 +++++----- .../Admin/Controllers/UploadController.cs | 106 +-- .../Admin/Controllers/WikiAdminController.cs | 299 ++++---- .../Admin/Views/Admin/BlogMLImport.cshtml | 2 +- .../Areas/Admin/Views/Admin/Comments.cshtml | 70 +- .../Areas/Admin/Views/Admin/Index.cshtml | 2 +- .../Areas/Admin/Views/Admin/Pingbacks.cshtml | 37 +- .../Areas/Admin/Views/Admin/Settings.cshtml | 666 +++++++++--------- .../2007-06/FederationMetadata.xml | 1 + src/FunnelWeb.Web/FunnelWeb.Web.csproj | 12 +- src/FunnelWeb.Web/Web.config | 265 ++++--- src/FunnelWeb.Web/packages.config | 1 + .../Authentication/IAuthenticator.cs | 14 +- .../Authentication/Internal/Authorization.cs | 80 +++ .../Internal/ClaimsPrincipalExtensions.cs | 12 + .../Internal/ClaimsRoleProvider.cs | 72 ++ .../Internal/ClaimsSessionHelper.cs | 35 + .../Internal/CustomAuthenticationManager.cs | 14 + .../Internal/CustomAuthorizationManager.cs | 118 ++++ .../Internal/FormsAuthenticator.cs | 84 ++- .../Internal/FormsFunnelWebMembership.cs | 58 +- .../Internal/FormsRoleProvider.cs | 26 +- .../Internal/SqlAuthenticator.cs | 86 --- .../Internal/SqlClaimsAuthenticator.cs | 88 +++ .../Internal/SqlFunnelWebMembership.cs | 75 +- .../Internal/SqlRoleProvider.cs | 92 --- src/FunnelWeb/FunnelWeb.csproj | 13 +- src/FunnelWeb/Model/Authentication/Role.cs | 17 +- src/FunnelWeb/Model/Authentication/User.cs | 31 +- .../Providers/File/AzureBlobFileRepository.cs | 431 ++++++------ .../Settings/SqlAuthenticationSettings.cs | 21 +- 40 files changed, 2624 insertions(+), 2249 deletions(-) create mode 100644 src/FunnelWeb.Web/FederationMetadata/2007-06/FederationMetadata.xml create mode 100644 src/FunnelWeb/Authentication/Internal/Authorization.cs create mode 100644 src/FunnelWeb/Authentication/Internal/ClaimsPrincipalExtensions.cs create mode 100644 src/FunnelWeb/Authentication/Internal/ClaimsRoleProvider.cs create mode 100644 src/FunnelWeb/Authentication/Internal/ClaimsSessionHelper.cs create mode 100644 src/FunnelWeb/Authentication/Internal/CustomAuthenticationManager.cs create mode 100644 src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs delete mode 100644 src/FunnelWeb/Authentication/Internal/SqlAuthenticator.cs create mode 100644 src/FunnelWeb/Authentication/Internal/SqlClaimsAuthenticator.cs delete mode 100644 src/FunnelWeb/Authentication/Internal/SqlRoleProvider.cs diff --git a/src/FunnelWeb.Tests/LifetimeJustifications.cs b/src/FunnelWeb.Tests/LifetimeJustifications.cs index 0bbb18b3..7a4d0f53 100644 --- a/src/FunnelWeb.Tests/LifetimeJustifications.cs +++ b/src/FunnelWeb.Tests/LifetimeJustifications.cs @@ -111,8 +111,10 @@ public void ComponentsThatShouldBePerRequest() 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."); + 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 diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs index 44ac09d0..3bdaf726 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,109 @@ 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() + { + var settingsProvider = Substitute.For(); + settingsProvider.GetSettings().Returns(new FunnelWebSettings()); + Controller = new UploadController + { + FileRepository = FileRepository = Substitute.For(), + ControllerContext = ControllerContext, + SettingsProvider = settingsProvider + }; + } - [Test] - public void IndexForExistingFile() - { - FileRepository.IsFile(Arg.Any()).Returns(true); + [Test] + public void IndexForExistingFile() + { + FileRepository.IsFile(Arg.Any()).Returns(true); - var result = (RedirectToRouteResult)Controller.Index("test"); + var result = (RedirectToRouteResult)Controller.Index("test"); - FileRepository.Received().IsFile(Arg.Any()); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + FileRepository.Received().IsFile(Arg.Any()); + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - [Test] - public void IndexForMissingFile() - { - FileRepository.IsFile(Arg.Any()).Returns(false); + [Test] + public void IndexForMissingFile() + { + FileRepository.IsFile(Arg.Any()).Returns(false); - var result = (ViewResult)Controller.Index("test"); + var result = (ViewResult)Controller.Index("test"); - FileRepository.Received().IsFile(Arg.Any()); - Assert.That(result.View, Is.EqualTo(null)); - } + FileRepository.Received().IsFile(Arg.Any()); + Assert.That(result.View, Is.EqualTo(null)); + } - [Test] - public void Upload() - { - var stream = new MemoryStream(); + [Test] + public void Upload() + { + var stream = new MemoryStream(); - var file = Substitute.For(); - file.InputStream.Returns(stream); + var file = Substitute.For(); + file.InputStream.Returns(stream); - var upload = new FileUpload(file); + var upload = new FileUpload(file); - var result = (RedirectToRouteResult)Controller.Upload("path", false, upload); + var result = (RedirectToRouteResult)Controller.Upload("path", false, upload); - FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(false)); + FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(false)); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - [Test] - public void UploadAndUnzip() - { - var stream = new MemoryStream(); + [Test] + public void UploadAndUnzip() + { + var stream = new MemoryStream(); - var file = Substitute.For(); - file.InputStream.Returns(stream); + var file = Substitute.For(); + file.InputStream.Returns(stream); - var upload = new FileUpload(file); + var upload = new FileUpload(file); - var result = (RedirectToRouteResult)Controller.Upload("path", true, upload); + var result = (RedirectToRouteResult)Controller.Upload("path", true, upload); - FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(true)); + FileRepository.Received().Save(Arg.Is(stream), Arg.Is("path"), Arg.Is(true)); - Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); - } + Assert.That(result.RouteValues["Action"], Is.EqualTo("Index")); + } - [Test] - public void CreateDirectory() - { - var result = (RedirectToRouteResult)Controller.CreateDirectory("path", string.Empty); + [Test] + public void CreateDirectory() + { + var result = (RedirectToRouteResult)Controller.CreateDirectory("path", string.Empty); - 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().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 Delete() - { - var result = (RedirectToRouteResult)Controller.Delete("path", "file"); + [Test] + public void Delete() + { + var result = (RedirectToRouteResult)Controller.Delete("path", "file"); - FileRepository.Received().Delete(Arg.Is("file")); - 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 RenderExistingFile() - { - FileRepository.IsFile(Arg.Is("file")).Returns(true); + [Test] + public void RenderExistingFile() + { + FileRepository.IsFile(Arg.Is("file")).Returns(true); - Controller.Render("file"); + Controller.Render("file"); - FileRepository.Received().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..ca625745 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web.Mvc; @@ -7,69 +6,66 @@ 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() + { + AdminController = new WikiAdminController + { + Repository = Repository = Substitute.For(), + ControllerContext = ControllerContext = ControllerContext + }; - [Test] - public void EditReturnsExistingPageWhenFound() - { - var entry = new EntryRevision { Name = "awesome-post" }; - Repository.FindFirstOrDefault(Arg.Any()).Returns(entry); + Identity = Substitute.For(); + User = Substitute.For(); + User.Identity.Returns(Identity); + ControllerContext.HttpContext.User.Returns(User); + } - 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.Web/Application/Authentication/AuthenticationModule.cs b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs index ba03b306..dcec5bc6 100644 --- a/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs +++ b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs @@ -4,52 +4,54 @@ 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(); + } + } } diff --git a/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs b/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs index 30082bb2..3674d305 100644 --- a/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs +++ b/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs @@ -1,67 +1,72 @@ using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; 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 class FunnelWebRoleProvider : RoleProvider + { + public FunnelWebRoleProvider() + { + ApplicationName = "FunnelWeb"; + } - public override bool IsUserInRole(string username, string roleName) - { - return DependencyResolver.Current.GetService().IsUserInRole(username, roleName); - } + public override bool IsUserInRole(string username, string roleName) + { + return ((ClaimsPrincipal)Thread.CurrentPrincipal).FindAll(c => c.Type == ClaimTypes.Role).Any(c => c.Value == roleName); + //return DependencyResolver.Current.GetService().IsUserInRole(username, roleName); + } - public override string[] GetRolesForUser(string username) - { - return DependencyResolver.Current.GetService().GetRolesForUser(username); - } + public override string[] GetRolesForUser(string username) + { + return ((ClaimsPrincipal)Thread.CurrentPrincipal).FindAll(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray(); + //return DependencyResolver.Current.GetService().GetRolesForUser(username); + } - public override void CreateRole(string roleName) - { - throw new NotSupportedException(); - } + public override void CreateRole(string roleName) + { + throw new NotSupportedException(); + } - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) - { - 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 bool RoleExists(string roleName) + { + throw new NotSupportedException(); + } - public override void AddUsersToRoles(string[] usernames, string[] roleNames) - { - 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 void RemoveUsersFromRoles(string[] usernames, string[] roleNames) + { + throw new NotSupportedException(); + } - public override string[] GetUsersInRole(string roleName) - { - throw new NotSupportedException(); - } + public override string[] GetUsersInRole(string roleName) + { + throw new NotSupportedException(); + } - public override string[] GetAllRoles() - { - throw new NotSupportedException(); - } + public override string[] GetAllRoles() + { + throw new NotSupportedException(); + } - public override string[] FindUsersInRole(string roleName, string usernameToMatch) - { - throw new NotSupportedException(); - } + public override string[] FindUsersInRole(string roleName, string usernameToMatch) + { + throw new NotSupportedException(); + } - public override string ApplicationName { get; set; } - } + 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..1daa509a 100644 --- a/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs +++ b/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs @@ -23,292 +23,292 @@ 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, 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; + } + } +} \ No newline at end of file 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/Areas/Admin/Controllers/AdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs index 5b402d87..8c7f197a 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,173 @@ 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 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.Operation.View, Resource = Authorization.Resource.Admin.Index)] + public virtual ActionResult Index() + { + return View(new IndexModel()); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.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.Operation.Delete, Resource = Authorization.Resource.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.Operation.Delete, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.Admin.Pingbacks)] + public virtual ActionResult Pingbacks() + { + var pingbacks = Repository.FindAll(); + return View(new PingbacksModel(pingbacks)); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.Admin.Task)] + public virtual ActionResult Task(int id) + { + var task = TaskRepository.Get(id); + return View("Task", new TaskModel(task)); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.BlogMl)] + public virtual ActionResult BlogMlImport() + { + return View(new BlogMLImportModel()); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.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/InstallController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs index b8d93932..4cff6454 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs @@ -1,9 +1,12 @@ 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; @@ -11,121 +14,128 @@ 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 Func> databaseProvidersInfo; + // 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) + { + this.databaseProvidersInfo = databaseProvidersInfo; + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Install.Index)] + 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); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Install.ChangeProvider)] + 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")] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.Install.Upgrade)] + 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)); + } + } + } } \ 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..c985c8eb 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.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.SqlAuthentications.NewAccount)] + public ActionResult NewAccount() + { + return View(new NewUser()); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.SqlAuthentications.Setup)] + public ActionResult Setup() + { + var setupModel = new SetupModel + { + HasAdminAccount = sqlMembership.HasAdminAccount() + }; + + return View(setupModel); + } + + [HttpPost] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.Setup)] + public ActionResult Setup(SetupModel setupModel) + { + return View(setupModel); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Insert, Resource = Authorization.Resource.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); + } + + sqlAuthSettings.SqlAuthenticationEnabled = true; + settingsProvider.SaveSettings(sqlAuthSettings); + + formsAuthenticator.Logout(); + return RedirectToAction("Index"); + } + + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.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.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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..52b6ab10 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.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.Delete, Resource = Authorization.Resource.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..50937630 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.Operation.View, Resource = Authorization.Resource.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.Operation.Update, Resource = Authorization.Resource.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.Operation.Delete, Resource = Authorization.Resource.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/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..688e7e3f 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 @@
} \ No newline at end of file diff --git a/src/FunnelWeb.Web/FederationMetadata/2007-06/FederationMetadata.xml b/src/FunnelWeb.Web/FederationMetadata/2007-06/FederationMetadata.xml new file mode 100644 index 00000000..97b74c8f --- /dev/null +++ b/src/FunnelWeb.Web/FederationMetadata/2007-06/FederationMetadata.xml @@ -0,0 +1 @@ +http://localhost:28392/http://localhost:28392/ \ No newline at end of file diff --git a/src/FunnelWeb.Web/FunnelWeb.Web.csproj b/src/FunnelWeb.Web/FunnelWeb.Web.csproj index 90f9ca70..d7c6acf6 100644 --- a/src/FunnelWeb.Web/FunnelWeb.Web.csproj +++ b/src/FunnelWeb.Web/FunnelWeb.Web.csproj @@ -105,6 +105,11 @@ ..\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 + @@ -380,6 +385,7 @@ + @@ -432,7 +438,9 @@ - + + Designer + @@ -462,7 +470,7 @@ - + diff --git a/src/FunnelWeb.Web/Web.config b/src/FunnelWeb.Web/Web.config index c80a4a8b..03cfb9f3 100644 --- a/src/FunnelWeb.Web/Web.config +++ b/src/FunnelWeb.Web/Web.config @@ -1,116 +1,155 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FunnelWeb.Web/packages.config b/src/FunnelWeb.Web/packages.config index e081183f..222b84ed 100644 --- a/src/FunnelWeb.Web/packages.config +++ b/src/FunnelWeb.Web/packages.config @@ -22,6 +22,7 @@ + diff --git a/src/FunnelWeb/Authentication/IAuthenticator.cs b/src/FunnelWeb/Authentication/IAuthenticator.cs index 0af1cd80..5acab03d 100644 --- a/src/FunnelWeb/Authentication/IAuthenticator.cs +++ b/src/FunnelWeb/Authentication/IAuthenticator.cs @@ -1,9 +1,9 @@ namespace FunnelWeb.Authentication { - public interface IAuthenticator - { - string GetName(); - bool AuthenticateAndLogin(string username, string password); - void Logout(); - } -} + public interface IAuthenticator + { + string GetName(); + bool AuthenticateAndLogin(string username, string password); + void Logout(); + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/Authorization.cs b/src/FunnelWeb/Authentication/Internal/Authorization.cs new file mode 100644 index 00000000..440b82d8 --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/Authorization.cs @@ -0,0 +1,80 @@ +using System.Security.Claims; + +namespace FunnelWeb.Authentication.Internal +{ + /// + /// All authorization contexts. + /// + public static class Authorization + { + public static class Roles + { + public static readonly Claim Admin = new Claim(ClaimTypes.Role, "Admin"); + public static readonly Claim Moderator = new Claim(ClaimTypes.Role, "Moderator"); + } + + public static class Operation + { + public const string View = "View"; + public const string Update = "Update"; + public const string Delete = "Delete"; + public const string Insert = "Insert"; + } + + public static class Resource + { + public static class Admin + { + private const string Base = "Admin."; + public const string Index = Base + "Index"; + public const string Settings = Base + "Settings"; + public const string Comments = Base + "Comments"; + public const string Comment = Base + "Comment"; + public const string AllSpam = Base + "AllSpam"; + public const string Spam = Base + "Spam"; + public const string Pingbacks = Base + "PingBacks"; + public const string Pingback = Base + "PingBack"; + public const string Tasks = Base + "Tasks"; + public const string Task = Base + "Task"; + public const string BlogMl = Base + "BlogML"; + public const string Pages = Base + "Pages"; + } + + public static class SqlAuthentications + { + private const string Base = "SqlAuthentication."; + public const string SqlAuthentication = Base + "SqlAuthentication"; + public const string NewAccount = Base + "NewAccount"; + public const string Setup = Base + "Setup"; + public const string AdminAccount = Base + "AdminAccount"; + public const string RoleAdd = Base + "RoleAdd"; + public const string RemoveRole = Base + "RemoveRole"; + } + + public static class Upload + { + private const string Base = "Upload."; + public const string Index = Base + "Upload"; + public const string CreateDirectory = Base + "CreateDirectory"; + public const string Move = Base + "Move"; + public const string Delete = Base + "Delete"; + } + + public static class WikiAdmin + { + private const string Base = "WikiAdmin."; + public const string Edit = Base + "Edit"; + public const string Page = Base + "Page"; + } + + public static class Install + { + private const string Base = "Install."; + public const string Index = Base + "Index"; + public const string ChangeProvider = Base + "ChangeProvider"; + public const string Test = Base + "Test"; + public const string Upgrade = Base + "Upgrade"; + } + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/ClaimsPrincipalExtensions.cs b/src/FunnelWeb/Authentication/Internal/ClaimsPrincipalExtensions.cs new file mode 100644 index 00000000..a44f9a11 --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/ClaimsPrincipalExtensions.cs @@ -0,0 +1,12 @@ +using System.Security.Claims; + +namespace FunnelWeb.Authentication.Internal +{ + public static class ClaimsPrincipalExtensions + { + public static bool IsInRole(this ClaimsPrincipal claimsPrincipal, Claim roleClaim) + { + return roleClaim.Type == ClaimTypes.Role && claimsPrincipal.IsInRole(roleClaim.Value); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/ClaimsRoleProvider.cs b/src/FunnelWeb/Authentication/Internal/ClaimsRoleProvider.cs new file mode 100644 index 00000000..7a1871ee --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/ClaimsRoleProvider.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using FunnelWeb.DatabaseDeployer; +using FunnelWeb.Model.Authentication; +using FunnelWeb.Settings; +using NHibernate; + +namespace FunnelWeb.Authentication.Internal +{ + public class ClaimsRoleProvider : IRoleProvider + { + private readonly Func upgradeDetector; + private readonly Func settingsProvider; + private readonly Func sessionCallback; + private readonly FormsRoleProvider formsRoleProvider; + + public ClaimsRoleProvider(Func upgradeDetector, Func settingsProvider, Func sessionCallback) + { + this.upgradeDetector = upgradeDetector; + this.settingsProvider = settingsProvider; + this.sessionCallback = sessionCallback; + formsRoleProvider = new FormsRoleProvider(); + } + + private bool UseFormsRoleProvider + { + get { return upgradeDetector().UpdateNeeded() || !settingsProvider().GetSettings().SqlAuthenticationEnabled; } + } + + public bool IsUserInRole(string username, string roleName) + { + return UseFormsRoleProvider + ? formsRoleProvider.IsUserInRole(username, roleName) + : ClaimsPrincipal.IsInRole(roleName); + } + + public string[] GetRolesForUser(string username) + { + var updateNeeded = UseFormsRoleProvider; + + return updateNeeded + ? formsRoleProvider.GetRolesForUser(username) + : FetchRolesForUser(); + } + + public void AddUserToRoles(User user, params Claim[] rolesToAdd) + { + var session = sessionCallback(); + + foreach (var roleToAddTo in rolesToAdd) + { + Claim roleClaim = roleToAddTo; + var role = session.QueryOver().Where(r => r.Name == roleClaim.Value).SingleOrDefault(); + role.Users.Add(user); + user.Roles.Add(role); + session.SaveOrUpdate(role); + } + } + + private static ClaimsPrincipal ClaimsPrincipal + { + get { return ((ClaimsPrincipal)Thread.CurrentPrincipal); } + } + + private string[] FetchRolesForUser() + { + return ClaimsPrincipal.FindAll(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray(); + } + } +} diff --git a/src/FunnelWeb/Authentication/Internal/ClaimsSessionHelper.cs b/src/FunnelWeb/Authentication/Internal/ClaimsSessionHelper.cs new file mode 100644 index 00000000..fc6a11a0 --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/ClaimsSessionHelper.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.IdentityModel.Services; +using System.IdentityModel.Tokens; +using System.Linq; +using System.Security.Claims; +using FunnelWeb.Model.Authentication; + +namespace FunnelWeb.Authentication.Internal +{ + public class ClaimsSessionHelper + { + public static void Login(User user) + { + var claims = new List(new[] + { + new Claim(ClaimTypes.NameIdentifier, user.Username), + new Claim(ClaimTypes.Name, user.Name) + }); + + claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role.Name))); + + var identity = new ClaimsIdentity(claims, "Forms"); + var principal = new ClaimsPrincipal(identity); + + var sessionToken = new SessionSecurityToken(principal); + var authenticationModule = FederatedAuthentication.SessionAuthenticationModule; + authenticationModule.WriteSessionTokenToCookie(sessionToken); + } + + public static void Logout() + { + FederatedAuthentication.SessionAuthenticationModule.SignOut(); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/CustomAuthenticationManager.cs b/src/FunnelWeb/Authentication/Internal/CustomAuthenticationManager.cs new file mode 100644 index 00000000..ba19bd1e --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/CustomAuthenticationManager.cs @@ -0,0 +1,14 @@ +using System.Security.Claims; + +namespace FunnelWeb.Authentication.Internal +{ + public class CustomAuthenticationManager : ClaimsAuthenticationManager + { + // Placeholder for when external authentication is enabled. + + //public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal) + //{ + // return base.Authenticate(resourceName, incomingPrincipal); + //} + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs b/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs new file mode 100644 index 00000000..2e56bef7 --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Operation = FunnelWeb.Authentication.Internal.Authorization.Operation; +using Resource = FunnelWeb.Authentication.Internal.Authorization.Resource; +using Roles = FunnelWeb.Authentication.Internal.Authorization.Roles; + +namespace FunnelWeb.Authentication.Internal +{ + public class CustomAuthorizationManager : ClaimsAuthorizationManager + { + private static AuthorizationSetting[] adminAuthorizations; + private static AuthorizationSetting[] moderatorAuthorizations; + + public override bool CheckAccess(AuthorizationContext authorizationContext) + { + var adminContributions = FilterAuthorizationSettingsForContext(authorizationContext, Roles.Admin, AdminAuthorizations); + var moderatorContributions = FilterAuthorizationSettingsForContext(authorizationContext, Roles.Moderator, ModeratorAuthorizations); + + return adminContributions.Union(moderatorContributions).Any(); + } + + private static IEnumerable FilterAuthorizationSettingsForContext( + AuthorizationContext authorizationContext, + Claim role, + IEnumerable authorizationSettings) + { + return authorizationContext.Principal.IsInRole(role) ? + authorizationSettings.Where(authorizationSetting => + authorizationSetting.Resource.Value == authorizationContext.Resource.First().Value && + authorizationSetting.Operation.Value == authorizationContext.Action.First().Value && + authorizationSetting.Role.Value == role.Value) : + Enumerable.Empty(); + } + + /// + /// Hard coded access rights for the role. + /// + private IEnumerable AdminAuthorizations + { + get + { + return adminAuthorizations ?? + (adminAuthorizations = new[] + { + Allow(Roles.Admin, Operation.View, Resource.Admin.Index), + Allow(Roles.Admin, Operation.View, Resource.Admin.Settings), + Allow(Roles.Admin, Operation.Update, Resource.Admin.Settings), + Allow(Roles.Admin, Operation.View, Resource.Admin.Comments), + Allow(Roles.Admin, Operation.Delete, Resource.Admin.Comment), + Allow(Roles.Admin, Operation.Delete, Resource.Admin.AllSpam), + Allow(Roles.Admin, Operation.Update, Resource.Admin.Spam), + Allow(Roles.Admin, Operation.View, Resource.Admin.Pingbacks), + Allow(Roles.Admin, Operation.Delete, Resource.Admin.Pingback), + Allow(Roles.Admin, Operation.Update, Resource.Admin.Pingback), + Allow(Roles.Admin, Operation.View, Resource.Admin.Tasks), + Allow(Roles.Admin, Operation.View, Resource.Admin.Task), + Allow(Roles.Admin, Operation.View, Resource.Admin.BlogMl), + Allow(Roles.Admin, Operation.Update, Resource.Admin.BlogMl), + Allow(Roles.Admin, Operation.View, Resource.Admin.Pages), + Allow(Roles.Admin, Operation.View, Resource.SqlAuthentications.SqlAuthentication), + Allow(Roles.Admin, Operation.Update, Resource.SqlAuthentications.SqlAuthentication), + Allow(Roles.Admin, Operation.View, Resource.SqlAuthentications.NewAccount), + Allow(Roles.Admin, Operation.Update, Resource.SqlAuthentications.NewAccount), + Allow(Roles.Admin, Operation.View, Resource.SqlAuthentications.Setup), + Allow(Roles.Admin, Operation.Update, Resource.SqlAuthentications.Setup), + Allow(Roles.Admin, Operation.Insert, Resource.SqlAuthentications.AdminAccount), + Allow(Roles.Admin, Operation.Delete, Resource.SqlAuthentications.RemoveRole), + Allow(Roles.Admin, Operation.View, Resource.SqlAuthentications.RoleAdd), + Allow(Roles.Admin, Operation.Update, Resource.SqlAuthentications.RoleAdd), + Allow(Roles.Admin, Operation.View, Resource.Install.Index), + Allow(Roles.Admin, Operation.Update, Resource.Install.ChangeProvider), + Allow(Roles.Admin, Operation.View, Resource.Install.Test), + Allow(Roles.Admin, Operation.Update, Resource.Install.Upgrade) + }); + } + } + + /// + /// Hard coded access rights for the role. + /// + private IEnumerable ModeratorAuthorizations + { + get + { + return moderatorAuthorizations ?? + (moderatorAuthorizations = new[] + { + Allow(Roles.Moderator, Operation.View, Resource.Upload.Index), + Allow(Roles.Moderator, Operation.Update, Resource.Upload.Index), + Allow(Roles.Moderator, Operation.Update, Resource.Upload.CreateDirectory), + Allow(Roles.Moderator, Operation.Update, Resource.Upload.Move), + Allow(Roles.Moderator, Operation.Delete, Resource.Upload.Delete), + Allow(Roles.Moderator, Operation.View, Resource.WikiAdmin.Edit), + Allow(Roles.Moderator, Operation.Update, Resource.WikiAdmin.Edit), + Allow(Roles.Moderator, Operation.Delete, Resource.WikiAdmin.Page) + }); + } + } + + private static AuthorizationSetting Allow(Claim role, string operation, string resource) + { + return new AuthorizationSetting + { + Role = role, + Operation = new Claim(ClaimTypes.Name, operation), + Resource = new Claim(ClaimTypes.Name, resource) + }; + } + + private class AuthorizationSetting + { + public Claim Role { get; set; } + public Claim Operation { get; set; } + public Claim Resource { get; set; } + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/FormsAuthenticator.cs b/src/FunnelWeb/Authentication/Internal/FormsAuthenticator.cs index c9c4e86e..0f6441ca 100644 --- a/src/FunnelWeb/Authentication/Internal/FormsAuthenticator.cs +++ b/src/FunnelWeb/Authentication/Internal/FormsAuthenticator.cs @@ -1,42 +1,54 @@ using System; -using System.Web.Security; +using System.Collections.Generic; +using FunnelWeb.Model.Authentication; using FunnelWeb.Settings; namespace FunnelWeb.Authentication.Internal { - public class FormsAuthenticator : IAuthenticator - { - private readonly Func settingsProvider; - private readonly IConfigSettings configSettings; - - public FormsAuthenticator(Func settingsProvider, IConfigSettings configSettings) - { - this.settingsProvider = settingsProvider; - this.configSettings = configSettings; - } - - public string GetName() - { - return settingsProvider().GetSettings().Author; - } - - public bool AuthenticateAndLogin(string username, string password) - { - var requiredUsername = configSettings.Get("funnelweb.configuration.authentication.username"); - var requiredPassword = configSettings.Get("funnelweb.configuration.authentication.password"); - - var authenticated = username == requiredUsername && password == requiredPassword; - - if (authenticated) - { - FormsAuthentication.SetAuthCookie(username, true); - } - return authenticated; - } - - public void Logout() - { - FormsAuthentication.SignOut(); - } - } + public class FormsAuthenticator : IAuthenticator + { + private readonly Func settingsProvider; + private readonly IConfigSettings configSettings; + + public FormsAuthenticator(Func settingsProvider, IConfigSettings configSettings) + { + this.settingsProvider = settingsProvider; + this.configSettings = configSettings; + } + + public string GetName() + { + return settingsProvider().GetSettings().Author; + } + + public bool AuthenticateAndLogin(string username, string password) + { + var requiredUsername = configSettings.Get("funnelweb.configuration.authentication.username"); + var requiredPassword = configSettings.Get("funnelweb.configuration.authentication.password"); + + if (username == requiredUsername && password == requiredPassword) + { + ClaimsSessionHelper.Login(new User + { + Username = username, + Name = username, + Roles = new List + { + // Hard coded two roles for non-db login. + new Role { Name = Authorization.Roles.Admin.Value }, + new Role { Name = Authorization.Roles.Moderator.Value } + } + }); + + return true; + } + + return false; + } + + public void Logout() + { + ClaimsSessionHelper.Logout(); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/FormsFunnelWebMembership.cs b/src/FunnelWeb/Authentication/Internal/FormsFunnelWebMembership.cs index d106992b..283c7430 100644 --- a/src/FunnelWeb/Authentication/Internal/FormsFunnelWebMembership.cs +++ b/src/FunnelWeb/Authentication/Internal/FormsFunnelWebMembership.cs @@ -5,37 +5,37 @@ namespace FunnelWeb.Authentication.Internal { - public class FormsFunnelWebMembership : IFunnelWebMembership - { - private readonly Func settingsProvider; - private readonly IConfigSettings configSettings; + public class FormsFunnelWebMembership : IFunnelWebMembership + { + private readonly Func settingsProvider; + private readonly IConfigSettings configSettings; - public FormsFunnelWebMembership(Func settingsProvider, IConfigSettings configSettings) - { - this.settingsProvider = settingsProvider; - this.configSettings = configSettings; - } + public FormsFunnelWebMembership(Func settingsProvider, IConfigSettings configSettings) + { + this.settingsProvider = settingsProvider; + this.configSettings = configSettings; + } - public bool HasAdminAccount() - { - return true; - } + public bool HasAdminAccount() + { + return true; + } - public User CreateAccount(string name, string email, string username, string password) - { - throw new NotSupportedException("You cannot create a new Forms Authentication Account"); - } + public User CreateAccount(string name, string email, string username, string password) + { + throw new NotSupportedException("You cannot create a new Forms Authentication Account"); + } - public IEnumerable GetUsers() - { - return new[] - { - new User - { - Name = settingsProvider().GetSettings().Author, - Username = configSettings.Get("funnelweb.configuration.authentication.username") - } - }; - } - } + public IEnumerable GetUsers() + { + return new[] + { + new User + { + Name = settingsProvider().GetSettings().Author, + Username = configSettings.Get("funnelweb.configuration.authentication.username") + } + }; + } + } } diff --git a/src/FunnelWeb/Authentication/Internal/FormsRoleProvider.cs b/src/FunnelWeb/Authentication/Internal/FormsRoleProvider.cs index 4cf68d4a..89e7eeda 100644 --- a/src/FunnelWeb/Authentication/Internal/FormsRoleProvider.cs +++ b/src/FunnelWeb/Authentication/Internal/FormsRoleProvider.cs @@ -1,17 +1,15 @@ -using System; - -namespace FunnelWeb.Authentication.Internal +namespace FunnelWeb.Authentication.Internal { - public class FormsRoleProvider : IRoleProvider - { - public bool IsUserInRole(string username, string roleName) - { - return true; - } + public class FormsRoleProvider : IRoleProvider + { + public bool IsUserInRole(string username, string roleName) + { + return true; + } - public string[] GetRolesForUser(string username) - { - return new[] {"Admin", "Moderator"}; - } - } + public string[] GetRolesForUser(string username) + { + return new[] { Authorization.Roles.Admin.Value, Authorization.Roles.Moderator.Value }; + } + } } \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/SqlAuthenticator.cs b/src/FunnelWeb/Authentication/Internal/SqlAuthenticator.cs deleted file mode 100644 index 03f22d88..00000000 --- a/src/FunnelWeb/Authentication/Internal/SqlAuthenticator.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Web; -using System.Web.Mvc; -using System.Web.Security; -using FunnelWeb.DatabaseDeployer; -using FunnelWeb.Model.Authentication; -using FunnelWeb.Settings; -using NHibernate; - -namespace FunnelWeb.Authentication.Internal -{ - public class SqlAuthenticator : IAuthenticator - { - private readonly FormsAuthenticator formsAuthenticator; - private readonly Func upgradeDetector; - private readonly Func settingsProvider; - private readonly Func sessionFactory; - - public SqlAuthenticator( - FormsAuthenticator formsAuthenticator, - Func upgradeDetector, - Func settingsProvider, - Func sessionFactory) - { - this.formsAuthenticator = formsAuthenticator; - this.upgradeDetector = upgradeDetector; - this.settingsProvider = settingsProvider; - this.sessionFactory = sessionFactory; - } - - public string GetName() - { - return UseFormsAuthentication - ? formsAuthenticator.GetName() - : SqlGetName(); - } - - private static string SqlGetName() - { - var username = ((FormsIdentity) HttpContext.Current.User.Identity).Name; - - var session = DependencyResolver.Current.GetService(); - var user = session.QueryOver() - .Where(u => u.Username == username) - .SingleOrDefault(); - - return user.Name; - } - - public bool AuthenticateAndLogin(string username, string password) - { - return UseFormsAuthentication ? - formsAuthenticator.AuthenticateAndLogin(username, password) : - SqlAuthenticateAndLogin(username, password); - } - - private bool UseFormsAuthentication - { - get - { - return upgradeDetector().UpdateNeeded() || - !settingsProvider().GetSettings().SqlAuthenticationEnabled; - } - } - - private bool SqlAuthenticateAndLogin(string username, string password) - { - var user = sessionFactory().QueryOver() - .Where(u => u.Username == username && u.Password == SqlFunnelWebMembership.HashPassword(password)) - .SingleOrDefault(); - - if (user != null) - { - FormsAuthentication.SetAuthCookie(username, true); - return true; - } - - return false; - } - - public void Logout() - { - formsAuthenticator.Logout(); - } - } -} diff --git a/src/FunnelWeb/Authentication/Internal/SqlClaimsAuthenticator.cs b/src/FunnelWeb/Authentication/Internal/SqlClaimsAuthenticator.cs new file mode 100644 index 00000000..d8610f64 --- /dev/null +++ b/src/FunnelWeb/Authentication/Internal/SqlClaimsAuthenticator.cs @@ -0,0 +1,88 @@ +using System; +using System.Web; +using System.Web.Mvc; +using System.Web.Security; +using FunnelWeb.DatabaseDeployer; +using FunnelWeb.Model.Authentication; +using FunnelWeb.Settings; +using NHibernate; + +namespace FunnelWeb.Authentication.Internal +{ + public class SqlClaimsAuthenticator : IAuthenticator + { + private readonly FormsAuthenticator formsAuthenticator; + private readonly Func upgradeDetector; + private readonly Func settingsProvider; + private readonly Func sessionFactory; + + public SqlClaimsAuthenticator( + FormsAuthenticator formsAuthenticator, + Func upgradeDetector, + Func settingsProvider, + Func sessionFactory) + { + this.formsAuthenticator = formsAuthenticator; + this.upgradeDetector = upgradeDetector; + this.settingsProvider = settingsProvider; + this.sessionFactory = sessionFactory; + } + + public string GetName() + { + return UseFormsAuthentication + ? formsAuthenticator.GetName() + : SqlGetName(); + } + + private static string SqlGetName() + { + var username = ((FormsIdentity)HttpContext.Current.User.Identity).Name; + + var session = DependencyResolver.Current.GetService(); + var user = session.QueryOver() + .Where(u => u.Username == username) + .SingleOrDefault(); + + return user.Name; + } + + public bool AuthenticateAndLogin(string username, string password) + { + return UseFormsAuthentication ? + formsAuthenticator.AuthenticateAndLogin(username, password) : + SqlAuthenticateAndLogin(username, password); + } + + private bool UseFormsAuthentication + { + get + { + return upgradeDetector().UpdateNeeded() || + !settingsProvider().GetSettings().SqlAuthenticationEnabled; + } + } + + private bool SqlAuthenticateAndLogin(string username, string password) + { + var user = sessionFactory() + .QueryOver() + .Where(u => u.Username == username && u.Password == SqlFunnelWebMembership.HashPassword(password)) + .SingleOrDefault(); + + if (user == null) + { + return false; + } + + ClaimsSessionHelper.Login(user); + + return true; + } + + public void Logout() + { + ClaimsSessionHelper.Logout(); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs b/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs index 8543010a..b758c7ee 100644 --- a/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs +++ b/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Web.Configuration; using System.Web.Mvc; using System.Web.Security; @@ -8,44 +7,44 @@ namespace FunnelWeb.Authentication.Internal { - public class SqlFunnelWebMembership : IFunnelWebMembership - { - public bool HasAdminAccount() - { - var count = DependencyResolver.Current.GetService() - .QueryOver() - .JoinQueryOver(x => x.Roles) - .Where(r => r.Name == "Admin") - .RowCount(); + public class SqlFunnelWebMembership : IFunnelWebMembership + { + public bool HasAdminAccount() + { + var count = DependencyResolver.Current.GetService() + .QueryOver() + .JoinQueryOver(x => x.Roles) + .Where(r => r.Name == Authorization.Roles.Admin.Value) + .RowCount(); - return count > 0; - } + return count > 0; + } - public User CreateAccount(string name, string email, string username, string password) - { - var user = new User - { - Name = name, - Email = email, - Password = HashPassword(password), - Username = username - }; + public User CreateAccount(string name, string email, string username, string password) + { + var user = new User + { + Name = name, + Email = email, + Password = HashPassword(password), + Username = username + }; - DependencyResolver.Current.GetService().Save(user); - return user; - } + DependencyResolver.Current.GetService().Save(user); + return user; + } - public IEnumerable GetUsers() - { - return DependencyResolver.Current - .GetService() - .QueryOver() - .List(); - } + public IEnumerable GetUsers() + { + return DependencyResolver.Current + .GetService() + .QueryOver() + .List(); + } - internal static string HashPassword(string password) - { - return FormsAuthentication.HashPasswordForStoringInConfigFile(password, FormsAuthPasswordFormat.SHA1.ToString()); - } - } -} + internal static string HashPassword(string password) + { + return FormsAuthentication.HashPasswordForStoringInConfigFile(password, FormsAuthPasswordFormat.SHA1.ToString()); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/SqlRoleProvider.cs b/src/FunnelWeb/Authentication/Internal/SqlRoleProvider.cs deleted file mode 100644 index af574a60..00000000 --- a/src/FunnelWeb/Authentication/Internal/SqlRoleProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Linq; -using FunnelWeb.DatabaseDeployer; -using FunnelWeb.Model.Authentication; -using FunnelWeb.Settings; -using NHibernate; - -namespace FunnelWeb.Authentication.Internal -{ - public class SqlRoleProvider : IRoleProvider - { - private readonly Func upgradeDetector; - private readonly Func settingsProvider; - private readonly Func sessionCallback; - private readonly FormsRoleProvider formsRoleProvider; - - public SqlRoleProvider(Func upgradeDetector, Func settingsProvider, Func sessionCallback) - { - this.upgradeDetector = upgradeDetector; - this.settingsProvider = settingsProvider; - this.sessionCallback = sessionCallback; - formsRoleProvider = new FormsRoleProvider(); - } - - private bool UseFormsRoleProvider - { - get - { - return upgradeDetector().UpdateNeeded() || - !settingsProvider() - .GetSettings().SqlAuthenticationEnabled; - } - } - - public bool IsUserInRole(string username, string roleName) - { - return UseFormsRoleProvider - ? formsRoleProvider.IsUserInRole(username, roleName) - : CheckIsUserInRole(username, roleName); - } - - private bool CheckIsUserInRole(string username, string roleName) - { - var session = sessionCallback(); - - var checkIsUserInRole = session - .QueryOver() - .Where(u => u.Username == username) - .JoinQueryOver(r => r.Roles) - .Where(r => r.Name == roleName) - .RowCount() == 1; - - return checkIsUserInRole; - } - - public string[] GetRolesForUser(string username) - { - var updateNeeded = UseFormsRoleProvider; - - return updateNeeded - ? formsRoleProvider.GetRolesForUser(username) - : FetchRolesForUser(username); - } - - private string[] FetchRolesForUser(string username) - { - var session = sessionCallback(); - - var roles = session - .QueryOver() - .JoinQueryOver(u => u.Users) - .Where(u => u.Username == username) - .Select(r => r.Name) - .List(); - - return roles.ToArray(); - } - - public void AddUserToRoles(User user, params string[] rolesToAddTo) - { - var session = sessionCallback(); - - foreach (var roleToAddTo in rolesToAddTo) - { - var role = session.QueryOver().Where(r => r.Name == roleToAddTo).SingleOrDefault(); - role.Users.Add(user); - user.Roles.Add(role); - session.SaveOrUpdate(role); - } - } - } -} diff --git a/src/FunnelWeb/FunnelWeb.csproj b/src/FunnelWeb/FunnelWeb.csproj index b401af2e..a8ea220d 100644 --- a/src/FunnelWeb/FunnelWeb.csproj +++ b/src/FunnelWeb/FunnelWeb.csproj @@ -99,6 +99,8 @@ True ..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll + + @@ -114,13 +116,18 @@ Properties\VersionInfo.cs - + + + + + + + + - - diff --git a/src/FunnelWeb/Model/Authentication/Role.cs b/src/FunnelWeb/Model/Authentication/Role.cs index 12a03f69..3d0c1a5b 100644 --- a/src/FunnelWeb/Model/Authentication/Role.cs +++ b/src/FunnelWeb/Model/Authentication/Role.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace FunnelWeb.Model.Authentication { - public class Role - { - public virtual int Id { get; protected set; } - public virtual string Name { get; set; } - public virtual IList Users { get; protected set; } - } -} + public class Role + { + public virtual int Id { get; protected set; } + public virtual string Name { get; set; } + public virtual IList Users { get; protected set; } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Model/Authentication/User.cs b/src/FunnelWeb/Model/Authentication/User.cs index ee7a34d8..ecf7748b 100644 --- a/src/FunnelWeb/Model/Authentication/User.cs +++ b/src/FunnelWeb/Model/Authentication/User.cs @@ -1,20 +1,19 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace FunnelWeb.Model.Authentication { - public class User - { - public User() - { - Roles = new List(); - } + public class User + { + public User() + { + Roles = new List(); + } - public virtual int Id { get; protected set; } - public virtual string Name { get; set; } - public virtual string Email { get; set; } - public virtual string Username { get; set; } - public virtual string Password { get; set; } - public virtual IList Roles { get; protected set; } - } -} + public virtual int Id { get; protected set; } + public virtual string Name { get; set; } + public virtual string Email { get; set; } + public virtual string Username { get; set; } + public virtual string Password { get; set; } + public virtual IList Roles { get; set; } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs b/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs index 75d014ac..782a3628 100644 --- a/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs +++ b/src/FunnelWeb/Providers/File/AzureBlobFileRepository.cs @@ -10,213 +10,226 @@ namespace FunnelWeb.Providers.File { - public class AzureBlobFileRepository : FileRepositoryBase - { - private readonly object containerLock = new object(); - private readonly object clientLock = new object(); - private readonly ISettingsProvider settingsProvider; - private volatile CloudBlobContainer cloudContainer; - private volatile CloudBlobClient client; - - public AzureBlobFileRepository(ISettingsProvider settingsProvider) - { - this.settingsProvider = settingsProvider; - } - - private CloudBlobClient Client - { - get - { - if (client == null) - { - lock (clientLock) - { - if (client == null) - { - - var funnelWebSettings = settingsProvider.GetSettings(); - var storageConnectionString = funnelWebSettings.StorageConnectionString; - var storageAccount = CloudStorageAccount.Parse(storageConnectionString); - client = storageAccount.CreateCloudBlobClient(); - } - } - } - - return client; - } - } - - private CloudBlobContainer Container - { - get - { - if (cloudContainer == null) - { - lock (containerLock) - { - if (cloudContainer == null) - { - var containerReference = Client.GetContainerReference(ContainerName); - - containerReference.CreateIfNotExist(); - containerReference.SetPermissions(new BlobContainerPermissions - { - PublicAccess = BlobContainerPublicAccessType.Blob - }); - cloudContainer = containerReference; - } - } - } - - return cloudContainer; - } - } - - private string ContainerName - { - get - { - var funnelWebSettings = settingsProvider.GetSettings(); - return funnelWebSettings.BlobContainerName.ToLower(); - } - } - - public static string ProviderName - { - get { return "Azure Blob Storage"; } - } - - public override bool IsFile(string path) - { - var reference = Container.GetBlobReference(path); - try - { - //Uses a HEAD request, so this won't use a transaction - reference.FetchAttributes(); - - } - catch (StorageClientException) - { - return false; - } - - return true; - } - - public override FileItem[] GetItems(string path) - { - var listBlobItems = string.IsNullOrEmpty(path) || path == "/" - ? Container.ListBlobs().ToArray() - : Container.GetDirectoryReference(path).ListBlobs().ToArray(); - - return listBlobItems - .OfType() - .Select(ToFileItem) - .Union(listBlobItems.OfType().Select(ToFileItem)) - .ToArray(); - } - - private static FileItem ToFileItem(CloudBlobDirectory dir) - { - var uriParts = dir.Uri.LocalPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); - - return new FileItem - { - Name = uriParts.Last(), - IsDirectory = !dir.Uri.IsFile, - Image = "dir.png", - Path = string.Join("/", uriParts.Skip(2)), - IsPathAbsolute = false, - FileSize = "", - Modified = "", - Extension = "" - }; - } - - private FileItem ToFileItem(CloudBlob blob) - { - var extension = Path.GetExtension(blob.Name); - - var baseUrl = Client.BaseUri + ContainerName; - return new FileItem - { - Extension = extension, - Name = Path.GetFileName(blob.Name), - Path = blob.Uri.ToString().Replace(baseUrl, string.Empty), - IsPathAbsolute = false, - FileSize = blob.Properties.Length.ToFileSizeString(), - Image = GetImage(blob.Name, extension), - Modified = blob.Properties.LastModifiedUtc.ToLocalTime().ToString("dd-MMM-yyyy") - }; - } - - public override void Move(string oldPath, string newPath) - { - throw new NotImplementedException(); - } - - public override void Delete(string filePath) - { - var blob = Container.GetBlobReference(filePath); - try - { - blob.Delete(); - } - catch (StorageClientException) - { - //File doesn't exist, might be a folder - var dir = Container.GetDirectoryReference(filePath); - // If it isn't a directory this will simply be an empty list - foreach (var childBlob in dir.ListBlobs().OfType()) - { - childBlob.Delete(); - } - } - } - - public override void CreateDirectory(string path, string name) - { - var dir = Container.GetDirectoryReference(Path.Combine(path, name).Trim('/')); - var placeholderFile = dir.GetBlobReference("Placeholder.txt"); - placeholderFile.UploadText("Azure does not support directories without files, so this placeholder allows us to create one"); - } - - public override void Save(Stream inputStream, string fullPath, bool unzip) - { - fullPath = fullPath.TrimStart('/'); - //TODO this doesn't work for azure blobs... - if (unzip && IsZipFile(fullPath)) - { - inputStream.Extract(fullPath); - } - else - { - var file = Container.GetBlobReference(fullPath); - file.UploadFromStream(inputStream); - } - } - - //public void Extract(Stream stream, string fullPath) - //{ - // var strings = fullPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); - // var basePath = string.Join("/", strings.Take(strings.Length - 1)); - - // using (var input = stream) - // using (var zipInput = new ZipInputStream(input)) - // { - // ZipEntry entry; - // while ((entry = zipInput.GetNextEntry()) != null) - // { - // var blob = Container.GetBlobReference(Path.Combine(basePath, entry.Name)); - // blob.UploadFromStream(zipInput); - // } - // } - //} - - public override ActionResult Render(string path) - { - string url = Container.GetBlobReference(path).Uri.ToString(); - return new RedirectResult(url); - } - } + public class AzureBlobFileRepository : FileRepositoryBase + { + public const string DevelopmentStorageAccountConnectionString = "UseDevelopmentStorage=true;"; + + private readonly object containerLock = new object(); + private readonly object clientLock = new object(); + private readonly ISettingsProvider settingsProvider; + private volatile CloudBlobContainer cloudContainer; + private volatile CloudBlobClient client; + + public AzureBlobFileRepository(ISettingsProvider settingsProvider) + { + this.settingsProvider = settingsProvider; + } + + private CloudBlobClient Client + { + get + { + if (client != null) + { + return client; + } + + lock (clientLock) + { + if (client != null) + { + return client; + } + + var funnelWebSettings = settingsProvider.GetSettings(); + var storageConnectionString = funnelWebSettings.StorageConnectionString; + + CloudStorageAccount storageAccount = storageConnectionString == DevelopmentStorageAccountConnectionString ? + CloudStorageAccount.DevelopmentStorageAccount : + CloudStorageAccount.Parse(storageConnectionString); + + client = storageAccount.CreateCloudBlobClient(); + } + + return client; + } + } + + private CloudBlobContainer Container + { + get + { + if (cloudContainer != null) + { + return cloudContainer; + } + + lock (containerLock) + { + if (cloudContainer != null) + { + return cloudContainer; + } + + var containerReference = Client.GetContainerReference(ContainerName); + + containerReference.CreateIfNotExist(); + containerReference.SetPermissions(new BlobContainerPermissions + { + PublicAccess = BlobContainerPublicAccessType.Blob + }); + cloudContainer = containerReference; + } + + return cloudContainer; + } + } + + private string ContainerName + { + get + { + var funnelWebSettings = settingsProvider.GetSettings(); + return funnelWebSettings.BlobContainerName.ToLower(); + } + } + + public static string ProviderName + { + get { return "Azure Blob Storage"; } + } + + public override bool IsFile(string path) + { + var reference = Container.GetBlobReference(path); + try + { + //Uses a HEAD request, so this won't use a transaction + reference.FetchAttributes(); + + } + catch (StorageClientException) + { + return false; + } + + return true; + } + + public override FileItem[] GetItems(string path) + { + var listBlobItems = string.IsNullOrEmpty(path) || path == "/" + ? Container.ListBlobs().ToArray() + : Container.GetDirectoryReference(path).ListBlobs().ToArray(); + + return listBlobItems + .OfType() + .Select(ToFileItem) + .Union(listBlobItems.OfType().Select(ToFileItem)) + .ToArray(); + } + + private static FileItem ToFileItem(CloudBlobDirectory dir) + { + var uriParts = dir.Uri.LocalPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + return new FileItem + { + Name = uriParts.Last(), + IsDirectory = !dir.Uri.IsFile, + Image = "dir.png", + Path = string.Join("/", uriParts.Skip(2)), + IsPathAbsolute = false, + FileSize = "", + Modified = "", + Extension = "" + }; + } + + private FileItem ToFileItem(CloudBlob blob) + { + var extension = Path.GetExtension(blob.Name); + + var baseUrl = Client.BaseUri + ContainerName; + return new FileItem + { + Extension = extension, + Name = Path.GetFileName(blob.Name), + Path = blob.Uri.ToString().Replace(baseUrl, string.Empty), + IsPathAbsolute = false, + FileSize = blob.Properties.Length.ToFileSizeString(), + Image = GetImage(blob.Name, extension), + Modified = blob.Properties.LastModifiedUtc.ToLocalTime().ToString("dd-MMM-yyyy") + }; + } + + public override void Move(string oldPath, string newPath) + { + throw new NotImplementedException(); + } + + public override void Delete(string filePath) + { + var blob = Container.GetBlobReference(filePath); + try + { + blob.Delete(); + } + catch (StorageClientException) + { + //File doesn't exist, might be a folder + var dir = Container.GetDirectoryReference(filePath); + // If it isn't a directory this will simply be an empty list + foreach (var childBlob in dir.ListBlobs().OfType()) + { + childBlob.Delete(); + } + } + } + + public override void CreateDirectory(string path, string name) + { + var dir = Container.GetDirectoryReference(Path.Combine(path, name).Trim('/')); + var placeholderFile = dir.GetBlobReference("Placeholder.txt"); + placeholderFile.UploadText("Azure does not support directories without files, so this placeholder allows us to create one"); + } + + public override void Save(Stream inputStream, string fullPath, bool unzip) + { + fullPath = fullPath.TrimStart('/'); + //TODO this doesn't work for azure blobs... + if (unzip && IsZipFile(fullPath)) + { + inputStream.Extract(fullPath); + } + else + { + var file = Container.GetBlobReference(fullPath); + file.UploadFromStream(inputStream); + } + } + + //public void Extract(Stream stream, string fullPath) + //{ + // var strings = fullPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); + // var basePath = string.Join("/", strings.Take(strings.Length - 1)); + + // using (var input = stream) + // using (var zipInput = new ZipInputStream(input)) + // { + // ZipEntry entry; + // while ((entry = zipInput.GetNextEntry()) != null) + // { + // var blob = Container.GetBlobReference(Path.Combine(basePath, entry.Name)); + // blob.UploadFromStream(zipInput); + // } + // } + //} + + public override ActionResult Render(string path) + { + string url = Container.GetBlobReference(path).Uri.ToString(); + return new RedirectResult(url); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb/Settings/SqlAuthenticationSettings.cs b/src/FunnelWeb/Settings/SqlAuthenticationSettings.cs index 94ebe7bf..5e9a48ea 100644 --- a/src/FunnelWeb/Settings/SqlAuthenticationSettings.cs +++ b/src/FunnelWeb/Settings/SqlAuthenticationSettings.cs @@ -1,14 +1,13 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; namespace FunnelWeb.Settings { - public class SqlAuthSettings : ISettings - { - [DisplayName("Sql Authentication Enabled")] - [DefaultValue(false)] - [Description("True if sql authentication is enabled")] - [SettingStorage(StorageLocation.Database, "sql-authentication")] - public bool SqlAuthenticationEnabled { get; set; } - } -} + public class SqlAuthSettings : ISettings + { + [DisplayName("Sql Authentication Enabled")] + [DefaultValue(false)] + [Description("True if sql authentication is enabled")] + [SettingStorage(StorageLocation.Database, "sql-authentication")] + public bool SqlAuthenticationEnabled { get; set; } + } +} \ No newline at end of file From 3bf57027c63cfec339318fca294ce5a752c881be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Mon, 14 Oct 2013 14:35:11 +0200 Subject: [PATCH 04/13] Added a comment. --- .../Authentication/Internal/CustomAuthorizationManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs b/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs index 2e56bef7..02560f4a 100644 --- a/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs +++ b/src/FunnelWeb/Authentication/Internal/CustomAuthorizationManager.cs @@ -7,6 +7,9 @@ namespace FunnelWeb.Authentication.Internal { + /// + /// This class implements mappnigs between database roles and code claims to enable claims based auth in the app. + /// public class CustomAuthorizationManager : ClaimsAuthorizationManager { private static AuthorizationSetting[] adminAuthorizations; From 66e8512174766187797c64cb5ecdaa1b7225a8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Mon, 14 Oct 2013 15:16:41 +0200 Subject: [PATCH 05/13] Fixed the user when running the tests. --- src/FunnelWeb.Tests/App.config | 88 ++++-- src/FunnelWeb.Tests/FunnelWeb.Tests.csproj | 4 + .../Admin/Controllers/AdminControllerTests.cs | 251 +++++++++--------- .../TestAuthenticationAndAuthorization.cs | 21 ++ .../Controllers/UploadControllerTests.cs | 2 + .../Controllers/WikiAdminControllerTests.cs | 2 + src/FunnelWeb.Tests/packages.config | 1 + src/FunnelWeb.Web/App_Start/RouteConfig.cs | 115 ++++---- .../Admin/Controllers/LoginController.cs | 105 ++++---- src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs | 113 ++++---- .../Areas/Admin/Scripts/Account.js | 9 + .../Controllers/LoginController.cs | 12 + src/FunnelWeb.Web/FunnelWeb.Web.csproj | 3 + src/FunnelWeb.Web/Views/Login/Index.cshtml | 21 ++ 14 files changed, 435 insertions(+), 312 deletions(-) create mode 100644 src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs create mode 100644 src/FunnelWeb.Web/Areas/Admin/Scripts/Account.js create mode 100644 src/FunnelWeb.Web/Controllers/LoginController.cs create mode 100644 src/FunnelWeb.Web/Views/Login/Index.cshtml diff --git a/src/FunnelWeb.Tests/App.config b/src/FunnelWeb.Tests/App.config index 96a36981..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/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj index f9a66d54..4a86df2b 100644 --- a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj +++ b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj @@ -88,6 +88,9 @@ 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 + @@ -181,6 +184,7 @@ + diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs index 8e6b36c3..7d23f823 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Security.Principal; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Model.Repositories; @@ -10,127 +11,129 @@ 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(); + + 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/TestAuthenticationAndAuthorization.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs new file mode 100644 index 00000000..fb7d33e5 --- /dev/null +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs @@ -0,0 +1,21 @@ +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() + { + IEnumerable claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "tester"), + new Claim(ClaimTypes.Name, "Test User"), + new Claim(ClaimTypes.Role, Authorization.Roles.Admin.Value), + new Claim(ClaimTypes.Role, Authorization.Roles.Moderator.Value) + }; + Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Test")); + } } +} \ 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 3bdaf726..d75bbb79 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs @@ -20,6 +20,8 @@ public class UploadControllerTests : ControllerTests [SetUp] public void SetUp() { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + var settingsProvider = Substitute.For(); settingsProvider.GetSettings().Returns(new FunnelWebSettings()); Controller = new UploadController diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs index ca625745..9f2e3dc1 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs @@ -20,6 +20,8 @@ public class WikiAdminControllerTests : ControllerTests [SetUp] public void Setup() { + TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + AdminController = new WikiAdminController { Repository = Repository = Substitute.For(), diff --git a/src/FunnelWeb.Tests/packages.config b/src/FunnelWeb.Tests/packages.config index a7a3fcf0..2d694d2d 100644 --- a/src/FunnelWeb.Tests/packages.config +++ b/src/FunnelWeb.Tests/packages.config @@ -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..bb8bb2fa 100644 --- a/src/FunnelWeb.Web/App_Start/RouteConfig.cs +++ b/src/FunnelWeb.Web/App_Start/RouteConfig.cs @@ -2,72 +2,81 @@ 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).FullName } + //); - // 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/Areas/Admin/Controllers/LoginController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs index a02c340c..2384ed3d 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs @@ -6,56 +6,57 @@ 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; + + 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("~/")); + } + } } \ 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 e81287b5..62c905c5 100644 --- a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs +++ b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs @@ -3,71 +3,74 @@ using System.Linq; using System.Web; 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).FullName } + ); - 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).FullName } + ); - 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" } + ); - context.MapRoute( - "Claims_List", - "admin/claims", - new { controller = "Claims", action = "Index" } - ); + 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 } - ); - } - } + //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/Scripts/Account.js b/src/FunnelWeb.Web/Areas/Admin/Scripts/Account.js new file mode 100644 index 00000000..b3ee7771 --- /dev/null +++ b/src/FunnelWeb.Web/Areas/Admin/Scripts/Account.js @@ -0,0 +1,9 @@ +// +// Code generated by Identity and Access VS Package +// + +function ShowSigninPage(IPs) { + $.each(IPs, function (i, ip) { + $("#IPDiv").append('' + ip.Name + '
'); + }); +}; diff --git a/src/FunnelWeb.Web/Controllers/LoginController.cs b/src/FunnelWeb.Web/Controllers/LoginController.cs new file mode 100644 index 00000000..232586e5 --- /dev/null +++ b/src/FunnelWeb.Web/Controllers/LoginController.cs @@ -0,0 +1,12 @@ +using System.Web.Mvc; + +namespace FunnelWeb.Web.Controllers +{ + public class LoginController : Controller + { + public ActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/FunnelWeb.Web.csproj b/src/FunnelWeb.Web/FunnelWeb.Web.csproj index d7c6acf6..6809b032 100644 --- a/src/FunnelWeb.Web/FunnelWeb.Web.csproj +++ b/src/FunnelWeb.Web/FunnelWeb.Web.csproj @@ -260,6 +260,7 @@ + @@ -280,6 +281,7 @@ + @@ -495,6 +497,7 @@ Designer + Web.config diff --git a/src/FunnelWeb.Web/Views/Login/Index.cshtml b/src/FunnelWeb.Web/Views/Login/Index.cshtml new file mode 100644 index 00000000..6921a4a7 --- /dev/null +++ b/src/FunnelWeb.Web/Views/Login/Index.cshtml @@ -0,0 +1,21 @@ +@model dynamic +@{ + ViewBag.Title = "FunnelWeb Login"; + Layout = "~/Areas/Admin/Views/Shared/_Private.cshtml"; +} + + + + + + @ViewBag.Title + + +
+
+
+ + + +@Scripts.Render("~/scripts/Account.js"); + From 2dde1a05a1367c61aec050f8f0c61fbbbc59ac80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Wed, 16 Oct 2013 22:52:01 +0200 Subject: [PATCH 06/13] Added External Authentication with Widows Azure Access Control Service! --- ...lWeb.Extensions.CommentNotification.csproj | 8 +- .../packages.config | 2 +- src/FunnelWeb.Tests/FunnelWeb.Tests.csproj | 21 +- src/FunnelWeb.Tests/LifetimeJustifications.cs | 362 ++++++++--------- .../Admin/Controllers/AdminControllerTests.cs | 2 +- .../Areas/Admin/Controllers/CustomResolver.cs | 42 ++ .../TestAuthenticationAndAuthorization.cs | 21 +- .../Controllers/UploadControllerTests.cs | 1 + .../Controllers/WikiAdminControllerTests.cs | 16 +- .../Web/Controllers/ControllerTests.cs | 120 +++--- .../Web/Controllers/LoginControllerTests.cs | 22 ++ .../Web/Controllers/WikiControllerTests.cs | 171 ++++---- src/FunnelWeb.Tests/Web/RoutingTests.cs | 2 +- src/FunnelWeb.Tests/packages.config | 4 +- src/FunnelWeb.Web/App_Start/RouteConfig.cs | 18 +- .../Authentication/AuthenticationModule.cs | 4 + .../Authentication/FunnelWebRoleProvider.cs | 72 ---- .../Extensions/MarkupExtensions.cs | 11 + .../ViewDataDictionaryExtensions.cs | 13 - .../Application/Mvc/Binders/BindersModule.cs | 30 +- .../Admin/Controllers/AdminController.cs | 59 ++- .../Admin/Controllers/InstallController.cs | 8 +- .../Admin/Controllers/LoginController.cs | 5 +- .../SqlAuthenticationController.cs | 28 +- .../Admin/Controllers/UploadController.cs | 10 +- .../Admin/Controllers/WikiAdminController.cs | 6 +- src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs | 10 +- .../Admin/Views/Admin/AcsSettings.cshtml | 63 +++ .../Areas/Admin/Views/Admin/Index.cshtml | 6 + .../Areas/Admin/Views/Admin/Settings.cshtml | 16 +- .../Areas/Admin/Views/Shared/_Private.cshtml | 5 +- .../Controllers/LoginController.cs | 75 +++- .../Controllers/WikiController.cs | 318 ++++++++------- src/FunnelWeb.Web/FunnelWeb.Web.csproj | 21 +- src/FunnelWeb.Web/Global.asax.cs | 170 ++++---- src/FunnelWeb.Web/Properties/AssemblyInfo.cs | 5 +- .../{Areas/Admin => }/Scripts/Account.js | 0 src/FunnelWeb.Web/Views/Login/Index.cshtml | 29 +- .../Shared/DisplayTemplates/Comment.cshtml | 2 +- .../Shared/DisplayTemplates/Comments.cshtml | 40 +- .../DisplayTemplates/EntrySummaries.cshtml | 2 +- .../Views/Shared/PageTemplates/Default.cshtml | 121 +++--- .../Views/Shared/_EditComments.cshtml | 107 ++--- .../Views/Shared/_EntryTools.cshtml | 7 +- src/FunnelWeb.Web/Views/Shared/_Mobile.cshtml | 2 +- src/FunnelWeb.Web/Views/Shared/_Public.cshtml | 186 ++++----- src/FunnelWeb.Web/Views/Wiki/PageModel.cs | 74 ++-- src/FunnelWeb.Web/Web.config | 26 +- src/FunnelWeb.Web/packages.config | 5 +- src/FunnelWeb.sln.DotSettings | 4 - .../Authentication/Internal/Authorization.cs | 31 +- .../Internal/AuthorizationService.cs | 138 +++++++ .../Internal/ClaimsPrincipalExtensions.cs | 12 - .../Internal/ClaimsRoleProvider.cs | 2 +- .../Internal/ClaimsSessionHelper.cs | 32 +- .../Internal/CustomAuthenticationManager.cs | 15 +- .../Internal/CustomAuthorizationManager.cs | 116 +----- .../FederatedAuthenticationService.cs | 36 ++ .../Internal/FormsAuthenticator.cs | 14 +- .../Internal/FormsRoleProvider.cs | 2 +- .../Internal/IAuthorizationService.cs | 29 ++ .../IFederatedAuthenticationService.cs | 10 + .../Authentication/Internal/Operation.cs | 40 ++ .../Internal/PrincipalExtensions.cs | 38 ++ .../Authentication/Internal/Resource.cs | 40 ++ src/FunnelWeb/Authentication/Internal/Role.cs | 40 ++ .../Internal/SqlClaimsAuthenticator.cs | 13 +- .../Internal/SqlFunnelWebMembership.cs | 4 +- .../DatabaseDeployer/Scripts/Script0033.sql | 19 + src/FunnelWeb/FunnelWeb.csproj | 72 +++- .../Settings/AccessControlServiceSettings.cs | 37 ++ .../FederatedAuthenticationConfigurator.cs | 81 ++++ src/FunnelWeb/Settings/FunnelWebSettings.cs | 2 +- .../IFederatedAuthenticationConfigurator.cs | 10 + src/FunnelWeb/Settings/ISettingsProvider.cs | 12 +- src/FunnelWeb/Settings/SettingsModule.cs | 66 ++-- src/FunnelWeb/Settings/SettingsProvider.cs | 366 +++++++++--------- src/FunnelWeb/packages.config | 15 +- 78 files changed, 2162 insertions(+), 1482 deletions(-) create mode 100644 src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs create mode 100644 src/FunnelWeb.Tests/Web/Controllers/LoginControllerTests.cs delete mode 100644 src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs delete mode 100644 src/FunnelWeb.Web/Application/Extensions/ViewDataDictionaryExtensions.cs create mode 100644 src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml rename src/FunnelWeb.Web/{Areas/Admin => }/Scripts/Account.js (100%) delete mode 100644 src/FunnelWeb.sln.DotSettings create mode 100644 src/FunnelWeb/Authentication/Internal/AuthorizationService.cs delete mode 100644 src/FunnelWeb/Authentication/Internal/ClaimsPrincipalExtensions.cs create mode 100644 src/FunnelWeb/Authentication/Internal/FederatedAuthenticationService.cs create mode 100644 src/FunnelWeb/Authentication/Internal/IAuthorizationService.cs create mode 100644 src/FunnelWeb/Authentication/Internal/IFederatedAuthenticationService.cs create mode 100644 src/FunnelWeb/Authentication/Internal/Operation.cs create mode 100644 src/FunnelWeb/Authentication/Internal/PrincipalExtensions.cs create mode 100644 src/FunnelWeb/Authentication/Internal/Resource.cs create mode 100644 src/FunnelWeb/Authentication/Internal/Role.cs create mode 100644 src/FunnelWeb/DatabaseDeployer/Scripts/Script0033.sql create mode 100644 src/FunnelWeb/Settings/AccessControlServiceSettings.cs create mode 100644 src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs create mode 100644 src/FunnelWeb/Settings/IFederatedAuthenticationConfigurator.cs diff --git a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj index 8702aa9b..9603d6a5 100644 --- a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj +++ b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj @@ -36,11 +36,9 @@ 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/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj index 4a86df2b..b5538229 100644 --- a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj +++ b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj @@ -38,11 +38,13 @@ 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 @@ -94,14 +96,7 @@ - - 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 @@ -184,10 +179,12 @@ + + diff --git a/src/FunnelWeb.Tests/LifetimeJustifications.cs b/src/FunnelWeb.Tests/LifetimeJustifications.cs index 7a4d0f53..43e15789 100644 --- a/src/FunnelWeb.Tests/LifetimeJustifications.cs +++ b/src/FunnelWeb.Tests/LifetimeJustifications.cs @@ -31,183 +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."); - 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 7d23f823..fb01e1bc 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Security.Principal; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Model.Repositories; @@ -21,6 +20,7 @@ public class AdminControllerTests : ControllerTests public void SetUp() { TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + CustomResolver.Initiate(); Controller = new AdminController { 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 index fb7d33e5..4f1d9975 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/TestAuthenticationAndAuthorization.cs @@ -7,15 +7,26 @@ namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers { public static class TestAuthenticationAndAuthorization { - public static void SetTestUserToCurrentPrincipal() + public static void SetTestUserToCurrentPrincipal(bool isAuthenticated = true) { IEnumerable claims = new[] { new Claim(ClaimTypes.NameIdentifier, "tester"), new Claim(ClaimTypes.Name, "Test User"), - new Claim(ClaimTypes.Role, Authorization.Roles.Admin.Value), - new Claim(ClaimTypes.Role, Authorization.Roles.Moderator.Value) + Authorization.Roles.Admin, + Authorization.Roles.Moderator }; - Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Test")); - } } + + 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 d75bbb79..7e4d1554 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/UploadControllerTests.cs @@ -21,6 +21,7 @@ public class UploadControllerTests : ControllerTests public void SetUp() { TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + CustomResolver.Initiate(); var settingsProvider = Substitute.For(); settingsProvider.GetSettings().Returns(new FunnelWebSettings()); diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs index 9f2e3dc1..b1f99cc9 100644 --- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs +++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/WikiAdminControllerTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Security.Principal; using System.Web.Mvc; using FunnelWeb.Model; using FunnelWeb.Repositories; @@ -20,18 +19,13 @@ public class WikiAdminControllerTests : ControllerTests [SetUp] public void Setup() { - TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal(); + CustomResolver.Initiate(); AdminController = new WikiAdminController - { - Repository = Repository = Substitute.For(), - ControllerContext = ControllerContext = ControllerContext - }; - - Identity = Substitute.For(); - User = Substitute.For(); - User.Identity.Returns(Identity); - ControllerContext.HttpContext.User.Returns(User); + { + Repository = Repository = Substitute.For(), + ControllerContext = ControllerContext = ControllerContext + }; } [Test] 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 2d694d2d..219bb229 100644 --- a/src/FunnelWeb.Tests/packages.config +++ b/src/FunnelWeb.Tests/packages.config @@ -1,7 +1,7 @@  - - + + diff --git a/src/FunnelWeb.Web/App_Start/RouteConfig.cs b/src/FunnelWeb.Web/App_Start/RouteConfig.cs index bb8bb2fa..3f37760e 100644 --- a/src/FunnelWeb.Web/App_Start/RouteConfig.cs +++ b/src/FunnelWeb.Web/App_Start/RouteConfig.cs @@ -15,12 +15,18 @@ public static void RegisterRoutes(RouteCollection routes) routes.IgnoreRoute("pingback"); // Login - //routes.MapRoute( - // name: "Login", - // url: "login", - // defaults: new { controller = "Login", action = "Index" }, - // namespaces: new[] { typeof(LoginController).FullName } - //); + 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 } + ); // Feeds routes.MapLowerCaseRoute("feed", new { controller = "Feed", action = "Feed", feedName = (string)null }); diff --git a/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs index dcec5bc6..6c263c08 100644 --- a/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs +++ b/src/FunnelWeb.Web/Application/Authentication/AuthenticationModule.cs @@ -52,6 +52,10 @@ protected override void Load(ContainerBuilder builder) .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 3674d305..00000000 --- a/src/FunnelWeb.Web/Application/Authentication/FunnelWebRoleProvider.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading; -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 ((ClaimsPrincipal)Thread.CurrentPrincipal).FindAll(c => c.Type == ClaimTypes.Role).Any(c => c.Value == roleName); - //return DependencyResolver.Current.GetService().IsUserInRole(username, roleName); - } - - public override string[] GetRolesForUser(string username) - { - return ((ClaimsPrincipal)Thread.CurrentPrincipal).FindAll(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray(); - //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 1daa509a..5e298955 100644 --- a/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs +++ b/src/FunnelWeb.Web/Application/Extensions/MarkupExtensions.cs @@ -264,6 +264,17 @@ public static FunnelWebSettings Settings(this HtmlHelper helper) 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) 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/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/Areas/Admin/Controllers/AdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs index 8c7f197a..0b964f5a 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs @@ -27,6 +27,7 @@ namespace FunnelWeb.Web.Areas.Admin.Controllers 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; } @@ -36,13 +37,13 @@ public class AdminController : Controller public Func> FileRepositoriesInfo { get; set; } // ReSharper restore UnusedAutoPropertyAccessor.Global - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.Index)] + [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.Operation.View, Resource = Authorization.Resource.Admin.Settings)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Settings)] public virtual ActionResult Settings() { var settings = SettingsProvider.GetSettings(); @@ -52,7 +53,7 @@ public virtual ActionResult Settings() } [HttpPost] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Admin.Settings)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Settings)] public virtual ActionResult Settings(FunnelWebSettings settings) { ViewBag.Themes = ThemeProvider.GetThemes(); @@ -68,7 +69,35 @@ public virtual ActionResult Settings(FunnelWebSettings settings) return RedirectToAction("Settings", "Admin").AndFlash("Your changes have been saved"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.Comments)] + [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); + } + + 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; @@ -77,7 +106,7 @@ public virtual ActionResult Comments(int? pageNumber) return View(new CommentsModel(page, comments)); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.Admin.Comment)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.Comment)] public virtual ActionResult DeleteComment(int id) { var item = Repository.Get(id); @@ -86,7 +115,7 @@ public virtual ActionResult DeleteComment(int id) return RedirectToAction("Comments", "Admin"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.Admin.AllSpam)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.AllSpam)] public virtual ActionResult DeleteAllSpam() { var comments = Repository.Find(new GetSpamQuery()).ToList(); @@ -99,7 +128,7 @@ public virtual ActionResult DeleteAllSpam() return RedirectToAction("Comments", "Admin"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Admin.Spam)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Spam)] public virtual ActionResult ToggleSpam(int id) { var item = Repository.Get(id); @@ -111,14 +140,14 @@ public virtual ActionResult ToggleSpam(int id) return RedirectToAction("Comments", "Admin"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.Pingbacks)] + [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.Operation.Delete, Resource = Authorization.Resource.Admin.Pingback)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Admin.Pingback)] public virtual ActionResult DeletePingback(int id) { var item = Repository.Get(id); @@ -126,7 +155,7 @@ public virtual ActionResult DeletePingback(int id) return RedirectToAction("Pingbacks", "Admin"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Admin.Pingback)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Admin.Pingback)] public virtual ActionResult TogglePingbackSpam(int id) { var item = Repository.Get(id); @@ -137,28 +166,28 @@ public virtual ActionResult TogglePingbackSpam(int id) return RedirectToAction("Pingbacks", "Admin"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.Tasks)] + [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.Operation.View, Resource = Authorization.Resource.Admin.Task)] + [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.Operation.View, Resource = Authorization.Resource.Admin.BlogMl)] + [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.Operation.Update, Resource = Authorization.Resource.Admin.BlogMl)] + [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)) @@ -175,7 +204,7 @@ public virtual ActionResult BlogMlImport(FileUpload upload) return RedirectToAction("Task", new { id }); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Admin.Pages)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Admin.Pages)] public virtual ActionResult PageList(EntriesSortColumn? sort, bool? asc) { if (sort == null) diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs index 4cff6454..18c2bf9a 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs @@ -32,7 +32,7 @@ public InstallController(Func> databaseProvider this.databaseProvidersInfo = databaseProvidersInfo; } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Install.Index)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Install.Index)] public virtual ActionResult Index() { var connectionString = ConnectionStringSettings.ConnectionString; @@ -75,7 +75,7 @@ public virtual ActionResult Index() return View("Index", model); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Install.ChangeProvider)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Install.ChangeProvider)] public ActionResult ChangeProvider(string databaseProvider) { var provider = databaseProvidersInfo().GetProviderByName(databaseProvider); @@ -91,7 +91,7 @@ public ActionResult ChangeProvider(string databaseProvider) [HttpPost] [ActionName("test")] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Install.Test)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Install.Test)] public virtual ActionResult Test(string connectionString, string schema) { ConnectionStringSettings.ConnectionString = connectionString; @@ -102,7 +102,7 @@ public virtual ActionResult Test(string connectionString, string schema) } [HttpPost] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Install.Upgrade)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Install.Upgrade)] public virtual ActionResult Upgrade() { var writer = new StringWriter(); diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs index 2384ed3d..f7824975 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/LoginController.cs @@ -1,6 +1,7 @@ using System; using System.Web.Mvc; using FunnelWeb.Authentication; +using FunnelWeb.Authentication.Internal; using FunnelWeb.Settings; using FunnelWeb.Web.Areas.Admin.Views.Login; @@ -12,13 +13,13 @@ public class LoginController : Controller { readonly Lazy bootstrapSettings; + public IAuthenticator Authenticator { get; set; } + public LoginController(Lazy bootstrapSettings) { this.bootstrapSettings = bootstrapSettings; } - public IAuthenticator Authenticator { get; set; } - [HttpGet] public virtual ActionResult Login(LoginModel model) { diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs index c985c8eb..1b926c18 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/SqlAuthenticationController.cs @@ -41,7 +41,7 @@ public SqlAuthenticationController( sqlAuthSettings = this.settingsProvider.GetSettings(); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.SqlAuthentications.SqlAuthentication)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] public ActionResult Index() { var users = sqlAuthSettings.SqlAuthenticationEnabled ? sqlMembership.GetUsers() : Enumerable.Empty(); @@ -54,7 +54,7 @@ public ActionResult Index() return View(indexModel); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.SqlAuthentication)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] public ActionResult EnableSqlAuthentication() { if (!sqlMembership.HasAdminAccount()) @@ -69,7 +69,7 @@ public ActionResult EnableSqlAuthentication() return RedirectToAction("Index"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.SqlAuthentication)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.SqlAuthentication)] public ActionResult DisableSqlAuthentication() { if (!sqlMembership.HasAdminAccount()) @@ -84,14 +84,14 @@ public ActionResult DisableSqlAuthentication() } [HttpGet] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.SqlAuthentications.NewAccount)] + [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.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.NewAccount)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.NewAccount)] public ActionResult NewAccount(NewUser user) { if (user.Password != user.RepeatPassword) @@ -107,7 +107,7 @@ public ActionResult NewAccount(NewUser user) } [HttpGet] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.SqlAuthentications.Setup)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.Setup)] public ActionResult Setup() { var setupModel = new SetupModel @@ -119,13 +119,13 @@ public ActionResult Setup() } [HttpPost] - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.Setup)] + [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.Operation.Insert, Resource = Authorization.Resource.SqlAuthentications.AdminAccount)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Insert, Resource = Authorization.Resources.SqlAuthentications.AdminAccount)] public ActionResult CreateAdminAccount(SetupModel setupModel) { if (!sqlMembership.HasAdminAccount()) @@ -138,7 +138,7 @@ public ActionResult CreateAdminAccount(SetupModel setupModel) } var user = sqlMembership.CreateAccount(setupModel.Name, setupModel.Email, setupModel.Username, setupModel.Password); - claimsRoleProvider.AddUserToRoles(user, Authorization.Roles.Admin, Authorization.Roles.Moderator); + claimsRoleProvider.AddUserToRoles(user, Authorization.Roles.Admin, Authorization.Roles.Moderator, Authorization.Roles.Guest); } sqlAuthSettings.SqlAuthenticationEnabled = true; @@ -148,7 +148,7 @@ public ActionResult CreateAdminAccount(SetupModel setupModel) return RedirectToAction("Index"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.SqlAuthentications.RemoveRole)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.SqlAuthentications.RemoveRole)] public ActionResult RemoveRole(int userId, int roleId) { var user = sessionFactory().Get(userId); @@ -164,7 +164,7 @@ public ActionResult RemoveRole(int userId, int roleId) return RedirectToAction("Index"); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.SqlAuthentications.RoleAdd)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.SqlAuthentications.RoleAdd)] public ActionResult AddRole(int userId) { var session = sessionFactory(); @@ -174,7 +174,7 @@ public ActionResult AddRole(int userId) .SingleOrDefault(); var roles = session - .QueryOver() + .QueryOver() .List() .Except(user.Roles) .ToList(); @@ -182,7 +182,7 @@ public ActionResult AddRole(int userId) return View(new AddRoleModel { User = user, Roles = roles }); } - [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.SqlAuthentications.RoleAdd)] + [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.SqlAuthentications.RoleAdd)] public ActionResult AddUserToRole(int userId, int roleId) { var session = sessionFactory(); @@ -195,7 +195,7 @@ public ActionResult AddUserToRole(int userId, int roleId) if (user.Roles.SingleOrDefault(r => r.Id == roleId) == null) { var role = session - .Get(roleId); + .Get(roleId); user.Roles.Add(role); role.Users.Add(user); } diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs index 52b6ab10..0ccbbb5b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/UploadController.cs @@ -17,7 +17,7 @@ public class UploadController : Controller public IFileRepository FileRepository { get; set; } public ISettingsProvider SettingsProvider { get; set; } - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.Upload.Index)] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.View, Resource = Authorization.Resources.Upload.Index)] public virtual ActionResult Index(string path) { path = path ?? string.Empty; @@ -33,7 +33,7 @@ public virtual ActionResult Index(string path) return View(); } - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Upload.Index)] + [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); @@ -41,21 +41,21 @@ public virtual ActionResult Upload(string path, bool? unzip, FileUpload upload) return RedirectToAction("Index", "Upload", new { path }); } - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Upload.CreateDirectory)] + [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, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.Upload.Move)] + [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, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.Upload.Delete)] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.Upload.Delete)] public virtual ActionResult Delete(string path, string filePath) { FileRepository.Delete(filePath); diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs index 50937630..c93f1e38 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/WikiAdminController.cs @@ -30,7 +30,7 @@ public class WikiAdminController : Controller // ReSharper disable once UnusedAutoPropertyAccessor.Global public IEventPublisher EventPublisher { get; set; } - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.View, Resource = Authorization.Resource.WikiAdmin.Edit)] + [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(); @@ -66,7 +66,7 @@ public virtual ActionResult Edit(PageName page, int? revertToRevision) } [HttpPost] - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Update, Resource = Authorization.Resource.WikiAdmin.Edit)] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.WikiAdmin.Edit)] public virtual ActionResult Edit(EntryRevision model) { model.AllTags = Repository.FindAll(); @@ -159,7 +159,7 @@ private List GetEditTags(EntryRevision model) } [HttpPost] - [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operation.Delete, Resource = Authorization.Resource.WikiAdmin.Page)] + [Authorize, ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Delete, Resource = Authorization.Resources.WikiAdmin.Page)] public virtual ActionResult DeletePage(int id) { Repository.Remove(Repository.Get(id)); diff --git a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs index 62c905c5..1978f0c4 100644 --- a/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs +++ b/src/FunnelWeb.Web/Areas/Admin/RouteConfig.cs @@ -1,8 +1,4 @@ -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 @@ -15,14 +11,14 @@ internal static void RegisterRoutes(AreaRegistrationContext context) "Admin_Logout", "admin/logout", new { controller = "Login", action = "Logout" }, - new[] { typeof(LoginController).FullName } + new[] { typeof(LoginController).Namespace } ); context.MapRoute( "Admin_Login", "admin/login/{action}", new { controller = "Login", action = "Login" }, - new[] { typeof(LoginController).FullName } + new[] { typeof(LoginController).Namespace } ); context.MapRoute( 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..713c8a25 --- /dev/null +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml @@ -0,0 +1,63 @@ +@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.LabelFor(m => m.Realm) +
+
+ @Html.TextBoxFor(m => m.Realm) + @Html.ValidationMessageFor(m => m.Realm) + @Html.HintFor(m => m.Realm) +
+ +
+ @Html.LabelFor(m => m.IssuerThumbprint) +
+
+ @Html.TextBoxFor(m => m.IssuerThumbprint) + @Html.ValidationMessageFor(m => m.IssuerThumbprint) + @Html.HintFor(m => m.IssuerThumbprint) +
+ +
+ +
+
+} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml index 688e7e3f..c19af99b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Index.cshtml @@ -69,3 +69,9 @@

+ diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml index eaac48bb..be20b1bc 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml @@ -156,14 +156,14 @@ +@section FedAuthScripts +{ + @Scripts.Render("~/scripts/Account.js") + +} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comment.cshtml b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comment.cshtml index 398dba19..ef28578b 100644 --- a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comment.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comment.cshtml @@ -10,7 +10,7 @@
@Html.Date(Model.Posted) @Html.CommentedAtRevision(Model, (EntryRevision)ViewBag.Entry) - @if (ViewData.IsLoggedIn()) + @if (User.Identity.IsAuthenticated) { @Html.ActionLink(Model.IsSpam ? "Unspam" : "Spam", "ToggleSpam", "Admin", new { Area = "Admin", id = Model.Id }, null) } diff --git a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml index 05635e98..283fabe2 100644 --- a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml @@ -1,19 +1,23 @@ @model IEnumerable -
-
- -

- Comments

- @if (!Model.Any() && !Html.Settings().EnableDisqusCommenting) - { -
-

- No comments yet. Be the first!

-
- } - @foreach (var comment in Model.Where(x => !x.IsSpam)) - { - @Html.DisplayFor(_ => comment, new {ViewBag.Entry}) - } -
-
\ No newline at end of file + +@if (!Model.Any()) +{ +
+
+ +

Comments

+ @if (!Model.Any() && !Html.Settings().EnableDisqusCommenting) + { +
+

+ No comments yet. Be the first! +

+
+ } + @foreach (var comment in Model.Where(x => !x.IsSpam)) + { + @Html.DisplayFor(_ => comment, new { ViewBag.Entry }) + } +
+
+} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummaries.cshtml b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummaries.cshtml index 5700bee7..8f3f0264 100644 --- a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummaries.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummaries.cshtml @@ -2,7 +2,7 @@ @if (!Model.Any()) {

No posts have been published yet.

- if (ViewData.IsLoggedIn()) + if (User.Identity.IsAuthenticated) {

Use the @Html.AdminActionLink("New Post", "Edit", "WikiAdmin") link at the top of diff --git a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml index df16b9ad..05da5a9e 100644 --- a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml @@ -1,89 +1,88 @@ @model FunnelWeb.Web.Views.Wiki.PageModel @{ - // This is the default template that renders a blog post. - // The actual "Page" does nothing except render the body of the post and meta data. - Layout = "~/Views/Shared/_Public.cshtml"; + // This is the default template that renders a blog post. + // The actual "Page" does nothing except render the body of the post and meta data. + Layout = "~/Views/Shared/_Public.cshtml"; } @section Meta { - - - + + + - @if (Model.Entry.IsPriorVersion) - { - - } + @if (Model.Entry.IsPriorVersion) + { + + } } @section Summary { - @Html.RenderTrusted(Model.Entry.Summary, Formats.Markdown) + @Html.RenderTrusted(Model.Entry.Summary, Formats.Markdown) } @if (!Html.Settings().EnableDisqusCommenting) { - @Html.ValidationSummary("Comment unsuccessful. Please correct the errors below.") + @Html.ValidationSummary("Comment unsuccessful. Please correct the errors below.") } @if (Model.Entry.Status == FunnelWeb.Model.EntryStatus.Private) { - Html.RenderPartial("_PrivateEntryNotification", Model.Page); + Html.RenderPartial("_PrivateEntryNotification", Model.Page); } @if (!Model.Entry.HideChrome) { -

- @Html.ActionLink(Model.Entry.Title, "Page", "Wiki", new { page = Model.Entry.Name }, null) -

- @Html.DisplayFor(x => x.Entry.Published, new { @class = "entry-date" }) +

+ @Html.ActionLink(Model.Entry.Title, "Page", "Wiki", new { page = Model.Entry.Name }, null) +

+ @Html.DisplayFor(x => x.Entry.Published, new { @class = "entry-date" }) } -
- @if (!Model.Entry.HideChrome && Model.Entry.IsPriorVersion) - { -
-
- - You are looking at revision @Model.Entry.RevisionNumber of this page, which may be out of date. @Html.ActionLink("View the latest version.", "Page", "Wiki", new { page = Model.Entry.Name }, null) - - @* ReSharper disable once Mvc.ActionNotResolved *@ - @if (ViewData.IsLoggedIn()) - { - @Html.ActionLink("Revert to this version.", "Revert", "WikiAdmin", new { Area = "Admin", page = Model.Page, revision = Model.Entry.RevisionNumber }, new { }) - } -   -
-
- } - @RenderBody() - @if (!Model.Entry.HideChrome) - { - @Html.DisplayFor(x => Html.Settings().FacebookLike) - - @Html.DisplayFor(x => x.Entry.TagsCommaSeparated) - - Html.RenderPartial("_EntryTools", Model, ViewData); - - @Html.DisplayFor(x => x.Entry.Pingbacks, new { Model.Entry.PingbackCount, Model.Entry.Name }) - } - else - { - Html.RenderPartial("_EntryTools", Model, ViewData); - } +
+ @if (!Model.Entry.HideChrome && Model.Entry.IsPriorVersion) + { +
+
+ You are looking at revision @Model.Entry.RevisionNumber of this page, which may be out of date. @Html.ActionLink("View the latest version.", "Page", "Wiki", new { page = Model.Entry.Name }, null) + + @* ReSharper disable once Mvc.ActionNotResolved *@ + @if (User.Identity.IsAuthenticated) + { + @Html.ActionLink("Revert to this version.", "Revert", "WikiAdmin", new { Area = "Admin", page = Model.Page, revision = Model.Entry.RevisionNumber }, new { }) + } +   +
+
+ } + @RenderBody() + @if (!Model.Entry.HideChrome) + { + @Html.DisplayFor(x => Html.Settings().FacebookLike) + + @Html.DisplayFor(x => x.Entry.TagsCommaSeparated) + + Html.RenderPartial("_EntryTools", Model, ViewData); + + @Html.DisplayFor(x => x.Entry.Pingbacks, new { Model.Entry.PingbackCount, Model.Entry.Name }) + } + else + { + Html.RenderPartial("_EntryTools", Model, ViewData); + }
- @Html.DisplayFor(x => x.Entry.Comments, new { Model.Entry }) @if (Model.Entry.IsDiscussionEnabled) { - if (Html.Settings().DisableCommentsOlderThan > 0 && DateTime.UtcNow.AddDays(-Html.Settings().DisableCommentsOlderThan) > Model.Entry.Published && !Html.Settings().EnableDisqusCommenting) - { -

No new comments are allowed on this post.

- } - else - { - Html.RenderPartial("_EditComments", Model); - } + if (Html.Settings().DisableCommentsOlderThan > 0 && DateTime.UtcNow.AddDays(-Html.Settings().DisableCommentsOlderThan) > Model.Entry.Published && !Html.Settings().EnableDisqusCommenting) + { +

No new comments are allowed on this post.

+ } + else + { + Html.RenderPartial("_EditComments", Model); + } } -@section Scripts{ - @Scripts.Render("~/bundles/wmd") - @Scripts.Render("~/bundles/site") +@section Scripts{ + @Scripts.Render("~/bundles/wmd") + @Scripts.Render("~/bundles/site") } diff --git a/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml b/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml index 5d905001..27c3870d 100644 --- a/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml @@ -1,58 +1,65 @@ -@model FunnelWeb.Web.Views.Wiki.PageModel +@using System.Threading +@using FunnelWeb.Authentication.Internal +@using Authorization = FunnelWeb.Authentication.Internal.Authorization +@model FunnelWeb.Web.Views.Wiki.PageModel @if (Html.Settings().EnableDisqusCommenting) { - Html.RenderPartial("_Disqus"); + Html.RenderPartial("_Disqus"); } -else +else if (Html.AcsSettings().RequireAuthenticationForComments && !User.CheckAccess(Authorization.Operations.View, Authorization.Resources.Blog.Comment)) { -

Your Comments

+
@Html.ActionLink("Login in", "Index", "Login", new{ ReturnUrl = @Request.Path },new{}) to comment!
+} +else if (!Html.AcsSettings().RequireAuthenticationForComments || Thread.CurrentPrincipal.CheckAccess(Authorization.Operations.View, Authorization.Resources.Blog.Comment)) +{ +

Your Comments

- @Html.ValidationSummary("Comment unsuccessful. Please correct the errors below.") + @Html.ValidationSummary("Comment unsuccessful. Please correct the errors below.") -
- @using (Html.BeginForm("Page", "Wiki", new { page = Model.Page }, FormMethod.Post, new { @class = "promptBeforeUnload" })) - { -
-
- @Html.LabelFor(m => m.CommenterName) -
-
- @Html.TextBoxFor(m => m.CommenterName, Html.AttributesFor(m => m.CommenterName)) - @Html.ValidationMessageFor(m => m.CommenterName) - @Html.HintFor(m => m.CommenterName) -
-
- @Html.LabelFor(m => m.CommenterBlog) -
-
- @Html.TextBoxFor(m => m.CommenterBlog, Html.AttributesFor(m => m.CommenterBlog)) - @Html.ValidationMessageFor(m => m.CommenterBlog) - @Html.HintFor(m => m.CommenterBlog) -
-
- @Html.LabelFor(m => m.CommenterEmail) -
-
- @Html.TextBoxFor(m => m.CommenterEmail, Html.AttributesFor(m => m.CommenterEmail)) - @Html.ValidationMessageFor(m => m.CommenterEmail) - @Html.HintFor(m => m.CommenterEmail) -
-
- @Html.LabelFor(m => m.Comments) -
-
- @Html.EditorFor(m => m.Comments, Html.AttributesFor(m => m.Comments)) - @Html.ValidationMessageFor(m => m.Comments) - @Html.HintFor(m => m.Comments) -
-
- -
-
- } -
-

Preview

-
-
+
+ @using (Html.BeginForm("Page", "Wiki", new { page = Model.Page }, FormMethod.Post, new { @class = "promptBeforeUnload" })) + { +
+
+ @Html.LabelFor(m => m.CommenterName) +
+
+ @Html.TextBoxFor(m => m.CommenterName, Html.AttributesFor(m => m.CommenterName)) + @Html.ValidationMessageFor(m => m.CommenterName) + @Html.HintFor(m => m.CommenterName) +
+
+ @Html.LabelFor(m => m.CommenterBlog) +
+
+ @Html.TextBoxFor(m => m.CommenterBlog, Html.AttributesFor(m => m.CommenterBlog)) + @Html.ValidationMessageFor(m => m.CommenterBlog) + @Html.HintFor(m => m.CommenterBlog) +
+
+ @Html.LabelFor(m => m.CommenterEmail) +
+
+ @Html.TextBoxFor(m => m.CommenterEmail, Html.AttributesFor(m => m.CommenterEmail)) + @Html.ValidationMessageFor(m => m.CommenterEmail) + @Html.HintFor(m => m.CommenterEmail) +
+
+ @Html.LabelFor(m => m.Comments) +
+
+ @Html.EditorFor(m => m.Comments, Html.AttributesFor(m => m.Comments)) + @Html.ValidationMessageFor(m => m.Comments) + @Html.HintFor(m => m.Comments) +
+
+ +
+
+ } +
+

Preview

+
+
} \ No newline at end of file diff --git a/src/FunnelWeb.Web/Views/Shared/_EntryTools.cshtml b/src/FunnelWeb.Web/Views/Shared/_EntryTools.cshtml index 6a3980ca..28293005 100644 --- a/src/FunnelWeb.Web/Views/Shared/_EntryTools.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/_EntryTools.cshtml @@ -1,4 +1,5 @@ -@model FunnelWeb.Web.Views.Wiki.PageModel +@using FunnelWeb.Authentication.Internal +@model FunnelWeb.Web.Views.Wiki.PageModel @if (!Model.Entry.HideChrome) { @@ -12,7 +13,7 @@ { @Html.ActionLink("History", "Revisions", "Wiki", new { page = Model.Page }, null) } - @if (ViewData.IsLoggedIn()) + @if (User.Identity.IsAuthenticated) { @Html.ActionLink("Edit", "Edit", "WikiAdmin", new { Area = "Admin", page = Model.Page }, new { }) } @@ -25,7 +26,7 @@ }
} -else if (ViewData.IsLoggedIn()) +else if (User.Identity.IsAuthenticated) {
@Html.ActionLink("Edit", "Edit", "WikiAdmin", new { Area = "Admin", page = Model.Page }, new { }) diff --git a/src/FunnelWeb.Web/Views/Shared/_Mobile.cshtml b/src/FunnelWeb.Web/Views/Shared/_Mobile.cshtml index 83d07a12..48223a80 100644 --- a/src/FunnelWeb.Web/Views/Shared/_Mobile.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/_Mobile.cshtml @@ -18,7 +18,7 @@
-
@Html.LabelFor(m => m.BlobContainerName)
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/FunnelWeb.Web.csproj b/src/FunnelWeb.Web/FunnelWeb.Web.csproj index 57b6951d..6d8aa1d2 100644 --- a/src/FunnelWeb.Web/FunnelWeb.Web.csproj +++ b/src/FunnelWeb.Web/FunnelWeb.Web.csproj @@ -558,7 +558,7 @@ True 8471 / - http://localhost:54861/ + http://localhost:54871 False False diff --git a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml index 283fabe2..7e320738 100644 --- a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/Comments.cshtml @@ -1,19 +1,19 @@ @model IEnumerable -@if (!Model.Any()) +@if (!Model.Any() && !Html.Settings().EnableDisqusCommenting) +{ +
+

+ No comments yet. Be the first! +

+
+} +else {

Comments

- @if (!Model.Any() && !Html.Settings().EnableDisqusCommenting) - { -
-

- No comments yet. Be the first! -

-
- } @foreach (var comment in Model.Where(x => !x.IsSpam)) { @Html.DisplayFor(_ => comment, new { ViewBag.Entry }) diff --git a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml index 05da5a9e..b0fb58c7 100644 --- a/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/PageTemplates/Default.cshtml @@ -55,11 +55,11 @@ @if (!Model.Entry.HideChrome) { @Html.DisplayFor(x => Html.Settings().FacebookLike) - + @Html.DisplayFor(x => x.Entry.TagsCommaSeparated) - + Html.RenderPartial("_EntryTools", Model, ViewData); - + @Html.DisplayFor(x => x.Entry.Pingbacks, new { Model.Entry.PingbackCount, Model.Entry.Name }) } else diff --git a/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml b/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml index 27c3870d..6e8291ad 100644 --- a/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/_EditComments.cshtml @@ -3,17 +3,23 @@ @using Authorization = FunnelWeb.Authentication.Internal.Authorization @model FunnelWeb.Web.Views.Wiki.PageModel +@{ + const string anchorName = "yourComments"; + var returnUrl = string.Format("{0}?#{1}", @Request.Path, anchorName); +} + @if (Html.Settings().EnableDisqusCommenting) { Html.RenderPartial("_Disqus"); } else if (Html.AcsSettings().RequireAuthenticationForComments && !User.CheckAccess(Authorization.Operations.View, Authorization.Resources.Blog.Comment)) { -
@Html.ActionLink("Login in", "Index", "Login", new{ ReturnUrl = @Request.Path },new{}) to comment!
+
@Html.ActionLink("Login in", "Index", "Login", new { ReturnUrl = returnUrl }, new { }) to comment!
} else if (!Html.AcsSettings().RequireAuthenticationForComments || Thread.CurrentPrincipal.CheckAccess(Authorization.Operations.View, Authorization.Resources.Blog.Comment)) { -

Your Comments

+   +

Your Comments on "@Model.Entry.Title":

@Html.ValidationSummary("Comment unsuccessful. Please correct the errors below.") diff --git a/src/FunnelWeb/Authentication/Internal/FederatedAuthenticationService.cs b/src/FunnelWeb/Authentication/Internal/FederatedAuthenticationService.cs index 5df983fd..fbc3d5d8 100644 --- a/src/FunnelWeb/Authentication/Internal/FederatedAuthenticationService.cs +++ b/src/FunnelWeb/Authentication/Internal/FederatedAuthenticationService.cs @@ -3,6 +3,7 @@ using System.IdentityModel.Tokens; using System.Linq; using System.Security.Claims; +using System.Threading; using FunnelWeb.Model.Authentication; namespace FunnelWeb.Authentication.Internal @@ -29,7 +30,12 @@ public void Login(User user) var sessionToken = new SessionSecurityToken(principal); var authenticationModule = FederatedAuthentication.SessionAuthenticationModule; + + // Persist the authentication cookie. authenticationModule.WriteSessionTokenToCookie(sessionToken); + + // Set the current user for the current request + Thread.CurrentPrincipal = principal; } public void Logout() diff --git a/src/FunnelWeb/DatabaseDeployer/DatabaseConnectionDetector.cs b/src/FunnelWeb/DatabaseDeployer/DatabaseConnectionDetector.cs new file mode 100644 index 00000000..f993d009 --- /dev/null +++ b/src/FunnelWeb/DatabaseDeployer/DatabaseConnectionDetector.cs @@ -0,0 +1,24 @@ +using FunnelWeb.Providers.Database; + +namespace FunnelWeb.DatabaseDeployer +{ + public class DatabaseConnectionDetector : IDatabaseConnectionDetector + { + private readonly IConnectionStringSettings connectionStringSettings; + private readonly IDatabaseProvider databaseProvider; + + public DatabaseConnectionDetector( + IConnectionStringSettings connectionStringSettings, + IDatabaseProvider databaseProvider) + { + this.connectionStringSettings = connectionStringSettings; + this.databaseProvider = databaseProvider; + } + + public bool CanConnect(out string error) + { + var connectionString = connectionStringSettings.ConnectionString; + return databaseProvider.TryConnect(connectionString, out error); + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/DatabaseDeployer/DatabaseModule.cs b/src/FunnelWeb/DatabaseDeployer/DatabaseModule.cs index 7d457df4..9d66d409 100644 --- a/src/FunnelWeb/DatabaseDeployer/DatabaseModule.cs +++ b/src/FunnelWeb/DatabaseDeployer/DatabaseModule.cs @@ -1,22 +1,15 @@ -using System; -using Autofac; +using Autofac; namespace FunnelWeb.DatabaseDeployer { - public class DatabaseModule : Module - { - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder - .RegisterType() - .As() - .SingleInstance(); - } - } + public class DatabaseModule : Module + { + protected override void Load(ContainerBuilder builder) + { + base.Load(builder); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + } + } } diff --git a/src/FunnelWeb/DatabaseDeployer/DatabaseUpgradeDetector.cs b/src/FunnelWeb/DatabaseDeployer/DatabaseUpgradeDetector.cs index d797ae63..bb615215 100644 --- a/src/FunnelWeb/DatabaseDeployer/DatabaseUpgradeDetector.cs +++ b/src/FunnelWeb/DatabaseDeployer/DatabaseUpgradeDetector.cs @@ -1,84 +1,91 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Collections.Generic; using System.Linq; using FunnelWeb.Providers.Database; namespace FunnelWeb.DatabaseDeployer { - public class DatabaseUpgradeDetector : IDatabaseUpgradeDetector - { - private readonly IConnectionStringSettings connectionStringSettings; - private readonly IEnumerable extensions; - private readonly IDatabaseProvider databaseProvider; - private readonly IApplicationDatabase database; - private bool? updateNeeded; - private readonly object @lock = new object(); + public class DatabaseUpgradeDetector : IDatabaseUpgradeDetector + { + private readonly IConnectionStringSettings connectionStringSettings; + private readonly IEnumerable extensions; + private readonly IDatabaseProvider databaseProvider; + private readonly IDatabaseConnectionDetector databaseConnectionDetector; + private readonly IApplicationDatabase database; + private bool? updateNeeded; + private readonly object @lock = new object(); - public DatabaseUpgradeDetector( - IConnectionStringSettings connectionStringSettings, - IEnumerable extensions, - IApplicationDatabase database, - IDatabaseProvider databaseProvider) - { - this.connectionStringSettings = connectionStringSettings; - this.extensions = extensions; - this.database = database; - this.databaseProvider = databaseProvider; - } + public DatabaseUpgradeDetector( + IConnectionStringSettings connectionStringSettings, + IEnumerable extensions, + IApplicationDatabase database, + IDatabaseProvider databaseProvider, + IDatabaseConnectionDetector databaseConnectionDetector) + { + this.connectionStringSettings = connectionStringSettings; + this.extensions = extensions; + this.database = database; + this.databaseProvider = databaseProvider; + this.databaseConnectionDetector = databaseConnectionDetector; + } - public bool UpdateNeeded() - { - if (updateNeeded != null) - return updateNeeded.Value; + public bool UpdateNeeded() + { + if (updateNeeded != null) + { + return updateNeeded.Value; + } - lock (@lock) - { - if (updateNeeded != null) - return updateNeeded.Value; + lock (@lock) + { + if (updateNeeded != null) + { + return updateNeeded.Value; + } - var connectionString = connectionStringSettings.ConnectionString; + var connectionString = connectionStringSettings.ConnectionString; - string error; - if (databaseProvider.TryConnect(connectionString, out error)) - { - var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); - var currentScripts = database.GetCoreExecutedScripts(connectionFactory); - var requiredScripts = database.GetCoreRequiredScripts(connectionFactory); - var notRun = requiredScripts.Select(x => x.Trim().ToLowerInvariant()) - .Except(currentScripts.Select(x => x.Trim().ToLowerInvariant())) - .ToList(); + string error; + if (databaseConnectionDetector.CanConnect(out error)) + { + var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); + var currentScripts = database.GetCoreExecutedScripts(connectionFactory); + var requiredScripts = database.GetCoreRequiredScripts(connectionFactory); + var notRun = requiredScripts.Select(x => x.Trim().ToLowerInvariant()) + .Except(currentScripts.Select(x => x.Trim().ToLowerInvariant())) + .ToList(); - updateNeeded = notRun.Count > 0 - || ExtensionsRequireUpdate(extensions, database, databaseProvider, connectionString); - } - else - { - updateNeeded = true; - } + updateNeeded = notRun.Count > 0 + || ExtensionsRequireUpdate(extensions, database, databaseProvider, connectionString); + } + else + { + updateNeeded = true; + } - return updateNeeded.Value; - } - } + return updateNeeded.Value; + } + } - public void Reset() - { - lock (@lock) - updateNeeded = null; - } + public void Reset() + { + lock (@lock) + { + updateNeeded = null; + } + } - private static bool ExtensionsRequireUpdate(IEnumerable extensions, IApplicationDatabase applicationDatabase, - IDatabaseProvider databaseProvider, string connectionString) - { - var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); - return (from x in extensions - let current = applicationDatabase.GetExtensionExecutedScripts(connectionFactory, x) - let required = applicationDatabase.GetExtensionRequiredScripts(connectionFactory, x) - let notRun = required.Select(z => z.Trim().ToLowerInvariant()) - .Except(current.Select(z => z.Trim().ToLowerInvariant())) - .ToList() - where notRun.Count > 0 - select current).Any(); - } - } + private static bool ExtensionsRequireUpdate(IEnumerable extensions, IApplicationDatabase applicationDatabase, + IDatabaseProvider databaseProvider, string connectionString) + { + var connectionFactory = databaseProvider.GetConnectionFactory(connectionString); + return (from x in extensions + let current = applicationDatabase.GetExtensionExecutedScripts(connectionFactory, x) + let required = applicationDatabase.GetExtensionRequiredScripts(connectionFactory, x) + let notRun = required.Select(z => z.Trim().ToLowerInvariant()) + .Except(current.Select(z => z.Trim().ToLowerInvariant())) + .ToList() + where notRun.Count > 0 + select current).Any(); + } + } } \ No newline at end of file diff --git a/src/FunnelWeb/DatabaseDeployer/IDatabaseConnectionDetector.cs b/src/FunnelWeb/DatabaseDeployer/IDatabaseConnectionDetector.cs new file mode 100644 index 00000000..4a27d38d --- /dev/null +++ b/src/FunnelWeb/DatabaseDeployer/IDatabaseConnectionDetector.cs @@ -0,0 +1,7 @@ +namespace FunnelWeb.DatabaseDeployer +{ + public interface IDatabaseConnectionDetector + { + bool CanConnect(out string error); + } +} \ No newline at end of file diff --git a/src/FunnelWeb/DatabaseDeployer/IDatabaseUpgradeDetector.cs b/src/FunnelWeb/DatabaseDeployer/IDatabaseUpgradeDetector.cs index 0b613381..63a2a07e 100644 --- a/src/FunnelWeb/DatabaseDeployer/IDatabaseUpgradeDetector.cs +++ b/src/FunnelWeb/DatabaseDeployer/IDatabaseUpgradeDetector.cs @@ -1,8 +1,8 @@ namespace FunnelWeb.DatabaseDeployer { - public interface IDatabaseUpgradeDetector - { - bool UpdateNeeded(); - void Reset(); - } + public interface IDatabaseUpgradeDetector + { + bool UpdateNeeded(); + void Reset(); + } } diff --git a/src/FunnelWeb/DatabaseDeployer/Scripts/Script0033.sql b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0033.sql index a484903c..28c68f2c 100644 --- a/src/FunnelWeb/DatabaseDeployer/Scripts/Script0033.sql +++ b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0033.sql @@ -16,4 +16,4 @@ VALUES ('acs-realm', 'The realm of your Relying Party.', 'Realm', '') INSERT INTO $schema$.[Setting]([Name], [Description], [DisplayName], [Value]) VALUES ('acs-issuerthumbprint', 'The thumbprint of the ACS Relying Party.', 'Issuer Thumbprint', '') INSERT INTO $schema$.[Setting]([Name], [Description], [DisplayName], [Value]) -VALUES ('acs-authenticatedcommenters', 'True if comment form is shown only for authenticated users.', 'Authenticated commenters', 'True') +VALUES ('acs-authenticatedcommenters', 'True if comment form is shown only for authenticated users.', 'Authenticated commenters', 'False') diff --git a/src/FunnelWeb/FunnelWeb.csproj b/src/FunnelWeb/FunnelWeb.csproj index ce93a859..cb52ca2a 100644 --- a/src/FunnelWeb/FunnelWeb.csproj +++ b/src/FunnelWeb/FunnelWeb.csproj @@ -159,6 +159,8 @@ + + diff --git a/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs b/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs index 6d11c7bd..fe3c0332 100644 --- a/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs +++ b/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs @@ -3,31 +3,38 @@ using System.IdentityModel.Services; using System.IdentityModel.Tokens; using System.Web; +using FunnelWeb.DatabaseDeployer; namespace FunnelWeb.Settings { public class FederatedAuthenticationConfigurator : IFederatedAuthenticationConfigurator { private readonly Func settingsProviderFactory; + private readonly Func databaseUpgradeDetectorFactory; private ISettingsProvider settingsProvider; private ISettingsProvider SettingsProvider { get { return settingsProvider ?? (settingsProvider = settingsProviderFactory()); } } - public FederatedAuthenticationConfigurator(Func settingsProviderFactory) + public FederatedAuthenticationConfigurator( + Func settingsProviderFactory, + Func databaseUpgradeDetectorFactory) { this.settingsProviderFactory = settingsProviderFactory; + this.databaseUpgradeDetectorFactory = databaseUpgradeDetectorFactory; } public void InitiateFederatedAuthentication(AccessControlServiceSettings accessControlServiceSettings = null) { if (accessControlServiceSettings == null) { - try + if (!databaseUpgradeDetectorFactory().UpdateNeeded()) { - accessControlServiceSettings = SettingsProvider.GetSettings(); + // Database needs an upgrade or is not reachable. We cannot configure Fed Auth at this time. + return; } - catch (Exception) + + if (!SettingsProvider.TryGetSettings(out accessControlServiceSettings)) { - // If the database is not reachable we can't get any settings and thus cannot configure FederatedAuthentication. Just return! + // Unable to load the settings from the databse. We cannot configure Fed Auth at this time. return; } } diff --git a/src/FunnelWeb/Settings/ISettingsProvider.cs b/src/FunnelWeb/Settings/ISettingsProvider.cs index 5ace3cc1..2d9068ec 100644 --- a/src/FunnelWeb/Settings/ISettingsProvider.cs +++ b/src/FunnelWeb/Settings/ISettingsProvider.cs @@ -2,6 +2,7 @@ namespace FunnelWeb.Settings { public interface ISettingsProvider { + bool TryGetSettings(out T t) where T : ISettings; T GetSettings() where T : ISettings; T GetDefaultSettings() where T : ISettings; void SaveSettings(T settings) where T : ISettings; diff --git a/src/FunnelWeb/Settings/SettingsProvider.cs b/src/FunnelWeb/Settings/SettingsProvider.cs index 74f368e3..2153e7bb 100644 --- a/src/FunnelWeb/Settings/SettingsProvider.cs +++ b/src/FunnelWeb/Settings/SettingsProvider.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Data.SqlClient; using System.Linq; using System.Reflection; using FunnelWeb.Model; using FunnelWeb.Model.Repositories; +using NHibernate.Exceptions; namespace FunnelWeb.Settings { @@ -19,21 +21,50 @@ public SettingsProvider(Func repository) this.repository = repository; } - public T GetSettings() where T : ISettings + public bool TryGetSettings(out T t) where T : ISettings { var settingsType = typeof(T); - if (!settingsStore.ContainsKey(settingsType)) + if (settingsStore.ContainsKey(settingsType)) + { + t = (T)settingsStore[settingsType]; + return true; + } + + lock (@lock) { - lock (@lock) + if (!settingsStore.ContainsKey(settingsType)) { - if (!settingsStore.ContainsKey(settingsType)) + try { LoadSettings(); + t = (T) settingsStore[settingsType]; + return true; + } + catch (GenericADOException) + { + + } + catch (SqlException) + { + } } } - return (T)settingsStore[settingsType]; + t = default(T); + return false; + } + + public T GetSettings() where T : ISettings + { + T t; + + if (!TryGetSettings(out t)) + { + throw new ApplicationException(string.Format("Unable to load settings for '{0}'", typeof(T).Name)); + } + + return t; } public T GetDefaultSettings() where T : ISettings From 900d4ebed438568fc26c76606d2000413337c1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Tue, 22 Oct 2013 08:37:31 +0200 Subject: [PATCH 11/13] Updated FedAuth to enable multiple AudienceUris --- .../Internal/SqlFunnelWebMembershipTests.cs | 18 +++++++++++++++ src/FunnelWeb.Tests/FunnelWeb.Tests.csproj | 1 + .../Admin/Controllers/AdminController.cs | 9 ++++++++ .../Admin/Views/Admin/AcsSettings.cshtml | 12 ++++++---- src/FunnelWeb.Web/FunnelWeb.Web.csproj | 3 ++- .../magnusmartenssoncomdev.pubxml | 22 +++++++++++++++++++ .../Internal/SqlFunnelWebMembership.cs | 2 +- src/FunnelWeb/Constants.cs | 16 ++++++++++++++ .../DatabaseDeployer/Scripts/Script0034.sql | 3 +++ src/FunnelWeb/FunnelWeb.csproj | 4 ++++ .../Settings/AccessControlServiceSettings.cs | 6 +++++ .../FederatedAuthenticationConfigurator.cs | 13 ++++++++++- 12 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs create mode 100644 src/FunnelWeb.Web/Properties/PublishProfiles/magnusmartenssoncomdev.pubxml create mode 100644 src/FunnelWeb/Constants.cs create mode 100644 src/FunnelWeb/DatabaseDeployer/Scripts/Script0034.sql 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/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj index b5538229..f0016db8 100644 --- a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj +++ b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj @@ -139,6 +139,7 @@ Properties\VersionInfo.cs + diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs index 0b964f5a..c2115434 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/AdminController.cs @@ -90,6 +90,15 @@ public virtual ActionResult AcsSettings(AccessControlServiceSettings acsSettings 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); diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml index 713c8a25..93ff4e67 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/AcsSettings.cshtml @@ -37,15 +37,16 @@ @Html.ValidationMessageFor(m => m.Namespace) @Html.HintFor(m => m.Namespace)
- -
- @Html.LabelFor(m => m.Realm) -
@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) @@ -55,6 +56,9 @@ @Html.ValidationMessageFor(m => m.IssuerThumbprint) @Html.HintFor(m => m.IssuerThumbprint)
+
+ @Html.LabelFor(m => m.Realm) +
diff --git a/src/FunnelWeb.Web/FunnelWeb.Web.csproj b/src/FunnelWeb.Web/FunnelWeb.Web.csproj index 6d8aa1d2..756728fa 100644 --- a/src/FunnelWeb.Web/FunnelWeb.Web.csproj +++ b/src/FunnelWeb.Web/FunnelWeb.Web.csproj @@ -388,6 +388,7 @@ + @@ -558,7 +559,7 @@ True 8471 / - http://localhost:54871 + http://localhost:54873 False False diff --git a/src/FunnelWeb.Web/Properties/PublishProfiles/magnusmartenssoncomdev.pubxml b/src/FunnelWeb.Web/Properties/PublishProfiles/magnusmartenssoncomdev.pubxml new file mode 100644 index 00000000..adfd44bb --- /dev/null +++ b/src/FunnelWeb.Web/Properties/PublishProfiles/magnusmartenssoncomdev.pubxml @@ -0,0 +1,22 @@ + + + + + MSDeploy + Release + Any CPU + http://magnusmartenssoncomdev.azurewebsites.net + False + waws-prod-db3-001.publish.azurewebsites.windows.net:443 + magnusmartenssoncomdev + + True + WMSVC + True + $magnusmartenssoncomdev + <_SavePWD>True + + \ No newline at end of file diff --git a/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs b/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs index 33ff7641..79259be8 100644 --- a/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs +++ b/src/FunnelWeb/Authentication/Internal/SqlFunnelWebMembership.cs @@ -42,7 +42,7 @@ public IEnumerable GetUsers() .List(); } - internal static string HashPassword(string password) + public static string HashPassword(string password) { return FormsAuthentication.HashPasswordForStoringInConfigFile(password, FormsAuthPasswordFormat.SHA1.ToString()); } diff --git a/src/FunnelWeb/Constants.cs b/src/FunnelWeb/Constants.cs new file mode 100644 index 00000000..e500085f --- /dev/null +++ b/src/FunnelWeb/Constants.cs @@ -0,0 +1,16 @@ +namespace FunnelWeb +{ + public static class Constants + { + public static class Chars + { + public const char Space = ' '; + public const char NewLine = '\n'; + } + + public static class Strings + { + public const string NewLine = "\n"; + } + } +} \ No newline at end of file diff --git a/src/FunnelWeb/DatabaseDeployer/Scripts/Script0034.sql b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0034.sql new file mode 100644 index 00000000..055af6ca --- /dev/null +++ b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0034.sql @@ -0,0 +1,3 @@ +/* The following settings are for Windows Azure Access Control Service as an external authentication provider. */ +INSERT INTO $schema$.[Setting]([Name], [Description], [DisplayName], [Value]) +VALUES ('acs-audienceuris', 'The Audience Uris of the ACS Relying Party.', 'Audience Uris', '') diff --git a/src/FunnelWeb/FunnelWeb.csproj b/src/FunnelWeb/FunnelWeb.csproj index cb52ca2a..7bb297a1 100644 --- a/src/FunnelWeb/FunnelWeb.csproj +++ b/src/FunnelWeb/FunnelWeb.csproj @@ -159,6 +159,7 @@ + @@ -440,6 +441,9 @@ + + + diff --git a/src/FunnelWeb/Settings/AccessControlServiceSettings.cs b/src/FunnelWeb/Settings/AccessControlServiceSettings.cs index 1253ae11..86a5efba 100644 --- a/src/FunnelWeb/Settings/AccessControlServiceSettings.cs +++ b/src/FunnelWeb/Settings/AccessControlServiceSettings.cs @@ -33,5 +33,11 @@ public class AccessControlServiceSettings : ISettings [Description("The thumbprint of the ACS Relying Party.")] [SettingStorage(StorageLocation.Database, "acs-issuerthumbprint")] public string IssuerThumbprint { get; set; } + + [DisplayName("Audience Uris")] + [DefaultValue("{AudienceUris}")] + [Description("The Audience Uris of the ACS Relying Party.")] + [SettingStorage(StorageLocation.Database, "acs-audienceuris")] + public string AudienceUris { get; set; } } } \ No newline at end of file diff --git a/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs b/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs index fe3c0332..31efb2a4 100644 --- a/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs +++ b/src/FunnelWeb/Settings/FederatedAuthenticationConfigurator.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.IdentityModel.Configuration; using System.IdentityModel.Services; using System.IdentityModel.Tokens; +using System.Linq; using System.Web; using FunnelWeb.DatabaseDeployer; @@ -42,6 +44,11 @@ public void InitiateFederatedAuthentication(AccessControlServiceSettings accessC string realm = accessControlServiceSettings.Realm; string acsNamespace = accessControlServiceSettings.Namespace; string thumbprint = accessControlServiceSettings.IssuerThumbprint; + IEnumerable audienceUris = accessControlServiceSettings + .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)); var defaultSettings = SettingsProvider.GetDefaultSettings(); if (!accessControlServiceSettings.Enabled || @@ -53,7 +60,11 @@ public void InitiateFederatedAuthentication(AccessControlServiceSettings accessC // system.identityModel -> identityConfiguration IdentityConfiguration identityConfiguration = FederatedAuthentication.FederationConfiguration.IdentityConfiguration; identityConfiguration.AudienceRestriction.AllowedAudienceUris.Clear(); - identityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(realm)); + foreach (var audienceUri in audienceUris) + { + identityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(audienceUri); + } + var validatingIssuerNameRegistry = identityConfiguration.IssuerNameRegistry as ValidatingIssuerNameRegistry; if (validatingIssuerNameRegistry != null) { From 649edac08f682eaf7c6c42aa2b3783fa3516db92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Tue, 22 Oct 2013 15:34:41 +0200 Subject: [PATCH 12/13] Fixed the upgrade screen to logout if sql user is enabled --- .../Admin/Controllers/InstallController.cs | 30 +++++++++++++++---- .../Areas/Admin/Views/Install/UpgradeModel.cs | 21 ++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs index 30333078..e161259b 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs +++ b/src/FunnelWeb.Web/Areas/Admin/Controllers/InstallController.cs @@ -10,6 +10,7 @@ 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 @@ -21,18 +22,24 @@ 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) + 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)] @@ -78,14 +85,17 @@ public virtual ActionResult Index() } [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Authorization.Operations.Update, Resource = Authorization.Resources.Install.ChangeProvider)] - public ActionResult ChangeProvider(string databaseProvider) + public ActionResult ChangeProvider(string newDatabaseProvider) { - var provider = databaseProvidersInfo().GetProviderByName(databaseProvider); + var provider = databaseProvidersInfo().GetProviderByName(newDatabaseProvider); ConnectionStringSettings.ConnectionString = provider.DefaultConnectionString; - ConnectionStringSettings.DatabaseProvider = databaseProvider; + ConnectionStringSettings.DatabaseProvider = newDatabaseProvider; if (!provider.SupportSchema) - ConnectionStringSettings.Schema = null; + { + ConnectionStringSettings.Schema = null; + } + UpgradeDetector.Reset(); return RedirectToAction("Index"); @@ -112,7 +122,15 @@ public virtual ActionResult Upgrade() var result = Database.PerformUpgrade(Extensions, log); UpgradeDetector.Reset(); - return View("UpgradeReport", new UpgradeModel(result, writer.ToString())); + 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 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 From c91308df6d4481992f0fbb7d98086b9da50cad47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20M=C3=A5rtensson?= Date: Wed, 23 Oct 2013 22:39:34 +0200 Subject: [PATCH 13/13] Added the setting to hide comments on teh recent post page. --- .../Areas/Admin/Views/Admin/Settings.cshtml | 9 ++++ .../Controllers/WikiController.cs | 29 +++++++---- .../DisplayTemplates/EntrySummary.cshtml | 49 ++++++++++--------- .../DatabaseDeployer/Scripts/Script0035.sql | 3 ++ src/FunnelWeb/FunnelWeb.csproj | 3 ++ src/FunnelWeb/Model/EntrySummary.cs | 49 ++++++++++--------- src/FunnelWeb/Settings/FunnelWebSettings.cs | 6 +++ 7 files changed, 93 insertions(+), 55 deletions(-) create mode 100644 src/FunnelWeb/DatabaseDeployer/Scripts/Script0035.sql diff --git a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml index cf627765..6358e52d 100644 --- a/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml +++ b/src/FunnelWeb.Web/Areas/Admin/Views/Admin/Settings.cshtml @@ -179,6 +179,15 @@

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)
diff --git a/src/FunnelWeb.Web/Controllers/WikiController.cs b/src/FunnelWeb.Web/Controllers/WikiController.cs index 8ff026dd..5c3b84f9 100644 --- a/src/FunnelWeb.Web/Controllers/WikiController.cs +++ b/src/FunnelWeb.Web/Controllers/WikiController.cs @@ -3,7 +3,6 @@ using System.Security.Claims; using System.Threading; using System.Web.Mvc; -using FluentNHibernate.Conventions; using FunnelWeb.Authentication.Internal; using FunnelWeb.Eventing; using FunnelWeb.Filters; @@ -16,6 +15,7 @@ using FunnelWeb.Web.Application.Mvc.ActionResults; using FunnelWeb.Web.Application.Spam; using FunnelWeb.Web.Views.Wiki; +using NHibernate.Linq; using ClaimTypes = System.IdentityModel.Claims.ClaimTypes; namespace FunnelWeb.Web.Controllers @@ -23,15 +23,22 @@ namespace FunnelWeb.Web.Controllers [FunnelWebRequest] [HandleError] [ValidateInput(false)] - public class WikiController : Controller + public sealed class WikiController : Controller { + private readonly ISettingsProvider settingsProvider; + + public WikiController(ISettingsProvider settingsProvider) + { + this.settingsProvider = settingsProvider; + } + private const int ItemsPerPage = 30; public IRepository Repository { get; set; } public ISpamChecker SpamChecker { get; set; } public IEventPublisher EventPublisher { get; set; } public ISettingsProvider SettingsProvider { get; set; } - public virtual ActionResult Home(int? pageNumber) + public ActionResult Home(int? pageNumber) { var settings = SettingsProvider.GetSettings(); if (!string.IsNullOrWhiteSpace(settings.CustomHomePage)) @@ -50,20 +57,22 @@ public virtual ActionResult Home(int? pageNumber) return Recent(pageNumber ?? 0); } - public virtual ActionResult Recent(int pageNumber) + public ActionResult Recent(int pageNumber) { + bool hideCommentCount = settingsProvider.GetSettings().HideCommentCountOnRecentPage; var result = Repository.Find(new GetEntriesQuery(EntryStatus.PublicBlog), pageNumber, ItemsPerPage); + result.ForEach(es => es.HideComments = hideCommentCount); ViewData.Model = new RecentModel("Recent Posts", result, ControllerContext.RouteData.Values["action"].ToString()); return View("Recent"); } - public virtual ActionResult Search([Bind(Prefix = "q")] string searchText, bool? is404) + public ActionResult Search([Bind(Prefix = "q")] string searchText, bool? is404) { var results = Repository.Find(new SwitchingSearchEntriesQuery(searchText), 0, 30); return View("Search", new SearchModel(searchText, is404 ?? false, results)); } - public virtual ActionResult Page(PageName page, int? revision) + public ActionResult Page(PageName page, int? revision) { if (revision != null && !SettingsProvider.GetSettings().EnablePublicHistory) { @@ -107,7 +116,7 @@ public virtual ActionResult Page(PageName page, int? revision) // Posting a comment [HttpPost] - public virtual ActionResult Page(PageName page, PageModel model) + public ActionResult Page(PageName page, PageModel model) { var entry = Repository.FindFirstOrDefault(new EntryByNameQuery(page)); if (entry == null) @@ -152,7 +161,7 @@ public virtual ActionResult Page(PageName page, PageModel model) .AndFlash("Thanks, your comment has been posted."); } - public virtual ActionResult Revisions(PageName page) + public ActionResult Revisions(PageName page) { var settings = SettingsProvider.GetSettings(); if (!settings.EnablePublicHistory) @@ -170,14 +179,14 @@ public virtual ActionResult Revisions(PageName page) return View(); } - public virtual ActionResult SiteMap() + public ActionResult SiteMap() { var allPosts = Repository.Find(new GetFullEntriesQuery(true), 0, 500); ViewData.Model = new SiteMapModel(allPosts); return View(); } - public virtual ActionResult Pingbacks(PageName page) + public ActionResult Pingbacks(PageName page) { var entry = Repository.FindFirst(new GetEntryWithPingbacksQuery(page)); return View(entry); diff --git a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummary.cshtml b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummary.cshtml index a9a4c3ce..7c2d86c4 100644 --- a/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummary.cshtml +++ b/src/FunnelWeb.Web/Views/Shared/DisplayTemplates/EntrySummary.cshtml @@ -1,27 +1,30 @@ @model EntrySummary
- @Html.DisplayFor(x => x.Published, new { @class = "revised" }) -
- @if (Html.Settings().EnableDisqusCommenting) - { - @Scripts.Render("~/bundles/disqus-count") - Comment(s) - } - else - { - - @Model.CommentCount - comment@(Model.CommentCount == 1 ? "" : "s") - - } -
-
-

@Html.ActionLink(Model.Title, "Page", "Wiki", new { page = Model.Name }, null)

- @if (!string.IsNullOrEmpty(Model.MetaDescription)) - { -

@Html.RenderTrusted(Model.MetaDescription, Formats.Markdown)

- } - @Html.DisplayFor(x => Model.TagsCommaSeparated) -
+ @Html.DisplayFor(x => x.Published, new { @class = "revised" }) +
+ @if (!Model.HideComments) + { + if (Html.Settings().EnableDisqusCommenting) + { + @Scripts.Render("~/bundles/disqus-count") + Comment(s) + } + else + { + + @Model.CommentCount + comment@(Model.CommentCount == 1 ? "" : "s") + + } + } +
+
+

@Html.ActionLink(Model.Title, "Page", "Wiki", new { page = Model.Name }, null)

+ @if (!string.IsNullOrEmpty(Model.MetaDescription)) + { +

@Html.RenderTrusted(Model.MetaDescription, Formats.Markdown)

+ } + @Html.DisplayFor(x => Model.TagsCommaSeparated) +
diff --git a/src/FunnelWeb/DatabaseDeployer/Scripts/Script0035.sql b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0035.sql new file mode 100644 index 00000000..4624454e --- /dev/null +++ b/src/FunnelWeb/DatabaseDeployer/Scripts/Script0035.sql @@ -0,0 +1,3 @@ +/* Hide comments on the recent post page. */ +INSERT INTO $schema$.[Setting]([Name], [Description], [DisplayName], [Value]) +VALUES ('spam-comment-hide-count', 'Hide comment count on the recent posts page.', 'Hide comment counts', 'false') diff --git a/src/FunnelWeb/FunnelWeb.csproj b/src/FunnelWeb/FunnelWeb.csproj index 7bb297a1..341d2247 100644 --- a/src/FunnelWeb/FunnelWeb.csproj +++ b/src/FunnelWeb/FunnelWeb.csproj @@ -444,6 +444,9 @@ + + + diff --git a/src/FunnelWeb/Model/EntrySummary.cs b/src/FunnelWeb/Model/EntrySummary.cs index 97644cb7..8725c67e 100644 --- a/src/FunnelWeb/Model/EntrySummary.cs +++ b/src/FunnelWeb/Model/EntrySummary.cs @@ -4,26 +4,31 @@ namespace FunnelWeb.Model { - public class EntrySummary - { - public virtual int Id { get; set; } - - public virtual PageName Name { get; set; } - public virtual string Title { get; set; } - - [DataType("Markdown")] - public virtual string Summary { get; set; } - - public virtual string Status { get; set; } - - public virtual int CommentCount { get; set; } - public virtual string MetaDescription { get; set; } - [DataType("PublishedDate")] - public virtual DateTime Published { get; set; } - public virtual DateTime LastRevised { get; set; } - - [DataType("TagsList")] - public virtual string TagsCommaSeparated { get; set; } - - } + public class EntrySummary + { + public int Id { get; set; } + + public PageName Name { get; set; } + + public string Title { get; set; } + + [DataType("Markdown")] + public string Summary { get; set; } + + public string Status { get; set; } + + public int CommentCount { get; set; } + + public bool HideComments { get; set; } + + public string MetaDescription { get; set; } + + [DataType("PublishedDate")] + public DateTime Published { get; set; } + + public DateTime LastRevised { get; set; } + + [DataType("TagsList")] + public string TagsCommaSeparated { get; set; } + } } \ No newline at end of file diff --git a/src/FunnelWeb/Settings/FunnelWebSettings.cs b/src/FunnelWeb/Settings/FunnelWebSettings.cs index f013cc4f..082da064 100644 --- a/src/FunnelWeb/Settings/FunnelWebSettings.cs +++ b/src/FunnelWeb/Settings/FunnelWebSettings.cs @@ -92,6 +92,12 @@ public class FunnelWebSettings : ISettings [SettingStorage(StorageLocation.Database, "spam-blacklist")] public string SpamWords { get; set; } + [DisplayName("Hide comment counts")] + [DefaultValue(false)] + [Description("Hide comment count on the recent posts page.")] + [SettingStorage(StorageLocation.Database, "spam-comment-hide-count")] + public bool HideCommentCountOnRecentPage { get; set; } + [DisplayName("Disable comments after")] [DefaultValue(0)] [Description("If a post is older than this many days, comments will be disabled. Use 0 to allow comments indefinitely.")]