summaryrefslogtreecommitdiff
path: root/lib/gitlab/middleware/same_site_cookies.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/middleware/same_site_cookies.rb')
-rw-r--r--lib/gitlab/middleware/same_site_cookies.rb89
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