Skip to content

Commit

Permalink
Updated SameSite compatibility check.
Browse files Browse the repository at this point in the history
  • Loading branch information
pmaytak committed Jun 30, 2020
1 parent 9924ba1 commit ae16104
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 34 deletions.
142 changes: 109 additions & 33 deletions src/Microsoft.Identity.Web/CookiePolicyOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License.

using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

Expand All @@ -14,7 +16,7 @@ public static class CookiePolicyOptionsExtensions
{
/// <summary>
/// Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1.
/// The default list of user-agents that disallow SameSite None, was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/.
/// The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/.
/// </summary>
/// <param name="options"><see cref="CookiePolicyOptions"/>to update.</param>
/// <returns><see cref="CookiePolicyOptions"/> to chain.</returns>
Expand All @@ -25,10 +27,10 @@ public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookieP

/// <summary>
/// Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
/// The default list of user-agents that disallow SameSite None, was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/.
/// The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/.
/// </summary>
/// <param name="options"><see cref="CookiePolicyOptions"/>to update.</param>
/// <param name="disallowsSameSiteNone">If you don't want to use the default user-agent list implementation, the method sent in this parameter will be run against the user-agent and if returned true, SameSite value will be set to Unspecified. The default user-agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. </param>
/// <param name="disallowsSameSiteNone">If you don't want to use the default user agent list implementation, the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. </param>
/// <returns><see cref="CookiePolicyOptions"/> to chain.</returns>
public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookiePolicyOptions options, Func<string, bool> disallowsSameSiteNone)
{
Expand Down Expand Up @@ -59,50 +61,124 @@ private static void CheckSameSite(HttpContext httpContext, CookieOptions options
}

/// <summary>
/// Checks if the specified user agent supports SameSite None cookies.
/// Checks if the specified user agent supports "SameSite=None" cookies.
/// </summary>
/// <param name="userAgent">Browser user agent.</param>
/// <remarks>Method taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/.</remarks>
/// <returns>True, if the user agent does not allow SameSite None cookie; otherwise, false.</returns>
/// <remarks>
/// Incompatible user agents include:
/// <list type="bullet">
/// <item>Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends).</item>
/// <item>Versions of UC Browser on Android prior to version 12.13.2.</item>
/// <item>Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12.</item>
/// </list>
/// Reference: https://www.chromium.org/updates/same-site/incompatible-clients.
/// </remarks>
/// <returns>True, if the user agent does not allow "SameSite=None" cookie; otherwise, false.</returns>
public static bool DisallowsSameSiteNone(string userAgent)
{
if (!string.IsNullOrEmpty(userAgent))
return HasWebKitSameSiteBug() ||
DropsUnrecognizedSameSiteCookies();

bool HasWebKitSameSiteBug() =>
IsIosVersion(12) ||
(IsMacosxVersion(10, 14) &&
(IsSafari() || IsMacEmbeddedBrowser()));

bool DropsUnrecognizedSameSiteCookies()
{
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the iOS networking
// stack.
if (userAgent.Contains("CPU iPhone OS 12") ||
userAgent.Contains("iPad; CPU OS 12"))
if (IsUcBrowser())
{
return true;
return !IsUcBrowserVersionAtLeast(12, 13, 2);
}

// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// Because they do not use the Mac OS networking stack.
if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
userAgent.Contains("Version/") && userAgent.Contains("Safari"))
return IsChromiumBased() &&
IsChromiumVersionAtLeast(51) &&
!IsChromiumVersionAtLeast(67);
}

bool IsIosVersion(int major)
{
string regex = @"\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/";

// Extract digits from first capturing group.
var m = Regex.Match(userAgent, regex);
return m.Groups[0].Value == major.ToString(CultureInfo.CurrentCulture);
}

bool IsMacosxVersion(int major, int minor)
{
string regex = @"\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit/";

// Extract digits from first and second capturing groups.
var m = Regex.Match(userAgent, regex);
return m.Groups[0].Value == major.ToString(CultureInfo.CurrentCulture) &&
m.Groups[1].Value == minor.ToString(CultureInfo.CurrentCulture);
}

bool IsSafari()
{
string regex = @"Version/.* Safari/";

var m = Regex.Match(userAgent, regex);
return Regex.IsMatch(userAgent, regex) &&
!IsChromiumBased();
}

bool IsMacEmbeddedBrowser()
{
string regex = @"^Mozilla/[\.\d]+ \(Macintosh;.*Mac OS X [_\d]+\) AppleWebKit/[\.\d]+ \(KHTML, like Gecko\)$";

var m = Regex.Match(userAgent, regex);
return Regex.IsMatch(userAgent, regex);
}

bool IsChromiumBased()
{
string regex = "Chrom(e|ium)";

var m = Regex.Match(userAgent, regex);
return Regex.IsMatch(userAgent, regex);
}

bool IsChromiumVersionAtLeast(int major)
{
string regex = @"Chrom[^ /]+/(\d+)[\.\d]* ";

// Extract digits from first capturing group.
var m = Regex.Match(userAgent, regex);
int version = Convert.ToInt32(m.Groups[0].Value, CultureInfo.CurrentCulture);
return version >= major;
}

bool IsUcBrowser()
{
string regex = "UCBrowser/";

var m = Regex.Match(userAgent, regex);
return Regex.IsMatch(userAgent, regex);
}

bool IsUcBrowserVersionAtLeast(int major, int minor, int build)
{
string regex = @"UCBrowser/(\d+)\.(\d+)\.(\d+)[\.\d]* ";

// Extract digits from three capturing groups.
var m = Regex.Match(userAgent, regex);
int major_version = Convert.ToInt32(m.Groups[0].Value, CultureInfo.CurrentCulture);
int minor_version = Convert.ToInt32(m.Groups[1].Value, CultureInfo.CurrentCulture);
int build_version = Convert.ToInt32(m.Groups[2].Value, CultureInfo.CurrentCulture);
if (major_version != major)
{
return true;
return major_version > major;
}

// Cover Chrome 50-69, because some versions are broken by SameSite=None,
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
if (minor_version != minor)
{
return true;
return minor_version > minor;
}
}

return false;
return build_version >= build;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public void HandleSameSiteCookieCompatibility_CustomFilter_ExecutesSuccessfully(
[InlineData(true, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15")]
[InlineData(true, "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36")]
[InlineData(true, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")]
[InlineData(true, "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-CN; F5121 Build/34.0.A.1.247) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.5.1.944 Mobile Safari/537.36")]
[InlineData(false, "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1")]
[InlineData(false, "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1")]
[InlineData(false, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36")]
Expand All @@ -105,11 +106,13 @@ public void HandleSameSiteCookieCompatibility_CustomFilter_ExecutesSuccessfully(
[InlineData(false, "Mozilla/5.0 (iPad; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")]
[InlineData(false, "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")]
[InlineData(false, "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36")]
[InlineData(false, "Mozilla/5.0 (Linux; U; Android 5.0; en-US; V80 Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.9.7.1153 Mobile Safari/537.36")]
[InlineData(false, "Invalid user agent")]
public void DisallowsSameSiteNone_VariousUserAgents_ExecutesSuccessfully(bool expectedResult, string userAgent)
{
var actualResult = CookiePolicyOptionsExtensions.DisallowsSameSiteNone(userAgent);

Assert.Equal(expectedResult, actualResult);
}
}
}
}

0 comments on commit ae16104

Please sign in to comment.