diff --git a/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs b/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs index fdb07b47d2..77804e3801 100644 --- a/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs +++ b/src/Umbraco.Web.BackOffice/Filters/SetAngularAntiForgeryTokens.cs @@ -36,7 +36,7 @@ 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 - var valResult = await _antiforgery.ValidateHeadersAsync(context.HttpContext); + var valResult = await _antiforgery.ValidateRequestAsync(context.HttpContext); if (valResult.Success) { await next(); diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs index 66e59ada6f..cdfd2cb4b7 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs @@ -1,18 +1,10 @@ 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 { @@ -38,48 +30,8 @@ namespace Umbraco.Web.BackOffice.Security _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) + /// + public async Task> ValidateRequestAsync(HttpContext httpContext) { httpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var cookieToken); @@ -88,6 +40,61 @@ namespace Umbraco.Web.BackOffice.Security cookieToken == null ? null : cookieToken); } + /// + public void GetTokens(HttpContext httpContext, out string cookieToken, out string headerToken) + { + var set = _defaultAntiforgery.GetTokens(httpContext); + + cookieToken = set.CookieToken; + headerToken = set.RequestToken; + } + + /// + /// Validates the cookie and header tokens + /// + /// + /// + /// + /// + private async Task ValidateTokensAsync(HttpContext httpContext, string cookieToken, string headerToken) + { + // TODO: see https://github.com/dotnet/aspnetcore/issues/22217 + // An alternative way to doing this would be to create a separate container specifically for antiforgery and add custom options to + // it and resolve services directly from there. Could be worth a shot and could actually be a way for us to deal with these global + // things later on if this problem arises again. + + // We need to do some tricks here, save the initial cookie/header vals, then reset later + var originalCookies = httpContext.Request.Cookies; + var originalCookiesHeader = httpContext.Request.Headers[HeaderNames.Cookie]; + var originalHeader = httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName]; + + try + { + // swap the cookie anti-forgery cookie value for the one we want to validate + // (this is how you modify 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(); + + // swap the anti-forgery header value for the one we want to validate + httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName] = headerToken; + + // now validate + return await _defaultAntiforgery.IsRequestValidAsync(httpContext); + } + finally + { + // reset + + // change request cookies back to original + var cookieHeaderVals = CookieHeaderValue.ParseList(originalCookiesHeader); + httpContext.Request.Headers[HeaderNames.Cookie] = cookieHeaderVals.Select(c => c.ToString()).ToArray(); + + // change the header back to normal + httpContext.Request.Headers[_antiforgeryOptions.Value.HeaderName] = originalHeader; + } + } + private async Task> ValidateHeadersAsync( HttpContext httpContext, string cookieToken) @@ -114,14 +121,5 @@ namespace Umbraco.Web.BackOffice.Security 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 index 5b0f6e33dd..d34fd493df 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeAntiforgery.cs @@ -8,10 +8,22 @@ namespace Umbraco.Web.BackOffice.Security /// /// Antiforgery implementation for the Umbraco back office /// - public interface IBackOfficeAntiforgery //: IAntiforgery + public interface IBackOfficeAntiforgery { - Task> ValidateHeadersAsync(HttpContext httpContext); - Task ValidateTokensAsync(HttpContext httpContext, string cookieToken, string headerToken); + /// + /// Validates the headers/cookies passed in for the request + /// + /// + /// + /// + Task> ValidateRequestAsync(HttpContext httpContext); + + /// + /// Generates tokens to use for the cookie and header antiforgery values + /// + /// + /// + /// void GetTokens(HttpContext httpContext, out string cookieToken, out string headerToken); } }