Fixes #583 Generating a unique cookie name for an application.

This commit is contained in:
Harsh Gupta 2015-03-20 18:54:04 -07:00
parent c1eea5b3fa
commit 5818c0b5b7
7 changed files with 70 additions and 29 deletions

View File

@ -1,10 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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 System.Threading.Tasks;
using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.DataProtection;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.WebEncoders; using Microsoft.Framework.WebEncoders;
@ -24,9 +29,13 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IDataProtectionProvider dataProtectionProvider, [NotNull] IDataProtectionProvider dataProtectionProvider,
[NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider, [NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider,
[NotNull] IOptions<MvcOptions> mvcOptions, [NotNull] IOptions<MvcOptions> mvcOptions,
[NotNull] IHtmlEncoder htmlEncoder) [NotNull] IHtmlEncoder htmlEncoder,
[NotNull] IOptions<DataProtectionOptions> dataProtectionOptions)
{ {
var config = mvcOptions.Options.AntiForgeryOptions; 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 serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose));
var tokenStore = new AntiForgeryTokenStore(config, serializer); var tokenStore = new AntiForgeryTokenStore(config, serializer);
var tokenProvider = new TokenProvider(config, claimUidExtractor, additionalDataProvider); var tokenProvider = new TokenProvider(config, claimUidExtractor, additionalDataProvider);
@ -109,5 +118,15 @@ namespace Microsoft.AspNet.Mvc
{ {
_worker.SetCookieTokenAndHeader(context); _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);
}
}
} }
} }

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc
public AntiForgeryOptions() public AntiForgeryOptions()
{ {
_cookieName = GetAntiForgeryCookieName();
} }
/// <summary> /// <summary>
@ -93,10 +92,5 @@ namespace Microsoft.AspNet.Mvc
get; get;
set; set;
} }
private string GetAntiForgeryCookieName()
{
return AntiForgeryTokenFieldName;
}
} }
} }

View File

@ -35,12 +35,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test
var dataProtectionProvider = new Mock<IDataProtectionProvider>(); var dataProtectionProvider = new Mock<IDataProtectionProvider>();
var additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>(); var additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
var optionsAccessor = new Mock<IOptions<MvcOptions>>(); var optionsAccessor = new Mock<IOptions<MvcOptions>>();
var mockDataProtectionOptions = new Mock<IOptions<DataProtectionOptions>>();
mockDataProtectionOptions
.SetupGet(options => options.Options)
.Returns(Mock.Of<DataProtectionOptions>());
optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions()); optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions());
return new AntiForgery(claimExtractor.Object, return new AntiForgery(claimExtractor.Object,
dataProtectionProvider.Object, dataProtectionProvider.Object,
additionalDataProvider.Object, additionalDataProvider.Object,
optionsAccessor.Object, optionsAccessor.Object,
new HtmlEncoder()); new HtmlEncoder(),
mockDataProtectionOptions.Object);
} }
} }
} }

View File

@ -258,13 +258,18 @@ namespace Microsoft.AspNet.Mvc.Rendering
var dataProtectionProvider = new Mock<IDataProtectionProvider>(); var dataProtectionProvider = new Mock<IDataProtectionProvider>();
var additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>(); var additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
var optionsAccessor = new Mock<IOptions<MvcOptions>>(); var optionsAccessor = new Mock<IOptions<MvcOptions>>();
var mockDataProtectionOptions = new Mock<IOptions<DataProtectionOptions>>();
mockDataProtectionOptions
.SetupGet(options => options.Options)
.Returns(Mock.Of<DataProtectionOptions>());
optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions()); optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions());
return new AntiForgery( return new AntiForgery(
claimExtractor.Object, claimExtractor.Object,
dataProtectionProvider.Object, dataProtectionProvider.Object,
additionalDataProvider.Object, additionalDataProvider.Object,
optionsAccessor.Object, optionsAccessor.Object,
new HtmlEncoder()); new HtmlEncoder(),
mockDataProtectionOptions.Object);
} }
private static string FormatOutput(ModelExplorer modelExplorer) private static string FormatOutput(ModelExplorer modelExplorer)

View File

