From 5818c0b5b71d4b0f21ef0ff5fc34a64200df10c0 Mon Sep 17 00:00:00 2001 From: Harsh Gupta Date: Fri, 20 Mar 2015 18:54:04 -0700 Subject: [PATCH] Fixes #583 Generating a unique cookie name for an application. --- .../AntiForgery/AntiForgery.cs | 21 +++++++++++++- .../AntiForgery/AntiForgeryOptions.cs | 6 ---- .../ValidateAntiForgeryTokenAttributeTest.cs | 7 ++++- .../Rendering/DefaultTemplatesUtilities.cs | 7 ++++- .../AntiForgeryTestHelper.cs | 22 +++++++++++---- .../AntiForgeryTests.cs | 28 +++++++++---------- .../TestableHtmlGenerator.cs | 8 +++++- 7 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index b4a51a064f..cc04379a88 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -1,10 +1,15 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.WebUtilities; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; @@ -24,9 +29,13 @@ namespace Microsoft.AspNet.Mvc [NotNull] IDataProtectionProvider dataProtectionProvider, [NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider, [NotNull] IOptions mvcOptions, - [NotNull] IHtmlEncoder htmlEncoder) + [NotNull] IHtmlEncoder htmlEncoder, + [NotNull] IOptions dataProtectionOptions) { var config = mvcOptions.Options.AntiForgeryOptions; + var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty; + config.CookieName = config.CookieName ?? ComputeCookieName(applicationId); + var serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); var tokenStore = new AntiForgeryTokenStore(config, serializer); var tokenProvider = new TokenProvider(config, claimUidExtractor, additionalDataProvider); @@ -109,5 +118,15 @@ namespace Microsoft.AspNet.Mvc { _worker.SetCookieTokenAndHeader(context); } + + private string ComputeCookieName(string applicationId) + { + using (var sha256 = SHA256.Create()) + { + var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId)); + var subHash = hash.Take(8).ToArray(); + return WebEncoders.Base64UrlEncode(subHash); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryOptions.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryOptions.cs index af92875826..d600bc0925 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryOptions.cs @@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc public AntiForgeryOptions() { - _cookieName = GetAntiForgeryCookieName(); } /// @@ -93,10 +92,5 @@ namespace Microsoft.AspNet.Mvc get; set; } - - private string GetAntiForgeryCookieName() - { - return AntiForgeryTokenFieldName; - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/ValidateAntiForgeryTokenAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/ValidateAntiForgeryTokenAttributeTest.cs index db4a21a904..1cd336a7ac 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/ValidateAntiForgeryTokenAttributeTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/ValidateAntiForgeryTokenAttributeTest.cs @@ -35,12 +35,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test var dataProtectionProvider = new Mock(); var additionalDataProvider = new Mock(); var optionsAccessor = new Mock>(); + var mockDataProtectionOptions = new Mock>(); + mockDataProtectionOptions + .SetupGet(options => options.Options) + .Returns(Mock.Of()); optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions()); return new AntiForgery(claimExtractor.Object, dataProtectionProvider.Object, additionalDataProvider.Object, optionsAccessor.Object, - new HtmlEncoder()); + new HtmlEncoder(), + mockDataProtectionOptions.Object); } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs index 4f2962afab..c6d5e7c110 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs @@ -258,13 +258,18 @@ namespace Microsoft.AspNet.Mvc.Rendering var dataProtectionProvider = new Mock(); var additionalDataProvider = new Mock(); var optionsAccessor = new Mock>(); + var mockDataProtectionOptions = new Mock>(); + mockDataProtectionOptions + .SetupGet(options => options.Options) + .Returns(Mock.Of()); optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions()); return new AntiForgery( claimExtractor.Object, dataProtectionProvider.Object, additionalDataProvider.Object, optionsAccessor.Object, - new HtmlEncoder()); + new HtmlEncoder(), + mockDataProtectionOptions.Object); } private static string FormatOutput(ModelExplorer modelExplorer) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs index 67ef8a9d9d..c0f07ae902 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs @@ -51,13 +51,25 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } } - public static string RetrieveAntiForgeryCookie(HttpResponseMessage response) + public static CookieMetadata RetrieveAntiForgeryCookie(HttpResponseMessage response) { var setCookieArray = response.Headers.GetValues("Set-Cookie").ToArray(); - return setCookieArray[0].Split(';') - .Where(headerValue => headerValue.StartsWith("__RequestVerificationToken")) - .First() - .Split('=')[1]; + var cookie = setCookieArray[0].Split(';').First().Split('='); + var cookieKey = cookie[0]; + var cookieData = cookie[1]; + + return new CookieMetadata() + { + Key = cookieKey, + Value = cookieData + }; + } + + public class CookieMetadata + { + public string Key { get; set; } + + public string Value { get; set; } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index 8df272737c..2f235086a7 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -38,8 +38,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Even though there are two forms there should only be one response cookie, // as for the second form, the cookie from the first token should be reused. - Assert.Equal(1, setCookieHeader.Length); - Assert.True(setCookieHeader[0].StartsWith("__RequestVerificationToken")); + Assert.Single(setCookieHeader); } [Fact] @@ -59,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", cookieToken.Key + "=" + cookieToken.Value); var nameValueCollection = new List> { new KeyValuePair("__RequestVerificationToken", formToken), @@ -88,9 +87,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var resposneBody = await getResponse.Content.ReadAsStringAsync(); var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/Login"); - var cookieToken = "asdad"; + var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", cookieToken.Key + "=invalidCookie"); var nameValueCollection = new List> { @@ -121,7 +120,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); var formToken = "adsad"; - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", cookieToken.Key + "=" + cookieToken.Value); var nameValueCollection = new List> { new KeyValuePair("__RequestVerificationToken", formToken), @@ -156,9 +155,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var resposneBody2 = await getResponse2.Content.ReadAsStringAsync(); var cookieToken2 = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse2); - var cookieToken = cookieToken2; + var cookieToken = cookieToken2.Value; var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", string.Format("{0}={1}", cookieToken2.Key, cookieToken2.Value)); var formToken = formToken1; var nameValueCollection = new List> { @@ -188,6 +187,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var getResponse = await client.GetAsync("http://localhost/Account/Login"); var resposneBody = await getResponse.Content.ReadAsStringAsync(); var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/Login"); + var cookieTokenKey = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse).Key; var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); var nameValueCollection = new List> @@ -204,7 +204,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert var exception = response.GetServerException(); - Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); + Assert.Equal( + "The required anti-forgery cookie \"" + cookieTokenKey + "\" is not present.", + exception.ExceptionMessage); } [Fact] @@ -218,7 +220,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", cookieToken.Key + "=" + cookieToken.Value); var nameValueCollection = new List> { new KeyValuePair("UserName", "abra"), @@ -251,9 +253,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("SAMEORIGIN", header); var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray(); - - var cookie = Assert.Single(setCookieHeader); - Assert.True(cookie.StartsWith("__RequestVerificationToken")); + Assert.Single(setCookieHeader); } [Fact] @@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/FlushAsyncLogin"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + request.Headers.Add("Cookie", cookieToken.Key + "=" + cookieToken.Value); var nameValueCollection = new List> { new KeyValuePair("__RequestVerificationToken", formToken), diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs index d763c0480f..23e933b196 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs @@ -94,6 +94,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called. var optionsAccessor = new Mock>(); + var mockDataProtectionOptions = new Mock>(); + mockDataProtectionOptions + .SetupGet(options => options.Options) + .Returns(Mock.Of()); + optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions()); optionsAccessor .SetupGet(o => o.Options) .Returns(new MvcOptions()); @@ -102,7 +107,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Mock.Of(), Mock.Of(), optionsAccessor.Object, - new HtmlEncoder()); + new HtmlEncoder(), + mockDataProtectionOptions.Object); return antiForgery; }