[3.0 patch] Re-implement SameSite for 2019 (#13870)

This commit is contained in:
Chris Ross 2019-10-09 14:19:33 -07:00 committed by Andrew Stanton-Nurse
parent 354d859d50
commit c0a7f04370
16 changed files with 293 additions and 61 deletions

View File

@ -17,6 +17,10 @@ Directory.Build.props checks this property using the following condition:
<PackagesInPatch>
Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
@microsoft/signalr;
Microsoft.Net.Http.Headers;
Microsoft.AspNetCore.Http.Abstractions;
Microsoft.AspNetCore.Http.Features;
Microsoft.AspNetCore.CookiePolicy;
</PackagesInPatch>
</PropertyGroup>
</Project>

View File

@ -20,8 +20,14 @@ namespace Microsoft.Net.Http.Headers
private const string SecureToken = "secure";
// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
private const string SameSiteToken = "samesite";
private static readonly string SameSiteNoneToken = SameSiteMode.None.ToString().ToLower();
private static readonly string SameSiteLaxToken = SameSiteMode.Lax.ToString().ToLower();
private static readonly string SameSiteStrictToken = SameSiteMode.Strict.ToString().ToLower();
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
internal static bool SuppressSameSiteNone;
private const string HttpOnlyToken = "httponly";
private const string SeparatorToken = "; ";
private const string EqualsToken = "=";
@ -36,6 +42,14 @@ namespace Microsoft.Net.Http.Headers
private StringSegment _name;
private StringSegment _value;
static SetCookieHeaderValue()
{
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.SuppressSameSiteNone", out var enabled))
{
SuppressSameSiteNone = enabled;
}
}
private SetCookieHeaderValue()
{
// Used by the parser to create a new instance of this type.
@ -92,16 +106,17 @@ namespace Microsoft.Net.Http.Headers
public bool Secure { get; set; }
public SameSiteMode SameSite { get; set; }
public SameSiteMode SameSite { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1); // Unspecified
public bool HttpOnly { get; set; }
// name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
// name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={strict|lax|none}; httponly
public override string ToString()
{
var length = _name.Length + EqualsToken.Length + _value.Length;
string maxAge = null;
string sameSite = null;
if (Expires.HasValue)
{
@ -129,9 +144,20 @@ namespace Microsoft.Net.Http.Headers
length += SeparatorToken.Length + SecureToken.Length;
}
if (SameSite != SameSiteMode.None)
// Allow for Unspecified (-1) to skip SameSite
if (SameSite == SameSiteMode.None && !SuppressSameSiteNone)
{
var sameSite = SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken;
sameSite = SameSiteNoneToken;
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
}
else if (SameSite == SameSiteMode.Lax)
{
sameSite = SameSiteLaxToken;
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
}
else if (SameSite == SameSiteMode.Strict)
{
sameSite = SameSiteStrictToken;
length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
}
@ -140,9 +166,9 @@ namespace Microsoft.Net.Http.Headers
length += SeparatorToken.Length + HttpOnlyToken.Length;
}
return string.Create(length, (this, maxAge), (span, tuple) =>
return string.Create(length, (this, maxAge, sameSite), (span, tuple) =>
{
var (headerValue, maxAgeValue) = tuple;
var (headerValue, maxAgeValue, sameSite) = tuple;
Append(ref span, headerValue._name);
Append(ref span, EqualsToken);
@ -180,9 +206,9 @@ namespace Microsoft.Net.Http.Headers
AppendSegment(ref span, SecureToken, null);
}
if (headerValue.SameSite != SameSiteMode.None)
if (sameSite != null)
{
AppendSegment(ref span, SameSiteToken, headerValue.SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
AppendSegment(ref span, SameSiteToken, sameSite);
}
if (headerValue.HttpOnly)
@ -248,9 +274,18 @@ namespace Microsoft.Net.Http.Headers
AppendSegment(builder, SecureToken, null);
}
if (SameSite != SameSiteMode.None)
// Allow for Unspecified (-1) to skip SameSite
if (SameSite == SameSiteMode.None && !SuppressSameSiteNone)
{
AppendSegment(builder, SameSiteToken, SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
AppendSegment(builder, SameSiteToken, SameSiteNoneToken);
}
else if (SameSite == SameSiteMode.Lax)
{
AppendSegment(builder, SameSiteToken, SameSiteLaxToken);
}
else if (SameSite == SameSiteMode.Strict)
{
AppendSegment(builder, SameSiteToken, SameSiteStrictToken);
}
if (HttpOnly)
@ -302,7 +337,7 @@ namespace Microsoft.Net.Http.Headers
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
}
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax|None}; httponly
private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue parsedValue)
{
Contract.Requires(startIndex >= 0);
@ -437,25 +472,34 @@ namespace Microsoft.Net.Http.Headers
{
result.Secure = true;
}
// samesite-av = "SameSite" / "SameSite=" samesite-value
// samesite-value = "Strict" / "Lax"
// samesite-av = "SameSite=" samesite-value
// samesite-value = "Strict" / "Lax" / "None"
else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
{
if (!ReadEqualsSign(input, ref offset))
{
result.SameSite = SameSiteMode.Strict;
result.SameSite = SuppressSameSiteNone ? SameSiteMode.Strict : (SameSiteMode)(-1); // Unspecified
}
else
{
var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
if (StringSegment.Equals(enforcementMode, SameSiteStrictToken, StringComparison.OrdinalIgnoreCase))
{
result.SameSite = SameSiteMode.Strict;
}
else if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
{
result.SameSite = SameSiteMode.Lax;
}
else if (!SuppressSameSiteNone
&& StringSegment.Equals(enforcementMode, SameSiteNoneToken, StringComparison.OrdinalIgnoreCase))
{
result.SameSite = SameSiteMode.None;
}
else
{
result.SameSite = SameSiteMode.Strict;
result.SameSite = SuppressSameSiteNone ? SameSiteMode.Strict : (SameSiteMode)(-1); // Unspecified
}
}
}

View File

@ -57,7 +57,7 @@ namespace Microsoft.Net.Http.Headers
{
SameSite = SameSiteMode.None,
};
dataset.Add(header7, "name7=value7");
dataset.Add(header7, "name7=value7; samesite=none");
return dataset;
@ -155,9 +155,20 @@ namespace Microsoft.Net.Http.Headers
{
SameSite = SameSiteMode.Strict
};
var string6a = "name6=value6; samesite";
var string6b = "name6=value6; samesite=Strict";
var string6c = "name6=value6; samesite=invalid";
var string6 = "name6=value6; samesite=Strict";
var header7 = new SetCookieHeaderValue("name7", "value7")
{
SameSite = SameSiteMode.None
};
var string7 = "name7=value7; samesite=None";
var header8 = new SetCookieHeaderValue("name8", "value8")
{
SameSite = (SameSiteMode)(-1) // Unspecified
};
var string8a = "name8=value8; samesite";
var string8b = "name8=value8; samesite=invalid";
dataset.Add(new[] { header1 }.ToList(), new[] { string1 });
dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
@ -170,9 +181,10 @@ namespace Microsoft.Net.Http.Headers
dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4) });
dataset.Add(new[] { header5 }.ToList(), new[] { string5a });
dataset.Add(new[] { header5 }.ToList(), new[] { string5b });
dataset.Add(new[] { header6 }.ToList(), new[] { string6a });
dataset.Add(new[] { header6 }.ToList(), new[] { string6b });
dataset.Add(new[] { header6 }.ToList(), new[] { string6c });
dataset.Add(new[] { header6 }.ToList(), new[] { string6 });
dataset.Add(new[] { header7 }.ToList(), new[] { string7 });
dataset.Add(new[] { header8 }.ToList(), new[] { string8a });
dataset.Add(new[] { header8 }.ToList(), new[] { string8b });
return dataset;
}
@ -301,6 +313,28 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedValue, input.ToString());
}
[Fact]
public void SetCookieHeaderValue_ToString_SameSiteNoneCompat()
{
SetCookieHeaderValue.SuppressSameSiteNone = true;
var input = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
Assert.Equal("name=value", input.ToString());
SetCookieHeaderValue.SuppressSameSiteNone = false;
var input2 = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
Assert.Equal("name=value; samesite=none", input2.ToString());
}
[Theory]
[MemberData(nameof(SetCookieHeaderDataSet))]
public void SetCookieHeaderValue_AppendToStringBuilder(SetCookieHeaderValue input, string expectedValue)
@ -312,6 +346,32 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedValue, builder.ToString());
}
[Fact]
public void SetCookieHeaderValue_AppendToStringBuilder_SameSiteNoneCompat()
{
SetCookieHeaderValue.SuppressSameSiteNone = true;
var builder = new StringBuilder();
var input = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
input.AppendToStringBuilder(builder);
Assert.Equal("name=value", builder.ToString());
SetCookieHeaderValue.SuppressSameSiteNone = false;
var builder2 = new StringBuilder();
var input2 = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
input2.AppendToStringBuilder(builder2);
Assert.Equal("name=value; samesite=none", builder2.ToString());
}
[Theory]
[MemberData(nameof(SetCookieHeaderDataSet))]
public void SetCookieHeaderValue_Parse_AcceptsValidValues(SetCookieHeaderValue cookie, string expectedValue)
@ -322,6 +382,31 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedValue, header.ToString());
}
[Fact]
public void SetCookieHeaderValue_Parse_AcceptsValidValues_SameSiteNoneCompat()
{
SetCookieHeaderValue.SuppressSameSiteNone = true;
var header = SetCookieHeaderValue.Parse("name=value; samesite=none");
var cookie = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.Strict,
};
Assert.Equal(cookie, header);
Assert.Equal("name=value; samesite=strict", header.ToString());
SetCookieHeaderValue.SuppressSameSiteNone = false;
var header2 = SetCookieHeaderValue.Parse("name=value; samesite=none");
var cookie2 = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
Assert.Equal(cookie2, header2);
Assert.Equal("name=value; samesite=none", header2.ToString());
}
[Theory]
[MemberData(nameof(SetCookieHeaderDataSet))]
public void SetCookieHeaderValue_TryParse_AcceptsValidValues(SetCookieHeaderValue cookie, string expectedValue)
@ -332,6 +417,31 @@ namespace Microsoft.Net.Http.Headers
Assert.Equal(expectedValue, header.ToString());
}
[Fact]
public void SetCookieHeaderValue_TryParse_AcceptsValidValues_SameSiteNoneCompat()
{
SetCookieHeaderValue.SuppressSameSiteNone = true;
Assert.True(SetCookieHeaderValue.TryParse("name=value; samesite=none", out var header));
var cookie = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.Strict,
};
Assert.Equal(cookie, header);
Assert.Equal("name=value; samesite=strict", header.ToString());
SetCookieHeaderValue.SuppressSameSiteNone = false;
Assert.True(SetCookieHeaderValue.TryParse("name=value; samesite=none", out var header2));
var cookie2 = new SetCookieHeaderValue("name", "value")
{
SameSite = SameSiteMode.None,
};
Assert.Equal(cookie2, header2);
Assert.Equal("name=value; samesite=none", header2.ToString());
}
[Theory]
[MemberData(nameof(InvalidSetCookieHeaderDataSet))]
public void SetCookieHeaderValue_Parse_RejectsInvalidValues(string value)

View File

@ -11,8 +11,20 @@ namespace Microsoft.AspNetCore.Http
/// </summary>
public class CookieBuilder
{
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
internal static bool SuppressSameSiteNone;
private string _name;
static CookieBuilder()
{
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.SuppressSameSiteNone", out var enabled))
{
SuppressSameSiteNone = enabled;
}
}
/// <summary>
/// The name of the cookie.
/// </summary>
@ -49,12 +61,12 @@ namespace Microsoft.AspNetCore.Http
public virtual bool HttpOnly { get; set; }
/// <summary>
/// The SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.None"/>
/// The SameSite attribute of the cookie. The default value is -1 (Unspecified)
/// </summary>
/// <remarks>
/// Determines the value that will set on <seealso cref="CookieOptions.SameSite"/>.
/// </remarks>
public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.None;
public virtual SameSiteMode SameSite { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
/// <summary>
/// The policy that will be used to determine <seealso cref="CookieOptions.Secure"/>.

View File

@ -10,6 +10,18 @@ namespace Microsoft.AspNetCore.Http
/// </summary>
public class CookieOptions
{
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
internal static bool SuppressSameSiteNone;
static CookieOptions()
{
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.SuppressSameSiteNone", out var enabled))
{
SuppressSameSiteNone = enabled;
}
}
/// <summary>
/// Creates a default cookie with a path of '/'.
/// </summary>
@ -43,10 +55,10 @@ namespace Microsoft.AspNetCore.Http
public bool Secure { get; set; }
/// <summary>
/// Gets or sets the value for the SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.None"/>
/// Gets or sets the value for the SameSite attribute of the cookie. The default value is -1 (Unspecified)
/// </summary>
/// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
public SameSiteMode SameSite { get; set; } = SameSiteMode.None;
public SameSiteMode SameSite { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
/// <summary>
/// Gets or sets a value that indicates whether a cookie is accessible by client-side script.

View File

@ -14,7 +14,9 @@
<Reference Include="Microsoft.AspNetCore" />
<Reference Include="Microsoft.AspNetCore.Authentication.Cookies" />
<Reference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<Reference Include="Microsoft.AspNetCore.CookiePolicy" />
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
<Reference Include="Microsoft.Net.Http.Headers" />
</ItemGroup>
<ItemGroup>

View File

@ -32,10 +32,33 @@ namespace OpenIdConnectSample
public IConfiguration Configuration { get; set; }
public IWebHostEnvironment Environment { get; }
private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite > (SameSiteMode)(-1))
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
// TODO: Use your User Agent library of choice here.
if (userAgent.Contains("CPU iPhone OS 12") // Also covers iPod touch
|| userAgent.Contains("iPad; CPU OS 12")
// Safari 12 and 13 are both broken on Mojave
|| userAgent.Contains("Macintosh; Intel Mac OS X 10_14"))
{
options.SameSite = (SameSiteMode)(-1);
}
}
}
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = (SameSiteMode)(-1);
options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
@ -84,6 +107,7 @@ namespace OpenIdConnectSample
public void Configure(IApplicationBuilder app, IOptionsMonitor<OpenIdConnectOptions> optionsMonitor)
{
app.UseDeveloperExceptionPage();
app.UseCookiePolicy(); // Before UseAuthentication or anything else that writes cookies.
app.UseAuthentication();
app.Run(async context =>

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>

View File

@ -229,7 +229,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
Assert.Contains(" path=/foo", setCookie1);
Assert.Contains(" domain=another.com", setCookie1);
Assert.Contains(" secure", setCookie1);
Assert.DoesNotContain(" samesite", setCookie1);
Assert.Contains(" samesite=none", setCookie1);
Assert.Contains(" httponly", setCookie1);
var server2 = CreateServer(o =>

View File

@ -49,6 +49,7 @@
<Reference Include="Microsoft.AspNetCore.Authentication.WsFederation" />
<Reference Include="Microsoft.AspNetCore.HttpOverrides" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.Net.Http.Headers" />
</ItemGroup>
</Project>

View File

@ -437,6 +437,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
var server = settings.CreateTestServer();
var transaction = await server.SendAsync(ChallengeEndpoint);
Assert.Contains("samesite=none", transaction.SetCookie.First());
var challengeCookies = SetCookieHeaderValue.ParseList(transaction.SetCookie);
var nonceCookie = challengeCookies.Where(cookie => cookie.Name.StartsWith(OpenIdConnectDefaults.CookieNoncePrefix, StringComparison.Ordinal)).Single();
Assert.True(nonceCookie.Expires.HasValue);

View File

@ -12,10 +12,22 @@ namespace Microsoft.AspNetCore.Builder
/// </summary>
public class CookiePolicyOptions
{
// True (old): https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-3.1
// False (new): https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
internal static bool SuppressSameSiteNone;
static CookiePolicyOptions()
{
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.SuppressSameSiteNone", out var enabled))
{
SuppressSameSiteNone = enabled;
}
}
/// <summary>
/// Affects the cookie's same site attribute.
/// </summary>
public SameSiteMode MinimumSameSitePolicy { get; set; } = SameSiteMode.None;
public SameSiteMode MinimumSameSitePolicy { get; set; } = SuppressSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1);
/// <summary>
/// Affects whether cookies must be HttpOnly.

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -115,7 +115,8 @@ namespace Microsoft.AspNetCore.CookiePolicy
private bool CheckPolicyRequired()
{
return !CanTrack
|| Options.MinimumSameSitePolicy != SameSiteMode.None
|| (CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != SameSiteMode.None)
|| (!CookiePolicyOptions.SuppressSameSiteNone && Options.MinimumSameSitePolicy != (SameSiteMode)(-1)) // Unspecified
|| Options.HttpOnly != HttpOnlyPolicy.None
|| Options.Secure != CookieSecurePolicy.None;
}
@ -241,27 +242,13 @@ namespace Microsoft.AspNetCore.CookiePolicy
default:
throw new InvalidOperationException();
}
switch (Options.MinimumSameSitePolicy)
if (options.SameSite < Options.MinimumSameSitePolicy)
{
case SameSiteMode.None:
break;
case SameSiteMode.Lax:
if (options.SameSite == SameSiteMode.None)
{
options.SameSite = SameSiteMode.Lax;
_logger.CookieSameSiteUpgraded(key, "lax");
}
break;
case SameSiteMode.Strict:
if (options.SameSite != SameSiteMode.Strict)
{
options.SameSite = SameSiteMode.Strict;
_logger.CookieSameSiteUpgraded(key, "strict");
}
break;
default:
throw new InvalidOperationException($"Unrecognized {nameof(SameSiteMode)} value {Options.MinimumSameSitePolicy.ToString()}");
options.SameSite = Options.MinimumSameSitePolicy;
_logger.CookieSameSiteUpgraded(key, Options.MinimumSameSitePolicy.ToString());
}
switch (Options.HttpOnly)
{
case HttpOnlyPolicy.Always:
@ -278,4 +265,4 @@ namespace Microsoft.AspNetCore.CookiePolicy
}
}
}
}
}

View File

@ -223,12 +223,12 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
Assert.Equal("yes", consentCookie.Value);
Assert.True(consentCookie.Expires.HasValue);
Assert.True(consentCookie.Expires.Value > DateTimeOffset.Now + TimeSpan.FromDays(364));
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
Assert.NotNull(consentCookie.Expires);
var testCookie = cookies[1];
Assert.Equal("Test", testCookie.Name);
Assert.Equal("Value", testCookie.Value);
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
Assert.Null(testCookie.Expires);
}
@ -400,12 +400,12 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
var testCookie = cookies[0];
Assert.Equal("Test", testCookie.Name);
Assert.Equal("Value1", testCookie.Value);
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
Assert.Null(testCookie.Expires);
var consentCookie = cookies[1];
Assert.Equal(".AspNet.Consent", consentCookie.Name);
Assert.Equal("", consentCookie.Value);
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
Assert.NotNull(consentCookie.Expires);
}
@ -512,7 +512,7 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
var testCookie = cookies[0];
Assert.Equal("Test", testCookie.Name);
Assert.Equal("", testCookie.Value);
Assert.Equal(Net.Http.Headers.SameSiteMode.None, testCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), testCookie.SameSite);
Assert.NotNull(testCookie.Expires);
}
@ -576,7 +576,7 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
var consentCookie = cookies[0];
Assert.Equal(".AspNet.Consent", consentCookie.Name);
Assert.Equal("yes", consentCookie.Value);
Assert.Equal(Net.Http.Headers.SameSiteMode.None, consentCookie.SameSite);
Assert.Equal((Net.Http.Headers.SameSiteMode)(-1), consentCookie.SameSite);
Assert.NotNull(consentCookie.Expires);
cookies = SetCookieHeaderValue.ParseList(httpContext.Response.Headers["ManualCookie"]);