@ -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(); var setCookieArray = response.Headers.GetValues("Set-Cookie").ToArray();
return setCookieArray[0].Split(';') var cookie = setCookieArray[0].Split(';').First().Split('=');
.Where(headerValue => headerValue.StartsWith("__RequestVerificationToken")) var cookieKey = cookie[0];
.First() var cookieData = cookie[1];
.Split('=')[1];
return new CookieMetadata()
{
Key = cookieKey,
Value = cookieData
};
}
public class CookieMetadata
{
public string Key { get; set; }
public string Value { get; set; }
} }
} }
} }

View File

@ -38,8 +38,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Even though there are two forms there should only be one response cookie, // 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. // as for the second form, the cookie from the first token should be reused.
Assert.Equal(1, setCookieHeader.Length); Assert.Single(setCookieHeader);
Assert.True(setCookieHeader[0].StartsWith("__RequestVerificationToken"));
} }
[Fact] [Fact]
@ -59,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); 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<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string,string>("__RequestVerificationToken", formToken), new KeyValuePair<string,string>("__RequestVerificationToken", formToken),
@ -88,9 +87,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var resposneBody = await getResponse.Content.ReadAsStringAsync(); var resposneBody = await getResponse.Content.ReadAsStringAsync();
var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/Login"); 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"); 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<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
@ -121,7 +120,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login");
var formToken = "adsad"; var formToken = "adsad";
request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); request.Headers.Add("Cookie", cookieToken.Key + "=" + cookieToken.Value);
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string,string>("__RequestVerificationToken", formToken), new KeyValuePair<string,string>("__RequestVerificationToken", formToken),
@ -156,9 +155,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var resposneBody2 = await getResponse2.Content.ReadAsStringAsync(); var resposneBody2 = await getResponse2.Content.ReadAsStringAsync();
var cookieToken2 = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse2); var cookieToken2 = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse2);
var cookieToken = cookieToken2; var cookieToken = cookieToken2.Value;
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); 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 formToken = formToken1;
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
@ -188,6 +187,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var getResponse = await client.GetAsync("http://localhost/Account/Login"); var getResponse = await client.GetAsync("http://localhost/Account/Login");
var resposneBody = await getResponse.Content.ReadAsStringAsync(); var resposneBody = await getResponse.Content.ReadAsStringAsync();
var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/Login"); var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/Login");
var cookieTokenKey = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse).Key;
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login");
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
@ -204,7 +204,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert // Assert
var exception = response.GetServerException(); 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] [Fact]
@ -218,7 +220,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/Login"); 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<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string,string>("UserName", "abra"), new KeyValuePair<string,string>("UserName", "abra"),
@ -251,9 +253,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("SAMEORIGIN", header); Assert.Equal("SAMEORIGIN", header);
var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray(); var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray();
Assert.Single(setCookieHeader);
var cookie = Assert.Single(setCookieHeader);
Assert.True(cookie.StartsWith("__RequestVerificationToken"));
} }
[Fact] [Fact]
@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/FlushAsyncLogin"); 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<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string,string>("__RequestVerificationToken", formToken), new KeyValuePair<string,string>("__RequestVerificationToken", formToken),

View File

@ -94,6 +94,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
// AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called. // AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called.
var optionsAccessor = new Mock<IOptions<MvcOptions>>(); var optionsAccessor = new Mock<IOptions<MvcOptions>>();
var mockDataProtectionOptions = new Mock<IOptions<DataProtectionOptions>>();
mockDataProtectionOptions
.SetupGet(options => options.Options)
.Returns(Mock.Of<DataProtectionOptions>());
optionsAccessor.SetupGet(o => o.Options).Returns(new MvcOptions());
optionsAccessor optionsAccessor
.SetupGet(o => o.Options) .SetupGet(o => o.Options)
.Returns(new MvcOptions()); .Returns(new MvcOptions());
@ -102,7 +107,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IDataProtectionProvider>(), Mock.Of<IDataProtectionProvider>(),
Mock.Of<IAntiForgeryAdditionalDataProvider>(), Mock.Of<IAntiForgeryAdditionalDataProvider>(),
optionsAccessor.Object, optionsAccessor.Object,
new HtmlEncoder()); new HtmlEncoder(),
mockDataProtectionOptions.Object);
return antiForgery; return antiForgery;
} }