Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/NuGetGallery/Infrastructure/CookieTempDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ protected virtual IDictionary<string, object> LoadTempData(ControllerContext con

protected virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> 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);
Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ public void StoresValuesInCookieInEncodedFormat()
{
var cookies = new HttpCookieCollection();
var httpContext = new Mock<HttpContextBase>();
var items = new System.Collections.Generic.Dictionary<object, object>
{
[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();
Expand All @@ -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");
Expand Down