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.
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue