Skip to content

Commit 187f59e

Browse files
committed
Quirks
1 parent 1e281bf commit 187f59e

File tree

6 files changed

+81
-37
lines changed

6 files changed

+81
-37
lines changed

src/Http/Http.Abstractions/src/CookieBuilder.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,20 @@ namespace Microsoft.AspNetCore.Http
1111
/// </summary>
1212
public class CookieBuilder
1313
{
14+
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
15+
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
16+
internal static bool SuppressSameSiteNone;
17+
1418
private string _name;
1519

20+
static CookieBuilder()
21+
{
22+
if (AppContext.TryGetSwitch("Microsoft.Net.Http.Headers.SetCookieHeaderValue.SuppressSameSiteNone", out var enabled))
23+
{
24+
SuppressSameSiteNone = enabled;
25+
}
26+
}
27+
1628
/// <summary>
1729
/// The name of the cookie.
1830
/// </summary>
@@ -49,12 +61,12 @@ public virtual string Name
4961
public virtual bool HttpOnly { get; set; }
5062

5163
/// <summary>
52-
/// The SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.None"/>
64+
/// The SameSite attribute of the cookie. The default value is -1 (Unspecified)
5365
/// </summary>
5466
/// <remarks>
5567
/// Determines the value that will set on <seealso cref="CookieOptions.SameSite"/>.
5668
/// </remarks>
57-
public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.None;
69+
public virtual SameSiteMode SameSite { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
5870

5971
/// <summary>
6072
/// The policy that will be used to determine <seealso cref="CookieOptions.Secure"/>.

src/Http/Http.Features/src/CookieOptions.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ namespace Microsoft.AspNetCore.Http
1010
/// </summary>
1111
public class CookieOptions
1212
{
13+
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
14+
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
15+
internal static bool SuppressSameSiteNone;
16+
17+
static CookieOptions()
18+
{
19+
if (AppContext.TryGetSwitch("Microsoft.Net.Http.Headers.SetCookieHeaderValue.SuppressSameSiteNone", out var enabled))
20+
{
21+
SuppressSameSiteNone = enabled;
22+
}
23+
}
24+
1325
/// <summary>
1426
/// Creates a default cookie with a path of '/'.
1527
/// </summary>
@@ -43,10 +55,10 @@ public CookieOptions()
4355
public bool Secure { get; set; }
4456

4557
/// <summary>
46-
/// Gets or sets the value for the SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.None"/>
58+
/// Gets or sets the value for the SameSite attribute of the cookie. The default value is -1 (Unspecified)
4759
/// </summary>
4860
/// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
49-
public SameSiteMode SameSite { get; set; } = SameSiteMode.None;
61+
public SameSiteMode SameSite { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
5062

5163
/// <summary>
5264
/// Gets or sets a value that indicates whether a cookie is accessible by client-side script.

src/Security/CookiePolicy/src/CookiePolicyOptions.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@ namespace Microsoft.AspNetCore.Builder
1212
/// </summary>
1313
public class CookiePolicyOptions
1414
{
15+
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
16+
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
17+
internal static bool SuppressSameSiteNone;
18+
19+
static CookiePolicyOptions()
20+
{
21+
if (AppContext.TryGetSwitch("Microsoft.Net.Http.Headers.SetCookieHeaderValue.SuppressSameSiteNone", out var enabled))
22+
{
23+
SuppressSameSiteNone = enabled;
24+
}
25+
}
26+
1527
/// <summary>
1628
/// Affects the cookie's same site attribute.
1729
/// </summary>
18-
public SameSiteMode MinimumSameSitePolicy { get; set; } = SameSiteMode.None;
30+
public SameSiteMode MinimumSameSitePolicy { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
1931

2032
/// <summary>
2133
/// Affects whether cookies must be HttpOnly.

src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -115,7 +115,8 @@ public string CreateConsentCookie()
115115
private bool CheckPolicyRequired()
116116
{
117117
return !CanTrack
118-
|| Options.MinimumSameSitePolicy != SameSiteMode.None
118+
|| (CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != SameSiteMode.None)
119+
|| (!CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != (SameSiteMode)(-1)) // Unspecified
119120
|| Options.HttpOnly != HttpOnlyPolicy.None
120121
|| Options.Secure != CookieSecurePolicy.None;
121122
}
@@ -241,27 +242,13 @@ private void ApplyPolicy(string key, CookieOptions options)
241242
default:
242243
throw new InvalidOperationException();
243244
}
244-
switch (Options.MinimumSameSitePolicy)
245+
246+
if (options.SameSite < Options.MinimumSameSitePolicy)
245247
{
246-
case SameSiteMode.None:
247-
break;
248-
case SameSiteMode.Lax:
249-
if (options.SameSite == SameSiteMode.None)
250-
{
251-
options.SameSite = SameSiteMode.Lax;
252-
_logger.CookieSameSiteUpgraded(key, "lax");
253-
}
254-
break;
255-
case SameSiteMode.Strict:
256-
if (options.SameSite != SameSiteMode.Strict)
257-
{
258-
options.SameSite = SameSiteMode.Strict;
259-
_logger.CookieSameSiteUpgraded(key, "strict");
260-
}
261-
break;
262-
default:
263-
throw new InvalidOperationException($"Unrecognized {nameof(SameSiteMode)} value {Options.MinimumSameSitePolicy.ToString()}");
248+
options.SameSite = Options.MinimumSameSitePolicy;
249+
_logger.CookieSameSiteUpgraded(key, Options.MinimumSameSitePolicy.ToString());
264250
}
251+
265252
switch (Options.HttpOnly)
266253
{
267254
case HttpOnlyPolicy.Always:
@@ -278,4 +265,4 @@ private void ApplyPolicy(string key, CookieOptions options)
278265
}
279266
}
280267
}
281-
}
268+
}

src/Security/CookiePolicy/test/CookieConsentTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,12 @@ public async Task GrantConsentSetsCookie()
223223
Assert.Equal("yes", consentCookie.Value);
224224
Assert.True(consentCookie.Expires.HasValue);
225225
Assert.True(consentCookie.Expires.Value > DateTimeOffset.Now + TimeSpan.FromDays(364));
226-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
226+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
227227
Assert.NotNull(consentCookie.Expires);
228228
var testCookie = cookies[1];
229229
Assert.Equal("Test", testCookie.Name);
230230
Assert.Equal("Value", testCookie.Value);
231-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
231+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
232232
Assert.Null(testCookie.Expires);
233233
}
234234

