diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
index 71ab76e082..1f78a431b6 100644
--- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs
+++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
@@ -1,4 +1,6 @@
using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Security.Principal;
using System.Threading;
using System.Web;
@@ -127,6 +129,26 @@ namespace Umbraco.Core.Security
Logout(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName);
}
+ ///
+ /// This clears the forms authentication cookie
+ ///
+ ///
+ public static void UmbracoLogout(this HttpResponseMessage response)
+ {
+ if (response == null) throw new ArgumentNullException("response");
+ //remove the cookie
+ var cookie = new CookieHeaderValue(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "")
+ {
+ Expires = DateTime.Now.AddYears(-1),
+ Path = "/"
+ };
+ response.Headers.AddCookies(new[] { cookie });
+ }
+
+ ///
+ /// This clears the forms authentication cookie
+ ///
+ ///
internal static void UmbracoLogout(this HttpContext http)
{
if (http == null) throw new ArgumentNullException("http");
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index a216a588ac..88ac584da9 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -72,6 +72,8 @@
..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.Entity.dll
+
+
diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs
index 1dc3f6cde8..8e1bdaf201 100644
--- a/src/Umbraco.Web/Editors/AuthenticationController.cs
+++ b/src/Umbraco.Web/Editors/AuthenticationController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Web;
+using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Security;
@@ -103,6 +104,7 @@ namespace Umbraco.Web.Editors
///
///
///
+ [SetAngularAntiForgeryToken]
public UserDetail PostLogin(string username, string password)
{
if (UmbracoContext.Security.ValidateBackOfficeCredentials(username, password))
@@ -111,6 +113,7 @@ namespace Umbraco.Web.Editors
//TODO: Clean up the int cast!
var timeoutSeconds = UmbracoContext.Security.PerformLogin((int)user.Id);
+
var result = Mapper.Map(user);
//set their remaining seconds
result.SecondsUntilTimeout = timeoutSeconds;
@@ -129,9 +132,10 @@ namespace Umbraco.Web.Editors
/// Logs the current user out
///
///
+ [UmbracoBackOfficeLogout]
+ [ClearAngularAntiForgeryToken]
public HttpResponseMessage PostLogout()
- {
- UmbracoContext.Security.ClearCurrentLogin();
+ {
return Request.CreateResponse(HttpStatusCode.OK);
}
}
diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs
index aa4966b14c..77f1a832fd 100644
--- a/src/Umbraco.Web/Editors/UpdateCheckController.cs
+++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs
@@ -57,6 +57,7 @@ namespace Umbraco.Web.Editors
//there is a result, set the outgoing cookie
var cookie = new CookieHeaderValue("UMB_UPDCHK", "1")
{
+ Path = "/",
Expires = DateTimeOffset.Now.AddDays(GlobalSettings.VersionCheckPeriod),
HttpOnly = true,
Secure = GlobalSettings.UseSSL
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index c96f7a9501..a2b12ce893 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -440,7 +440,12 @@
+
+
+
+
+
diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs
new file mode 100644
index 0000000000..ba98fb51f1
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs
@@ -0,0 +1,56 @@
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Web.Helpers;
+using Umbraco.Core;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ public static class AngularAntiForgeryHelper
+ {
+ public const string CookieName = "XSRF-TOKEN";
+ public const string Headername = "X-XSRF-TOKEN";
+
+ public static bool Validate(HttpRequestHeaders requestHeaders, out string failedReason)
+ {
+ failedReason = "";
+
+ if (requestHeaders.Any(z => StringExtensions.InvariantEquals(z.Key, Headername)) == false)
+ {
+ failedReason = "Missing token";
+ return false;
+ }
+
+ var headerToken = requestHeaders
+ .Where(z => z.Key.InvariantEquals(Headername))
+ .Select(z => z.Value)
+ .SelectMany(z => z)
+ .FirstOrDefault();
+
+ var cookieToken = requestHeaders
+ .GetCookies()
+ .Select(c => c[CookieName])
+ .FirstOrDefault();
+
+ // both header and cookie must be there
+ if (cookieToken == null || headerToken == null)
+ {
+ failedReason = "Missing token null";
+ return false;
+ }
+
+ // ensure that the cookie matches the header and then ensure it matches the correct value!
+ try
+ {
+ AntiForgery.Validate(cookieToken.Value, headerToken);
+ }
+ catch
+ {
+ failedReason = "Invalid token";
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs
new file mode 100644
index 0000000000..14994080d2
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Web.Http.Filters;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ public sealed class ClearAngularAntiForgeryTokenAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(HttpActionExecutedContext context)
+ {
+ if (context.Response == null) return;
+
+ //remove the cookie
+ var cookie = new CookieHeaderValue(AngularAntiForgeryHelper.CookieName, "null")
+ {
+ Expires = DateTime.Now.AddYears(-1),
+ //must be js readable
+ HttpOnly = false,
+ Path = "/"
+ };
+ context.Response.Headers.AddCookies(new[] { cookie });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokenAttribute.cs
new file mode 100644
index 0000000000..07c6474032
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokenAttribute.cs
@@ -0,0 +1,33 @@
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Web.Helpers;
+using System.Web.Http.Filters;
+using Umbraco.Core.Configuration;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ ///
+ /// A filter to set the csrf cookie token based on angular conventions
+ ///
+ public sealed class SetAngularAntiForgeryTokenAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(HttpActionExecutedContext context)
+ {
+ if (context.Response == null) return;
+
+ string cookieToken, formToken;
+ AntiForgery.GetTokens(null, out cookieToken, out formToken);
+
+ //there is a result, set the outgoing cookie
+ var cookie = new CookieHeaderValue(AngularAntiForgeryHelper.CookieName, cookieToken)
+ {
+ Path = "/",
+ //must be js readable
+ HttpOnly = false,
+ Secure = GlobalSettings.UseSSL
+ };
+ context.Response.Headers.AddCookies(new[] { cookie });
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs
new file mode 100644
index 0000000000..e8c37c81c8
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs
@@ -0,0 +1,22 @@
+using System.Web.Http.Filters;
+using Umbraco.Core.Security;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ ///
+ /// A filter that is used to remove the authorization cookie for the current user
+ ///
+ ///
+ /// This is used so that we can log a user out in conjunction with using other filters that modify the cookies collection.
+ /// SD: I beleive this is a bug with web api since if you modify the cookies collection on the HttpContext.Current and then
+ /// use a filter to write the cookie headers, the filter seems to have no affect at all.
+ ///
+ public sealed class UmbracoBackOfficeLogoutAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuted(HttpActionExecutedContext context)
+ {
+ if (context.Response == null) return;
+ context.Response.UmbracoLogout();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs
new file mode 100644
index 0000000000..cdbe3de4ac
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs
@@ -0,0 +1,28 @@
+using System.Net;
+using System.Net.Http;
+using System.Web.Http.Filters;
+
+namespace Umbraco.Web.WebApi.Filters
+{
+ ///
+ /// A filter to check for the csrf token based on Angular's standard approach
+ ///
+ ///
+ /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/
+ ///
+ public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
+ {
+ string failedReason;
+ if (AngularAntiForgeryHelper.Validate(actionContext.Request.Headers, out failedReason) == false)
+ {
+ actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.ExpectationFailed);
+ actionContext.Response.ReasonPhrase = failedReason;
+ return;
+ }
+
+ base.OnActionExecuting(actionContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs
index 7262d3b759..27c81f5c8d 100644
--- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs
+++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
@@ -12,7 +13,7 @@ using Umbraco.Core;
namespace Umbraco.Web.WebApi
{
-
+
public static class HttpRequestMessageExtensions
{
///
diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj
index 864cd78f9c..1d1d543f08 100644
--- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj
+++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj
@@ -124,6 +124,7 @@
System.Data
+
System.Web