diff --git a/src/Umbraco.Web.BackOffice/Extensions/AngularAntiForgeryExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/AngularAntiForgeryExtensions.cs
deleted file mode 100644
index a9e0c64751..0000000000
--- a/src/Umbraco.Web.BackOffice/Extensions/AngularAntiForgeryExtensions.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Core;
-using Microsoft.AspNetCore.Antiforgery;
-using Microsoft.AspNetCore.Http;
-
-namespace Umbraco.Extensions
-{
- ///
- /// A helper class to deal with csrf prevention with angularjs and webapi
- ///
- public static class AngularAntiForgeryExtensions
- {
- ///
- /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value
- ///
- ///
- ///
- ///
- /// .Net provides us a way to validate one token with another for added security. With the way angular works, this
- /// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate
- /// this header value against our original cookie value.
- ///
- public static void GetTokens(this IAntiforgery antiforgery, HttpContext httpContext, out string cookieToken, out string headerToken)
- {
- var result = antiforgery.GetTokens(httpContext);
-
- cookieToken = result.CookieToken;
- headerToken = result.RequestToken;
- }
-
- /////
- ///// Validates the header token against the validation cookie value
- /////
- /////
- /////
- /////
- //public static bool ValidateTokens(this IAntiforgery antiforgery, HttpContext httpContext, string cookieToken, string headerToken)
- //{
- // // ensure that the cookie matches the header and then ensure it matches the correct value!
- // try
- // {
- // antiforgery.Va .Validate(cookieToken, headerToken);
- // }
- // catch (Exception ex)
- // {
- // Current.Logger.Error(typeof(AngularAntiForgeryHelper), ex, "Could not validate XSRF token");
- // return false;
- // }
- // return true;
- //}
-
- //internal static bool ValidateHeaders(
- // KeyValuePair>[] requestHeaders,
- // string cookieToken,
- // out string failedReason)
- //{
- // failedReason = "";
-
- // if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false)
- // {
- // failedReason = "Missing token";
- // return false;
- // }
-
- // var headerToken = requestHeaders
- // .Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername))
- // .Select(z => z.Value)
- // .SelectMany(z => z)
- // .FirstOrDefault();
-
- // // both header and cookie must be there
- // if (cookieToken == null || headerToken == null)
- // {
- // failedReason = "Missing token null";
- // return false;
- // }
-
- // if (ValidateTokens(cookieToken, headerToken) == false)
- // {
- // failedReason = "Invalid token";
- // return false;
- // }
-
- // return true;
- //}
-
- /////
- ///// Validates the headers/cookies passed in for the request
- /////
- /////
- /////
- /////
- //public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason)
- //{
- // var cookieToken = requestHeaders.GetCookieValue(Constants.Web.CsrfValidationCookieName);
-
- // return ValidateHeaders(
- // requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(),
- // cookieToken == null ? null : cookieToken,
- // out failedReason);
- //}
-
- }
-}
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
index e4b81d806f..5bf5224b07 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
@@ -11,6 +11,19 @@ namespace Umbraco.Extensions
{
public static class UmbracoBackOfficeServiceCollectionExtensions
{
+ ///
+ /// Adds the services required for running the Umbraco back office
+ ///
+ ///
+ public static void AddUmbracoBackOffice(this IServiceCollection services)
+ {
+ services.AddAntiforgery();
+ }
+
+ ///
+ /// Adds the services required for using Umbraco back office Identity
+ ///
+ ///
public static void AddUmbracoBackOfficeIdentity(this IServiceCollection services)
{
services.AddDataProtection();
diff --git a/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs b/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs
index 8a830181cb..fdb07b47d2 100644
--- a/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration;
+using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Extensions
{
@@ -14,10 +15,10 @@ namespace Umbraco.Extensions
///
public sealed class SetAngularAntiForgeryTokens : IAsyncActionFilter
{
- private readonly IAntiforgery _antiforgery;
+ private readonly IBackOfficeAntiforgery _antiforgery;
private readonly IGlobalSettings _globalSettings;
- public SetAngularAntiForgeryTokens(IAntiforgery antiforgery, IGlobalSettings globalSettings)
+ public SetAngularAntiForgeryTokens(IBackOfficeAntiforgery antiforgery, IGlobalSettings globalSettings)
{
_antiforgery = antiforgery;
_globalSettings = globalSettings;
@@ -35,7 +36,8 @@ namespace Umbraco.Extensions
&& context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var csrfCookieVal))
{
//if they are not valid for some strange reason - we need to continue setting valid ones
- if (await _antiforgery.IsRequestValidAsync(context.HttpContext))
+ var valResult = await _antiforgery.ValidateHeadersAsync(context.HttpContext);
+ if (valResult.Success)
{
await next();
return;
diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
index 43aae82ed5..2826eddde1 100644
--- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
+++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
@@ -17,6 +17,8 @@ namespace Umbraco.Web.BackOffice.Runtime
composition.RegisterUnique();
composition.RegisterUnique();
composition.Register(Lifetime.Scope);
+
+ composition.RegisterUnique();
}
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs
new file mode 100644
index 0000000000..66e59ada6f
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs
@@ -0,0 +1,127 @@
+using Microsoft.AspNetCore.Antiforgery;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Core;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Hosting;
+
+namespace Umbraco.Web.BackOffice.Security
+{
+
+ ///
+ /// Antiforgery implementation for the Umbraco back office
+ ///
+ ///
+ /// This is a wrapper around the global/default .net service. Because this service is a single/global
+ /// object and all of it is internal we don't have the flexibility to create our own segregated service so we have to work around
+ /// that limitation by wrapping the default and doing a few tricks to have this segregated for the Back office only.
+ ///
+ public class BackOfficeAntiforgery : IBackOfficeAntiforgery
+ {
+ private readonly IAntiforgery _defaultAntiforgery;
+ private readonly IOptions _antiforgeryOptions;
+
+ public BackOfficeAntiforgery(
+ IAntiforgery defaultAntiforgery,
+ IOptions antiforgeryOptions)
+ {
+ _defaultAntiforgery = defaultAntiforgery;
+ _antiforgeryOptions = antiforgeryOptions;
+ }
+
+ public async Task ValidateTokensAsync(HttpContext httpContext, string cookieToken, string headerToken)
+ {
+ // We need to do some tricks here, save the initial cookie vals, then reset later
+ var originalCookies = httpContext.Request.Cookies;
+ var originalCookiesHeader = httpContext.Request.Headers[HeaderNames.Cookie];
+ var originalHeader = httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName];
+ //var originalForm = httpContext.Request.Form;
+ try
+ {
+ // this is how you write to the request cookies, it's the only way
+ var cookieHeaderVals = CookieHeaderValue.ParseList(originalCookiesHeader);
+ cookieHeaderVals.Add(new CookieHeaderValue(_antiforgeryOptions.Value.Cookie.Name, cookieToken));
+ httpContext.Request.Headers[HeaderNames.Cookie] = cookieHeaderVals.Select(c => c.ToString()).ToArray();
+
+ // change the header/form val to ours
+ //var newForm = httpContext.Request.Form.ToDictionary(x => x.Key, x => x.Value);
+ //newForm.Add(_antiforgeryOptions.Value.FormFieldName, headerToken);
+ //httpContext.Request.Form = new FormCollection(newForm);
+ httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName] = headerToken;
+
+ return await _defaultAntiforgery.IsRequestValidAsync(httpContext);
+ }
+ finally
+ {
+ // reset
+ var cookieHeaderVals = CookieHeaderValue.ParseList(originalCookiesHeader);
+ httpContext.Request.Headers[HeaderNames.Cookie] = cookieHeaderVals.Select(c => c.ToString()).ToArray();
+
+ if (originalHeader.Count > 0)
+ httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName] = originalHeader;
+
+ //httpContext.Request.Form = originalForm;
+ }
+ }
+
+ ///
+ /// Validates the headers/cookies passed in for the request
+ ///
+ ///
+ ///
+ ///
+ public async Task> ValidateHeadersAsync(HttpContext httpContext)
+ {
+ httpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var cookieToken);
+
+ return await ValidateHeadersAsync(
+ httpContext,
+ cookieToken == null ? null : cookieToken);
+ }
+
+ private async Task> ValidateHeadersAsync(
+ HttpContext httpContext,
+ string cookieToken)
+ {
+ var requestHeaders = httpContext.Request.Headers;
+ if (!requestHeaders.TryGetValue(Constants.Web.AngularHeadername, out var headerVals))
+ {
+ return Attempt.Fail("Missing token");
+ }
+
+ var headerToken = headerVals.FirstOrDefault();
+
+ // both header and cookie must be there
+ if (cookieToken == null || headerToken == null)
+ {
+ return Attempt.Fail("Missing token null");
+ }
+
+ if (await ValidateTokensAsync(httpContext, cookieToken, headerToken) == false)
+ {
+ return Attempt.Fail("Invalid token");
+ }
+
+ return Attempt.Succeed();
+ }
+
+ public void GetTokens(HttpContext httpContext, out string cookieToken, out string headerToken)
+ {
+ var set = _defaultAntiforgery.GetTokens(httpContext);
+
+ cookieToken = set.RequestToken;
+ headerToken = set.RequestToken;
+ }
+
+
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs
new file mode 100644
index 0000000000..5b0f6e33dd
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Antiforgery;
+using Microsoft.AspNetCore.Http;
+using System.Threading.Tasks;
+using Umbraco.Core;
+
+namespace Umbraco.Web.BackOffice.Security
+{
+ ///
+ /// Antiforgery implementation for the Umbraco back office
+ ///
+ public interface IBackOfficeAntiforgery //: IAntiforgery
+ {
+ Task> ValidateHeadersAsync(HttpContext httpContext);
+ Task ValidateTokensAsync(HttpContext httpContext, string cookieToken, string headerToken);
+ void GetTokens(HttpContext httpContext, out string cookieToken, out string headerToken);
+ }
+}
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 547a320de1..c8dd845546 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -39,10 +39,13 @@ namespace Umbraco.Web.UI.BackOffice
// relying on a global configuration set by a user since if a custom IControllerActivator is used for our own controllers we may not
// guarantee it will work. And then... is that even possible?
+ // TODO: we will need to simplify this and prob just have a one or 2 main method that devs call which call all other required methods,
+ // but for now we'll just be explicit with all of them
services.AddUmbracoConfiguration(_config);
services.AddUmbracoCore(_env, out var factory);
services.AddUmbracoWebComponents();
services.AddUmbracoRuntimeMinifier(_config);
+ services.AddUmbracoBackOffice();
services.AddUmbracoBackOfficeIdentity();
services.AddMvc();