@@ -400,12 +400,12 @@ public async Task WithdrawConsentDeletesCookie()
400400
var testCookie = cookies[0];
401401
Assert.Equal("Test", testCookie.Name);
402402
Assert.Equal("Value1", testCookie.Value);
403-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
403+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
404404
Assert.Null(testCookie.Expires);
405405
var consentCookie = cookies[1];
406406
Assert.Equal(".AspNet.Consent", consentCookie.Name);
407407
Assert.Equal("", consentCookie.Value);
408-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
408+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
409409
Assert.NotNull(consentCookie.Expires);
410410
}
411411

@@ -512,7 +512,7 @@ public async Task DeleteCookieDoesNotRequireConsent()
512512
var testCookie = cookies[0];
513513
Assert.Equal("Test", testCookie.Name);
514514
Assert.Equal("", testCookie.Value);
515-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
515+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
516516
Assert.NotNull(testCookie.Expires);
517517
}
518518

@@ -576,7 +576,7 @@ public async Task CreateConsentCookieMatchesGrantConsentCookie()
576576
var consentCookie = cookies[0];
577577
Assert.Equal(".AspNet.Consent", consentCookie.Name);
578578
Assert.Equal("yes", consentCookie.Value);
579-
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
579+
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
580580
Assert.NotNull(consentCookie.Expires);
581581

582582
cookies = SetCookieHeaderValue.ParseList(httpContext.Response.Headers["ManualCookie"]);

src/Security/CookiePolicy/test/CookiePolicyTests.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public class CookiePolicyTests
3939
private RequestDelegate SameSiteCookieAppends = context =>
4040
{
4141
context.Response.Cookies.Append("A", "A");
42-
context.Response.Cookies.Append("B", "B", new CookieOptions { SameSite = Http.SameSiteMode.None });
43-
context.Response.Cookies.Append("C", "C", new CookieOptions());
42+
context.Response.Cookies.Append("B", "B", new CookieOptions());
43+
context.Response.Cookies.Append("C", "C", new CookieOptions { SameSite = Http.SameSiteMode.None });
4444
context.Response.Cookies.Append("D", "D", new CookieOptions { SameSite = Http.SameSiteMode.Lax });
4545
context.Response.Cookies.Append("E", "E", new CookieOptions { SameSite = Http.SameSiteMode.Strict });
4646
return Task.FromResult(0);
@@ -198,7 +198,7 @@ await RunTest("/sameSiteLax",
198198
}
199199

200200
[Fact]
201-
public async Task SameSiteNoneLeavesItAlone()
201+
public async Task SameSiteNoneSetsItAlways()
202202
{
203203
await RunTest("/sameSiteNone",
204204
new CookiePolicyOptions
@@ -208,11 +208,32 @@ await RunTest("/sameSiteNone",
208208
SameSiteCookieAppends,
209209
new RequestTest("http://example.com/sameSiteNone",
210210
transaction =>
211+
{
212+
Assert.NotNull(transaction.SetCookie);
213+
Assert.Equal("A=A; path=/; samesite=none", transaction.SetCookie[0]);
214+
Assert.Equal("B=B; path=/; samesite=none", transaction.SetCookie[1]);
215+
Assert.Equal("C=C; path=/; samesite=none", transaction.SetCookie[2]);
216+
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
217+
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
218+
}));
219+
}
220+
221+
[Fact]
222+
public async Task SameSiteUnspecifiedLeavesItAlone()
223+
{
224+
await RunTest("/sameSiteNone",
225+
new CookiePolicyOptions
226+
{
227+
MinimumSameSitePolicy = (Http.SameSiteMode)(-1)
228+
},
229+
SameSiteCookieAppends,
230+
new RequestTest("http://example.com/sameSiteNone",
231+
transaction =>
211232
{
212233
Assert.NotNull(transaction.SetCookie);
213234
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
214235
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
215-
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
236+
Assert.Equal("C=C; path=/; samesite=none", transaction.SetCookie[2]);
216237
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
217238
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
218239
}));

0 commit comments

Comments
 (0)