diff --git a/src/NuGetGallery/Infrastructure/CookieTempDataProvider.cs b/src/NuGetGallery/Infrastructure/CookieTempDataProvider.cs index 4444c0d33d..92bf59f9d5 100644 --- a/src/NuGetGallery/Infrastructure/CookieTempDataProvider.cs +++ b/src/NuGetGallery/Infrastructure/CookieTempDataProvider.cs @@ -109,7 +109,12 @@ protected virtual IDictionary LoadTempData(ControllerContext con protected virtual void SaveTempData(ControllerContext controllerContext, IDictionary values) { - if (values.Count > 0) + // Only save TempData as a cookie if the user has consented or if there was already a TempData cookie + // This prevents setting non-essential cookies without user consent + var hasExistingCookie = _httpContext.Request?.Cookies[TempDataCookieKey] != null; + var canWriteCookies = CanWriteNonEssentialCookies(); + + if (values.Count > 0 && (hasExistingCookie || canWriteCookies)) { // Serialize dictionary to JSON string json = JsonConvert.SerializeObject(values); @@ -121,10 +126,24 @@ protected virtual void SaveTempData(ControllerContext controllerContext, IDictio var cookie = new HttpCookie(TempDataCookieKey, protectedJson) { HttpOnly = true, - Secure = true + Secure = true, + SameSite = SameSiteMode.Lax }; _httpContext.Response.Cookies.Add(cookie); } } + + private bool CanWriteNonEssentialCookies() + { + // Check if cookie consent has been granted + // This uses the same mechanism as the CookieComplianceHttpModule + if (_httpContext?.Items?[ServicesConstants.CookieComplianceCanWriteAnalyticsCookies] is bool canWrite) + { + return canWrite; + } + + // If consent information is not available, default to false for safety + return false; + } } } diff --git a/tests/NuGetGallery.Facts/Infrastructure/CookieTempDataProviderFacts.cs b/tests/NuGetGallery.Facts/Infrastructure/CookieTempDataProviderFacts.cs index 5d46322d96..aea13f3356 100644 --- a/tests/NuGetGallery.Facts/Infrastructure/CookieTempDataProviderFacts.cs +++ b/tests/NuGetGallery.Facts/Infrastructure/CookieTempDataProviderFacts.cs @@ -124,6 +124,12 @@ public void StoresValuesInCookieInEncodedFormat() { var cookies = new HttpCookieCollection(); var httpContext = new Mock(); + var items = new System.Collections.Generic.Dictionary + { + [ServicesConstants.CookieComplianceCanWriteAnalyticsCookies] = true + }; + httpContext.Setup(c => c.Items).Returns(items); + httpContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection()); httpContext.Setup(c => c.Response.Cookies).Returns(cookies); ITempDataProvider provider = new CookieTempDataProvider(httpContext.Object); var controllerContext = new ControllerContext(); @@ -140,6 +146,7 @@ public void StoresValuesInCookieInEncodedFormat() var cookie = cookies["__Controller::TempData"]; Assert.True(cookie.HttpOnly); Assert.True(cookie.Secure); + Assert.Equal(SameSiteMode.Lax, cookie.SameSite); // Decrypt and deserialize the cookie value var unprotectedBytes = MachineKey.Unprotect(Convert.FromBase64String(cookie.Value), "__Controller::TempData");