View File

@ -39,8 +39,8 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
private RequestDelegate SameSiteCookieAppends = context =>
{
context.Response.Cookies.Append("A", "A");
context.Response.Cookies.Append("B", "B", new CookieOptions { SameSite = Http.SameSiteMode.None });
context.Response.Cookies.Append("C", "C", new CookieOptions());
context.Response.Cookies.Append("B", "B", new CookieOptions());
context.Response.Cookies.Append("C", "C", new CookieOptions { SameSite = Http.SameSiteMode.None });
context.Response.Cookies.Append("D", "D", new CookieOptions { SameSite = Http.SameSiteMode.Lax });
context.Response.Cookies.Append("E", "E", new CookieOptions { SameSite = Http.SameSiteMode.Strict });
return Task.FromResult(0);
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
}
[Fact]
public async Task SameSiteNoneLeavesItAlone()
public async Task SameSiteNoneSetsItAlways()
{
await RunTest("/sameSiteNone",
new CookiePolicyOptions
@ -208,11 +208,32 @@ namespace Microsoft.AspNetCore.CookiePolicy.Test
SameSiteCookieAppends,
new RequestTest("http://example.com/sameSiteNone",
transaction =>
{
Assert.NotNull(transaction.SetCookie);
Assert.Equal("A=A; path=/; samesite=none", transaction.SetCookie[0]);
Assert.Equal("B=B; path=/; samesite=none", transaction.SetCookie[1]);
Assert.Equal("C=C; path=/; samesite=none", transaction.SetCookie[2]);
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
}));
}
[Fact]
public async Task SameSiteUnspecifiedLeavesItAlone()
{
await RunTest("/sameSiteNone",
new CookiePolicyOptions
{
MinimumSameSitePolicy = (Http.SameSiteMode)(-1)
},
SameSiteCookieAppends,
new RequestTest("http://example.com/sameSiteNone",
transaction =>
{
Assert.NotNull(transaction.SetCookie);
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
Assert.Equal("C=C; path=/; samesite=none", transaction.SetCookie[2]);
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
}));

View File

@ -13,6 +13,7 @@
<Reference Include="Microsoft.AspNetCore.CookiePolicy" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Net.Http.Headers" />
</ItemGroup>
</Project>