diff options
Diffstat (limited to 'lib/gitlab/middleware/same_site_cookies.rb')
-rw-r--r-- | lib/gitlab/middleware/same_site_cookies.rb | 89 |
1 files changed, 87 insertions, 2 deletions
diff --git a/lib/gitlab/middleware/same_site_cookies.rb b/lib/gitlab/middleware/same_site_cookies.rb index 45968035e79..37ccc5abb10 100644 --- a/lib/gitlab/middleware/same_site_cookies.rb +++ b/lib/gitlab/middleware/same_site_cookies.rb @@ -30,6 +30,7 @@ module Gitlab set_cookie = headers['Set-Cookie']&.strip return result if set_cookie.blank? || !ssl? + return result if same_site_none_incompatible?(env['HTTP_USER_AGENT']) cookies = set_cookie.split(COOKIE_SEPARATOR) @@ -39,11 +40,11 @@ module Gitlab # Chrome will drop SameSite=None cookies without the Secure # flag. If we remove this middleware, we may need to ensure # that all cookies set this flag. - if ssl? && !(cookie =~ /;\s*secure/i) + unless SECURE_REGEX.match?(cookie) cookie << '; Secure' end - unless cookie =~ /;\s*samesite=/i + unless SAME_SITE_REGEX.match?(cookie) cookie << '; SameSite=None' end end @@ -55,9 +56,93 @@ module Gitlab private + # Taken from https://www.chromium.org/updates/same-site/incompatible-clients + # We use RE2 instead of the browser gem for performance. + IOS_REGEX = RE2('\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/') + MACOS_REGEX = RE2('\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\/') + SAFARI_REGEX = RE2('Version\/.* Safari\/') + CHROMIUM_REGEX = RE2('Chrom(e|ium)') + CHROMIUM_VERSION_REGEX = RE2('Chrom[^ \/]+\/(\d+)') + UC_BROWSER_REGEX = RE2('UCBrowser\/') + UC_BROWSER_VERSION_REGEX = RE2('UCBrowser\/(\d+)\.(\d+)\.(\d+)') + + SECURE_REGEX = RE2(';\s*secure', case_sensitive: false) + SAME_SITE_REGEX = RE2(';\s*samesite=', case_sensitive: false) + def ssl? Gitlab.config.gitlab.https end + + def same_site_none_incompatible?(user_agent) + return false if user_agent.blank? + + has_webkit_same_site_bug?(user_agent) || drops_unrecognized_same_site_cookies?(user_agent) + end + + def has_webkit_same_site_bug?(user_agent) + ios_version?(12, user_agent) || + (macos_version?(10, 14, user_agent) && safari?(user_agent)) + end + + def drops_unrecognized_same_site_cookies?(user_agent) + if uc_browser?(user_agent) + return !uc_browser_version_at_least?(12, 13, 2, user_agent) + end + + chromium_based?(user_agent) && chromium_version_between?(51, 66, user_agent) + end + + def ios_version?(major, user_agent) + m = IOS_REGEX.match(user_agent) + + return false if m.nil? + + m[1].to_i == major + end + + def macos_version?(major, minor, user_agent) + m = MACOS_REGEX.match(user_agent) + + return false if m.nil? + + m[1].to_i == major && m[2].to_i == minor + end + + def safari?(user_agent) + SAFARI_REGEX.match?(user_agent) + end + + def chromium_based?(user_agent) + CHROMIUM_REGEX.match?(user_agent) + end + + def chromium_version_between?(from_major, to_major, user_agent) + m = CHROMIUM_VERSION_REGEX.match(user_agent) + + return false if m.nil? + + version = m[1].to_i + version >= from_major && version <= to_major + end + + def uc_browser?(user_agent) + UC_BROWSER_REGEX.match?(user_agent) + end + + def uc_browser_version_at_least?(major, minor, build, user_agent) + m = UC_BROWSER_VERSION_REGEX.match(user_agent) + + return false if m.nil? + + major_version = m[1].to_i + minor_version = m[2].to_i + build_version = m[3].to_i + + return major_version > major if major_version != major + return minor_version > minor if minor_version != minor + + build_version >= build + end end end end |