From 9fd60ff864009cc712fc9d65387a7b580d8adf82 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 5 Jan 2018 10:47:51 -0800 Subject: [PATCH] Marked CookieTempDataProvider's cookie as non-essential [Fixes #7160] Determine if the temp data cookie is considered essential --- build/dependencies.props | 1 + .../CookieTempDataProviderOptions.cs | 11 +- ...TempDataInCookiesUsingCookieConsentTest.cs | 112 ++++++++++++++++++ .../WebSites/BasicWebSite/BasicWebSite.csproj | 1 + .../Controllers/TempDataController.cs | 7 ++ ...hCookieTempDataProviderAndCookieConsent.cs | 31 +++++ 6 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs create mode 100644 test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs diff --git a/build/dependencies.props b/build/dependencies.props index fb1b20df8d..06095e77c8 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -12,6 +12,7 @@ 2.1.0-preview1-27965 2.1.0-preview1-27965 2.1.0-preview1-27965 + 2.1.0-preview1-27965 2.1.0-preview1-27965 2.1.0-preview1-27965 2.1.0-preview1-27965 diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs index 0b2badbd32..138105d7c8 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs @@ -18,6 +18,11 @@ namespace Microsoft.AspNetCore.Mvc HttpOnly = true, SameSite = SameSiteMode.Strict, + // This cookie has been marked as non-essential because a user could use the SessionStateTempDataProvider, + // which is more common in production scenarios. Check the comment on CookieBuilder below + // for more information. + IsEssential = false, + // Some browsers do not allow non-secure endpoints to set cookies with a 'secure' flag or overwrite cookies // whose 'secure' flag is set (http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-alone.html). // Since mixing secure and non-secure endpoints is a common scenario in applications, we are relaxing the @@ -33,7 +38,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// defaults to . /// defaults to . - /// defaults to true + /// defaults to true. + /// defaults to false, This property is only considered when a + /// user opts into the CookiePolicyMiddleware. If you are using this middleware and want to use + /// , then either set this property to true or + /// request user consent for non-essential cookies. /// /// public CookieBuilder Cookie diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs new file mode 100644 index 0000000000..c109e64a41 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. 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.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class TempDataInCookiesUsingCookieConsentTest + : IClassFixture> + { + private readonly HttpClient _client; + + public TempDataInCookiesUsingCookieConsentTest( + MvcTestFixture fixture) + { + _client = fixture.Client; + } + + [Fact] + public async Task CookieTempDataProviderCookie_SetInResponse_OnGrantingConsent() + { + // Arrange + var nameValueCollection = new List> + { + new KeyValuePair("value", "Foo"), + }; + var content = new FormUrlEncodedContent(nameValueCollection); + // This response would have the consent cookie which would be sent on rest of the requests here + var response = await _client.GetAsync("/TempData/GrantConsent"); + + // Act 1 + response = await _client.SendAsync(GetPostRequest("/TempData/SetTempData", content, response)); + + // Assert 1 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Act 2 + response = await _client.SendAsync(GetRequest("/TempData/GetTempData", response)); + + // Assert 2 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal("Foo", body); + + // Act 3 + response = await _client.SendAsync(GetRequest("/TempData/GetTempData", response)); + + // Assert 3 + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + [Fact] + public async Task CookieTempDataProviderCookie_NotSetInResponse_OnNoConsent() + { + // Arrange + var nameValueCollection = new List> + { + new KeyValuePair("value", "Foo"), + }; + var content = new FormUrlEncodedContent(nameValueCollection); + + // Act 1 + var response = await _client.PostAsync("/TempData/SetTempData", content); + + // Assert 1 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Act 2 + response = await _client.SendAsync(GetRequest("/TempData/GetTempData", response)); + + // Assert 2 + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + private HttpRequestMessage GetRequest(string path, HttpResponseMessage response) + { + var request = new HttpRequestMessage(HttpMethod.Get, path); + SetCookieHeaders(request, response); + return request; + } + + private HttpRequestMessage GetPostRequest(string path, HttpContent content, HttpResponseMessage response) + { + var request = new HttpRequestMessage(HttpMethod.Post, path); + request.Content = content; + SetCookieHeaders(request, response); + return request; + } + + private void SetCookieHeaders(HttpRequestMessage request, HttpResponseMessage response) + { + IEnumerable values; + if (response.Headers.TryGetValues("Set-Cookie", out values)) + { + foreach (var cookie in SetCookieHeaderValue.ParseList(values.ToList())) + { + if (cookie.Expires == null || cookie.Expires >= DateTimeOffset.UtcNow) + { + request.Headers.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString()); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/test/WebSites/BasicWebSite/BasicWebSite.csproj index ca26efa240..b001b4aa71 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.csproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.csproj @@ -13,5 +13,6 @@ + diff --git a/test/WebSites/BasicWebSite/Controllers/TempDataController.cs b/test/WebSites/BasicWebSite/Controllers/TempDataController.cs index a0fedabe02..1adeb1922a 100644 --- a/test/WebSites/BasicWebSite/Controllers/TempDataController.cs +++ b/test/WebSites/BasicWebSite/Controllers/TempDataController.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using BasicWebSite.Filters; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; namespace BasicWebSite.Controllers @@ -117,5 +118,11 @@ namespace BasicWebSite.Controllers { return TempData[nameof(UnhandledExceptionAndSettingTempData)]?.ToString(); } + + [HttpGet] + public void GrantConsent() + { + HttpContext.Features.Get().GrantConsent(); + } } } diff --git a/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs b/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs new file mode 100644 index 0000000000..7030e5eaaa --- /dev/null +++ b/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupWithCookieTempDataProviderAndCookieConsent + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + + services.Configure(o => + { + o.CheckConsentNeeded = httpContext => true; + }); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.UseCookiePolicy(); + + app.UseMvcWithDefaultRoute(); + } + } +} +