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.
// 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> mvcOptions,
[NotNull] IHtmlEncoder htmlEncoder)
[NotNull] IHtmlEncoder htmlEncoder,
[NotNull] IOptions<DataProtectionOptions> 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);
}
}
}
}

View File

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

View File

@ -35,12 +35,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test
var dataProtectionProvider = new Mock<IDataProtectionProvider>();
var additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
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());
return new AntiForgery(claimExtractor.Object,
dataProtectionProvider.Object,
additionalDataProvider.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 additionalDataProvider = new Mock<IAntiForgeryAdditionalDataProvider>();
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());
return new AntiForgery(
claimExtractor.Object,
dataProtectionProvider.Object,
additionalDataProvider.Object,
optionsAccessor.Object,
new HtmlEncoder());
new HtmlEncoder(),
mockDataProtectionOptions.Object);
}
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();
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; }
}
}
}

View File

@ -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<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("__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<KeyValuePair<string, string>>
{
@ -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<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("__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<KeyValuePair<string, string>>
{
@ -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<KeyValuePair<string, string>>
@ -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<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("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<KeyValuePair<string, string>>
{
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.
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());
@ -102,7 +107,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IDataProtectionProvider>(),
Mock.Of<IAntiForgeryAdditionalDataProvider>(),
optionsAccessor.Object,
new HtmlEncoder());
new HtmlEncoder(),
mockDataProtectionOptions.Object);
return antiForgery;
}