Fixes #583 Generating a unique cookie name for an application.
This commit is contained in:
parent
c1eea5b3fa
commit
5818c0b5b7
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue