From 3edf93fa066b01a0878e5f59f9027e597e9f1846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Wed, 6 Oct 2021 21:26:06 -0300 Subject: [PATCH 01/15] Add OneTrust installation script This script is added to marketing related pages and only for open routes, so there is no way user data gets analyzed. --- app/views/devise/registrations/new.html.haml | 1 + app/views/devise/sessions/new.html.haml | 1 + app/views/layouts/_one_trust.html.haml | 6 ++++++ ee/app/views/trial_registrations/new.html.haml | 1 + ee/app/views/trials/new.html.haml | 2 ++ ee/app/views/trials/select.html.haml | 1 + 6 files changed, 12 insertions(+) create mode 100644 app/views/layouts/_one_trust.html.haml diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 4ec3fcde337be6..87108c8ea78cfb 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -2,6 +2,7 @@ - add_page_specific_style 'page_bundles/signup' - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" + = render "layouts/one_trust" = render "layouts/google_tag_manager_body" .signup-page diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 74f3e3e7e34f70..da6232b2a2bd0a 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,6 +1,7 @@ - page_title _("Sign in") - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" + = render "layouts/one_trust" = render "layouts/google_tag_manager_body" #signin-container diff --git a/app/views/layouts/_one_trust.html.haml b/app/views/layouts/_one_trust.html.haml new file mode 100644 index 00000000000000..1dff568ce29357 --- /dev/null +++ b/app/views/layouts/_one_trust.html.haml @@ -0,0 +1,6 @@ + += javascript_include_tag "https://cdn.cookielaw.org/consent/-/OtAutoBlock.js" +%script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => "-", :defer => true, :nonce => true } += javascript_tag do + :plain + function OptanonWrapper() { } diff --git a/ee/app/views/trial_registrations/new.html.haml b/ee/app/views/trial_registrations/new.html.haml index f3e585c6e8989f..2b5df5791fe234 100644 --- a/ee/app/views/trial_registrations/new.html.haml +++ b/ee/app/views/trial_registrations/new.html.haml @@ -2,6 +2,7 @@ - add_page_specific_style 'page_bundles/signup' - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" + = render "layouts/one_trust" = render "layouts/google_tag_manager_body" - registration_form_content = capture do diff --git a/ee/app/views/trials/new.html.haml b/ee/app/views/trials/new.html.haml index 4d0b6ebe436c9e..77ae075658f58a 100644 --- a/ee/app/views/trials/new.html.haml +++ b/ee/app/views/trials/new.html.haml @@ -1,5 +1,7 @@ - page_title _('Start your Free Ultimate Trial') - glm_params = { glm_source: params[:glm_source], glm_content: params[:glm_content] } +- content_for :page_specific_javascripts do + = render "layouts/one_trust" .row .col-md-6.offset-md-3 diff --git a/ee/app/views/trials/select.html.haml b/ee/app/views/trials/select.html.haml index 4a5d6fab531e83..dcfa7b8e23d8cf 100644 --- a/ee/app/views/trials/select.html.haml +++ b/ee/app/views/trials/select.html.haml @@ -1,6 +1,7 @@ - page_title _('Start a Free Ultimate Trial') - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" + = render "layouts/one_trust" = render "layouts/google_tag_manager_body" .row -- GitLab From 91af3ae63666450e39a30e7f5c9c2a9546b4da21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Fri, 1 Oct 2021 17:34:31 -0300 Subject: [PATCH 02/15] Add OneTrust URL to content_security_policy --- lib/gitlab/content_security_policy/config_loader.rb | 4 ++-- spec/lib/gitlab/content_security_policy/config_loader_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 0e3fa8b8d8700e..ed4f42c92e5f96 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -15,7 +15,7 @@ def self.default_directives directives = { 'default_src' => "'self'", 'base_uri' => "'self'", - 'connect_src' => "'self'", + 'connect_src' => "'self' https://cdn.cookielaw.org", 'font_src' => "'self'", 'form_action' => "'self' https: http:", 'frame_ancestors' => "'self'", @@ -23,7 +23,7 @@ def self.default_directives 'img_src' => "'self' data: blob: http: https:", 'manifest_src' => "'self'", 'media_src' => "'self'", - 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com", + 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://cdn.cookielaw.org", 'style_src' => "'self' 'unsafe-inline'", 'worker_src' => "'self' blob: data:", 'object_src' => "'none'", diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 3ec332dace594b..af605cce3b7cf9 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -81,7 +81,7 @@ end it 'adds CDN host to CSP' do - expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://example.com") + expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://cdn.cookielaw.org https://example.com") expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com") expect(directives['font_src']).to eq("'self' https://example.com") end @@ -94,7 +94,7 @@ end it 'adds sentry path to CSP without user' do - expect(directives['connect_src']).to eq("'self' ws://example.com dummy://example.com/43") + expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com dummy://example.com/43") end end -- GitLab From 5c720cf21535b0d3e58d83c918d8f6e31cc68078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Mon, 4 Oct 2021 17:53:54 -0300 Subject: [PATCH 03/15] Update OneTrust installation script - Put it behind a feature flag - Add a config value for the OneTrust id - Add tests --- app/helpers/auth_helper.rb | 7 ++++ app/views/layouts/_one_trust.html.haml | 13 ++++--- ...strumentation_google_analytics_cookies.yml | 8 ++++ config/gitlab.yml.example | 3 ++ spec/helpers/auth_helper_spec.rb | 39 +++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index a0c3a6f2f522d8..183d64ee313881 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -169,6 +169,13 @@ def google_tag_manager_enabled? !current_user end + def one_trust_enabled? + Feature.enabled?(:ecomm_instrumentation_google_analytics_cookies, nil, type: :ops) && + extra_config.has_key?('one_trust_id') && + extra_config.one_trust_id.present? && + !current_user + end + def auth_app_owner_text(owner) return unless owner diff --git a/app/views/layouts/_one_trust.html.haml b/app/views/layouts/_one_trust.html.haml index 1dff568ce29357..8460d071a88637 100644 --- a/app/views/layouts/_one_trust.html.haml +++ b/app/views/layouts/_one_trust.html.haml @@ -1,6 +1,7 @@ - -= javascript_include_tag "https://cdn.cookielaw.org/consent/-/OtAutoBlock.js" -%script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => "-", :defer => true, :nonce => true } -= javascript_tag do - :plain - function OptanonWrapper() { } +- if one_trust_enabled? + + = javascript_include_tag "https://cdn.cookielaw.org/consent/#{extra_config.one_trust_id}/OtAutoBlock.js" + %script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => extra_config.one_trust_id, :defer => true, :nonce => true } + = javascript_tag nonce: true do + :plain + function OptanonWrapper() { } diff --git a/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml b/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml new file mode 100644 index 00000000000000..b82d7d82399dbd --- /dev/null +++ b/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml @@ -0,0 +1,8 @@ +--- +name: ecomm_instrumentation_google_analytics_cookies +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71242 +rollout_issue_url: +milestone: '14.4' +type: ops +group: group::product intelligence +default_enabled: false diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 89f2d82c6291cd..3d2acce9a691b6 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -1295,6 +1295,9 @@ production: &base ## Google tag manager # google_tag_manager_id: '_your_tracking_id' + ## OneTrust + # one_trust_id: '_your_one_trust_id' + ## Matomo analytics. # matomo_url: '_your_matomo_url' # matomo_site_id: '_your_matomo_site_id' diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index c1c961c5cbb5fe..cc16fdb65a4716 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -314,6 +314,45 @@ def auth_active? end end + describe '#one_trust_enabled?' do + let(:user) { nil } + + before do + stub_config(extra: { one_trust_id: 'key' }) + allow(helper).to receive(:current_user).and_return(user) + end + + subject(:one_trust_enabled?) { helper.one_trust_enabled? } + + context 'with ecomm_instrumentation_google_analytics_cookies feature flag enabled' do + before do + stub_feature_flags(ecomm_instrumentation_google_analytics_cookies: true) + end + + context 'when current user is set' do + let(:user) { instance_double('User') } + + it { is_expected.to be_falsey } + end + + context 'when no id is set' do + before do + stub_config(extra: {}) + end + + it { is_expected.to be_falsey } + end + + context 'when id is set and no user is set' do + before do + stub_config(extra: { one_trust_id: 'key' }) + end + + it { is_expected.to be_truthy } + end + end + end + describe '#auth_app_owner_text' do shared_examples 'generates text with the correct info' do it 'includes the name of the application owner' do -- GitLab From d8dc442f109e5d8613695d836306359be0e23b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Thu, 7 Oct 2021 00:18:00 -0300 Subject: [PATCH 04/15] Fix pipeline failures --- .../gitlab/content_security_policy/config_loader_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index af605cce3b7cf9..d0afd502050b87 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -56,22 +56,22 @@ context 'adds all websocket origins to support Safari' do it 'with insecure domain' do stub_config_setting(host: 'example.com', https: false) - expect(directives['connect_src']).to eq("'self' ws://example.com") + expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com") end it 'with secure domain' do stub_config_setting(host: 'example.com', https: true) - expect(directives['connect_src']).to eq("'self' wss://example.com") + expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org wss://example.com") end it 'with custom port' do stub_config_setting(host: 'example.com', port: '1234') - expect(directives['connect_src']).to eq("'self' ws://example.com:1234") + expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com:1234") end it 'with custom port and secure domain' do stub_config_setting(host: 'example.com', https: true, port: '1234') - expect(directives['connect_src']).to eq("'self' wss://example.com:1234") + expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org wss://example.com:1234") end end -- GitLab From 54551b924deb74dae156ba1c5f23b385d1abc03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Thu, 7 Oct 2021 15:57:39 -0300 Subject: [PATCH 05/15] Set correct related MR for GA feature flag --- .../ops/ecomm_instrumentation_google_analytics_cookies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml b/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml index b82d7d82399dbd..5915977114ba34 100644 --- a/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml +++ b/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml @@ -1,6 +1,6 @@ --- name: ecomm_instrumentation_google_analytics_cookies -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71242 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71243 rollout_issue_url: milestone: '14.4' type: ops -- GitLab From aa00d2213f2f56c3574fa284b35c33ca8b510e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Thu, 7 Oct 2021 16:09:24 -0300 Subject: [PATCH 06/15] Remove OneTrust from guarded trial pages --- ee/app/views/trials/new.html.haml | 2 -- ee/app/views/trials/select.html.haml | 1 - 2 files changed, 3 deletions(-) diff --git a/ee/app/views/trials/new.html.haml b/ee/app/views/trials/new.html.haml index 77ae075658f58a..4d0b6ebe436c9e 100644 --- a/ee/app/views/trials/new.html.haml +++ b/ee/app/views/trials/new.html.haml @@ -1,7 +1,5 @@ - page_title _('Start your Free Ultimate Trial') - glm_params = { glm_source: params[:glm_source], glm_content: params[:glm_content] } -- content_for :page_specific_javascripts do - = render "layouts/one_trust" .row .col-md-6.offset-md-3 diff --git a/ee/app/views/trials/select.html.haml b/ee/app/views/trials/select.html.haml index dcfa7b8e23d8cf..4a5d6fab531e83 100644 --- a/ee/app/views/trials/select.html.haml +++ b/ee/app/views/trials/select.html.haml @@ -1,7 +1,6 @@ - page_title _('Start a Free Ultimate Trial') - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" - = render "layouts/one_trust" = render "layouts/google_tag_manager_body" .row -- GitLab From 7029aa51e9e0e0272a4fcf0c2398c9f69c155706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Fri, 8 Oct 2021 11:12:08 -0300 Subject: [PATCH 07/15] Address Content Security Policy concerns Moves CSP from the default/global level to the controllers, so the exception gets less risky. --- app/controllers/registrations_controller.rb | 10 ++++++++++ app/controllers/sessions_controller.rb | 10 ++++++++++ app/views/layouts/_one_trust.html.haml | 4 ++-- ee/app/controllers/trial_registrations_controller.rb | 10 ++++++++++ lib/gitlab/content_security_policy/config_loader.rb | 4 ++-- .../content_security_policy/config_loader_spec.rb | 12 ++++++------ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index fe800de5dd8b84..748d1b26e5c119 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -15,6 +15,16 @@ class RegistrationsController < Devise::RegistrationsController feature_category :authentication_and_authorization + content_security_policy do |policy| + next if policy.directives.blank? + + script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + policy.script_src(*script_src_values) + + connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + policy.connect_src(*connect_src_values) + end + def new @resource = build_resource end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 4fcf82c605b317..92014c11e3308d 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -55,6 +55,16 @@ class SessionsController < Devise::SessionsController CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha' MAX_FAILED_LOGIN_ATTEMPTS = 5 + content_security_policy do |policy| + next if policy.directives.blank? + + script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + policy.script_src(*script_src_values) + + connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + policy.connect_src(*connect_src_values) + end + def new set_minimum_password_length diff --git a/app/views/layouts/_one_trust.html.haml b/app/views/layouts/_one_trust.html.haml index 8460d071a88637..28d57e60ddc30b 100644 --- a/app/views/layouts/_one_trust.html.haml +++ b/app/views/layouts/_one_trust.html.haml @@ -1,7 +1,7 @@ - if one_trust_enabled? = javascript_include_tag "https://cdn.cookielaw.org/consent/#{extra_config.one_trust_id}/OtAutoBlock.js" - %script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => extra_config.one_trust_id, :defer => true, :nonce => true } - = javascript_tag nonce: true do + %script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => extra_config.one_trust_id, :defer => true, :nonce => content_security_policy_nonce } + = javascript_tag nonce: content_security_policy_nonce do :plain function OptanonWrapper() { } diff --git a/ee/app/controllers/trial_registrations_controller.rb b/ee/app/controllers/trial_registrations_controller.rb index 9da8a7f0402545..a43ab34f2a546b 100644 --- a/ee/app/controllers/trial_registrations_controller.rb +++ b/ee/app/controllers/trial_registrations_controller.rb @@ -12,6 +12,16 @@ class TrialRegistrationsController < RegistrationsController before_action :check_if_gl_com_or_dev before_action :set_redirect_url, only: [:new] + content_security_policy do |policy| + next if policy.directives.blank? + + script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + policy.script_src(*script_src_values) + + connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + policy.connect_src(*connect_src_values) + end + def new end diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index ed4f42c92e5f96..0e3fa8b8d8700e 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -15,7 +15,7 @@ def self.default_directives directives = { 'default_src' => "'self'", 'base_uri' => "'self'", - 'connect_src' => "'self' https://cdn.cookielaw.org", + 'connect_src' => "'self'", 'font_src' => "'self'", 'form_action' => "'self' https: http:", 'frame_ancestors' => "'self'", @@ -23,7 +23,7 @@ def self.default_directives 'img_src' => "'self' data: blob: http: https:", 'manifest_src' => "'self'", 'media_src' => "'self'", - 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://cdn.cookielaw.org", + 'script_src' => "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com", 'style_src' => "'self' 'unsafe-inline'", 'worker_src' => "'self' blob: data:", 'object_src' => "'none'", diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index d0afd502050b87..3ec332dace594b 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -56,22 +56,22 @@ context 'adds all websocket origins to support Safari' do it 'with insecure domain' do stub_config_setting(host: 'example.com', https: false) - expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com") + expect(directives['connect_src']).to eq("'self' ws://example.com") end it 'with secure domain' do stub_config_setting(host: 'example.com', https: true) - expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org wss://example.com") + expect(directives['connect_src']).to eq("'self' wss://example.com") end it 'with custom port' do stub_config_setting(host: 'example.com', port: '1234') - expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com:1234") + expect(directives['connect_src']).to eq("'self' ws://example.com:1234") end it 'with custom port and secure domain' do stub_config_setting(host: 'example.com', https: true, port: '1234') - expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org wss://example.com:1234") + expect(directives['connect_src']).to eq("'self' wss://example.com:1234") end end @@ -81,7 +81,7 @@ end it 'adds CDN host to CSP' do - expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://cdn.cookielaw.org https://example.com") + expect(directives['script_src']).to eq("'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com https://example.com") expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://example.com") expect(directives['font_src']).to eq("'self' https://example.com") end @@ -94,7 +94,7 @@ end it 'adds sentry path to CSP without user' do - expect(directives['connect_src']).to eq("'self' https://cdn.cookielaw.org ws://example.com dummy://example.com/43") + expect(directives['connect_src']).to eq("'self' ws://example.com dummy://example.com/43") end end -- GitLab From 3390ca52c4126df0db0b90af450105cb77afc949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Fri, 8 Oct 2021 16:28:48 -0300 Subject: [PATCH 08/15] Update Content Security Policy headers Adding the default-src values to policies so they work as usual on tests. Or other environments without explicit CSP config. --- app/controllers/registrations_controller.rb | 6 ++++-- app/controllers/sessions_controller.rb | 6 ++++-- ee/app/controllers/trial_registrations_controller.rb | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 748d1b26e5c119..2caed03a5a4845 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -18,10 +18,12 @@ class RegistrationsController < Devise::RegistrationsController content_security_policy do |policy| next if policy.directives.blank? - script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] policy.script_src(*script_src_values) - connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] + connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] policy.connect_src(*connect_src_values) end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 92014c11e3308d..656f3b88f33d81 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -58,10 +58,12 @@ class SessionsController < Devise::SessionsController content_security_policy do |policy| next if policy.directives.blank? - script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] policy.script_src(*script_src_values) - connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] + connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] policy.connect_src(*connect_src_values) end diff --git a/ee/app/controllers/trial_registrations_controller.rb b/ee/app/controllers/trial_registrations_controller.rb index a43ab34f2a546b..bc62008bc451f6 100644 --- a/ee/app/controllers/trial_registrations_controller.rb +++ b/ee/app/controllers/trial_registrations_controller.rb @@ -15,10 +15,12 @@ class TrialRegistrationsController < RegistrationsController content_security_policy do |policy| next if policy.directives.blank? - script_src_values = Array.wrap(policy.directives['script-src']) | ['https://cdn.cookielaw.org https://*.onetrust.com'] + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] policy.script_src(*script_src_values) - connect_src_values = Array.wrap(policy.directives['connect-src']) | ['https://cdn.cookielaw.org'] + default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] + connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] policy.connect_src(*connect_src_values) end -- GitLab From f615ee9b8316c2b04aec443b9a228e6e44e28f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Fri, 8 Oct 2021 17:28:45 -0300 Subject: [PATCH 09/15] Include OneTrust script on JS to avoid templates --- app/views/layouts/_one_trust.html.haml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/_one_trust.html.haml b/app/views/layouts/_one_trust.html.haml index 28d57e60ddc30b..cf0c75704a4fac 100644 --- a/app/views/layouts/_one_trust.html.haml +++ b/app/views/layouts/_one_trust.html.haml @@ -1,7 +1,14 @@ - if one_trust_enabled? = javascript_include_tag "https://cdn.cookielaw.org/consent/#{extra_config.one_trust_id}/OtAutoBlock.js" - %script{ :src => "https://cdn.cookielaw.org/scripttemplates/otSDKStub.js", :charset => "UTF-8", :"data-domain-script" => extra_config.one_trust_id, :defer => true, :nonce => content_security_policy_nonce } = javascript_tag nonce: content_security_policy_nonce do :plain + const oneTrustScript = document.createElement('script'); + oneTrustScript.src = 'https://cdn.cookielaw.org/scripttemplates/otSDKStub.js'; + oneTrustScript.dataset.domainScript = '#{extra_config.one_trust_id}'; + oneTrustScript.nonce = '#{content_security_policy_nonce}' + oneTrustScript.charset = 'UTF-8'; + oneTrustScript.defer = true; + document.head.appendChild(oneTrustScript); + function OptanonWrapper() { } -- GitLab From 5fb3adafaf33f8a3ad6462fde83b084e950ed77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Fri, 8 Oct 2021 19:04:37 -0300 Subject: [PATCH 10/15] Rename ecomm_instrumentation feature flag To follow-up the epic renaming, at: https://gitlab.com/groups/gitlab-org/-/epics/6347 --- app/helpers/auth_helper.rb | 2 +- ...google_analytics_cookies.yml => ecomm_instrumentation.yml} | 2 +- spec/helpers/auth_helper_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename config/feature_flags/ops/{ecomm_instrumentation_google_analytics_cookies.yml => ecomm_instrumentation.yml} (78%) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 183d64ee313881..f3831be18e3014 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -170,7 +170,7 @@ def google_tag_manager_enabled? end def one_trust_enabled? - Feature.enabled?(:ecomm_instrumentation_google_analytics_cookies, nil, type: :ops) && + Feature.enabled?(:ecomm_instrumentation, nil, type: :ops) && extra_config.has_key?('one_trust_id') && extra_config.one_trust_id.present? && !current_user diff --git a/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml b/config/feature_flags/ops/ecomm_instrumentation.yml similarity index 78% rename from config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml rename to config/feature_flags/ops/ecomm_instrumentation.yml index 5915977114ba34..e35937fa34492a 100644 --- a/config/feature_flags/ops/ecomm_instrumentation_google_analytics_cookies.yml +++ b/config/feature_flags/ops/ecomm_instrumentation.yml @@ -1,5 +1,5 @@ --- -name: ecomm_instrumentation_google_analytics_cookies +name: ecomm_instrumentation introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71243 rollout_issue_url: milestone: '14.4' diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index cc16fdb65a4716..5aae32f670ddf3 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -324,9 +324,9 @@ def auth_active? subject(:one_trust_enabled?) { helper.one_trust_enabled? } - context 'with ecomm_instrumentation_google_analytics_cookies feature flag enabled' do + context 'with ecomm_instrumentation feature flag enabled' do before do - stub_feature_flags(ecomm_instrumentation_google_analytics_cookies: true) + stub_feature_flags(ecomm_instrumentation: true) end context 'when current user is set' do -- GitLab From 2075d6fe12a77d702667c7b2ac0e6368728daaed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Tue, 12 Oct 2021 01:11:50 -0300 Subject: [PATCH 11/15] Put OneTrust CSP config behind a concern The concerns were previously set at controller level. --- app/controllers/concerns/one_trust_csp.rb | 19 +++++++++++++++++++ app/controllers/registrations_controller.rb | 13 +------------ app/controllers/sessions_controller.rb | 13 +------------ .../trial_registrations_controller.rb | 14 ++------------ 4 files changed, 23 insertions(+), 36 deletions(-) create mode 100644 app/controllers/concerns/one_trust_csp.rb diff --git a/app/controllers/concerns/one_trust_csp.rb b/app/controllers/concerns/one_trust_csp.rb new file mode 100644 index 00000000000000..4e98ec586ca887 --- /dev/null +++ b/app/controllers/concerns/one_trust_csp.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module OneTrustCSP + extend ActiveSupport::Concern + + included do + content_security_policy do |policy| + next if policy.directives.blank? + + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ["'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] + policy.script_src(*script_src_values) + + default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] + connect_src_values = Array.wrap(default_connect_src) | ['https://cdn.cookielaw.org'] + policy.connect_src(*connect_src_values) + end + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 2caed03a5a4845..840d7d3ca35170 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -5,6 +5,7 @@ class RegistrationsController < Devise::RegistrationsController include AcceptsPendingInvitations include RecaptchaHelper include InvisibleCaptchaOnSignup + include OneTrustCSP layout 'devise' @@ -15,18 +16,6 @@ class RegistrationsController < Devise::RegistrationsController feature_category :authentication_and_authorization - content_security_policy do |policy| - next if policy.directives.blank? - - default_script_src = policy.directives['script-src'] || policy.directives['default-src'] - script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] - policy.script_src(*script_src_values) - - default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] - policy.connect_src(*connect_src_values) - end - def new @resource = build_resource end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 656f3b88f33d81..bbd7e5d57253ee 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -9,6 +9,7 @@ class SessionsController < Devise::SessionsController include RendersLdapServers include KnownSignIn include Gitlab::Utils::StrongMemoize + include OneTrustCSP skip_before_action :check_two_factor_requirement, only: [:destroy] skip_before_action :check_password_expiration, only: [:destroy] @@ -55,18 +56,6 @@ class SessionsController < Devise::SessionsController CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha' MAX_FAILED_LOGIN_ATTEMPTS = 5 - content_security_policy do |policy| - next if policy.directives.blank? - - default_script_src = policy.directives['script-src'] || policy.directives['default-src'] - script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] - policy.script_src(*script_src_values) - - default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] - policy.connect_src(*connect_src_values) - end - def new set_minimum_password_length diff --git a/ee/app/controllers/trial_registrations_controller.rb b/ee/app/controllers/trial_registrations_controller.rb index bc62008bc451f6..568684cd663112 100644 --- a/ee/app/controllers/trial_registrations_controller.rb +++ b/ee/app/controllers/trial_registrations_controller.rb @@ -5,6 +5,8 @@ class TrialRegistrationsController < RegistrationsController extend ::Gitlab::Utils::Override + include OneTrustCSP + layout 'minimal' skip_before_action :require_no_authentication @@ -12,18 +14,6 @@ class TrialRegistrationsController < RegistrationsController before_action :check_if_gl_com_or_dev before_action :set_redirect_url, only: [:new] - content_security_policy do |policy| - next if policy.directives.blank? - - default_script_src = policy.directives['script-src'] || policy.directives['default-src'] - script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://cdn.cookielaw.org https://*.onetrust.com'] - policy.script_src(*script_src_values) - - default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | ["'self'", 'https://cdn.cookielaw.org'] - policy.connect_src(*connect_src_values) - end - def new end -- GitLab From bc54c51748ac14f3a282a4c4bd699d66885ab055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Tue, 12 Oct 2021 01:46:51 -0300 Subject: [PATCH 12/15] Fix false positive on OneTrust helper specs It also moves the logic from the AuthHelper to its own helper. --- app/helpers/auth_helper.rb | 7 ---- app/helpers/one_trust_helper.rb | 10 ++++++ spec/helpers/auth_helper_spec.rb | 39 ---------------------- spec/helpers/one_trust_helper_spec.rb | 48 +++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 46 deletions(-) create mode 100644 app/helpers/one_trust_helper.rb create mode 100644 spec/helpers/one_trust_helper_spec.rb diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index f3831be18e3014..a0c3a6f2f522d8 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -169,13 +169,6 @@ def google_tag_manager_enabled? !current_user end - def one_trust_enabled? - Feature.enabled?(:ecomm_instrumentation, nil, type: :ops) && - extra_config.has_key?('one_trust_id') && - extra_config.one_trust_id.present? && - !current_user - end - def auth_app_owner_text(owner) return unless owner diff --git a/app/helpers/one_trust_helper.rb b/app/helpers/one_trust_helper.rb new file mode 100644 index 00000000000000..9f92a73a4d4184 --- /dev/null +++ b/app/helpers/one_trust_helper.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module OneTrustHelper + def one_trust_enabled? + Feature.enabled?(:ecomm_instrumentation, type: :ops) && + Gitlab.config.extra.has_key?('one_trust_id') && + Gitlab.config.extra.one_trust_id.present? && + !current_user + end +end diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index 5aae32f670ddf3..c1c961c5cbb5fe 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -314,45 +314,6 @@ def auth_active? end end - describe '#one_trust_enabled?' do - let(:user) { nil } - - before do - stub_config(extra: { one_trust_id: 'key' }) - allow(helper).to receive(:current_user).and_return(user) - end - - subject(:one_trust_enabled?) { helper.one_trust_enabled? } - - context 'with ecomm_instrumentation feature flag enabled' do - before do - stub_feature_flags(ecomm_instrumentation: true) - end - - context 'when current user is set' do - let(:user) { instance_double('User') } - - it { is_expected.to be_falsey } - end - - context 'when no id is set' do - before do - stub_config(extra: {}) - end - - it { is_expected.to be_falsey } - end - - context 'when id is set and no user is set' do - before do - stub_config(extra: { one_trust_id: 'key' }) - end - - it { is_expected.to be_truthy } - end - end - end - describe '#auth_app_owner_text' do shared_examples 'generates text with the correct info' do it 'includes the name of the application owner' do diff --git a/spec/helpers/one_trust_helper_spec.rb b/spec/helpers/one_trust_helper_spec.rb new file mode 100644 index 00000000000000..37f486be78668a --- /dev/null +++ b/spec/helpers/one_trust_helper_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe OneTrustHelper do + describe '#one_trust_enabled?' do + let(:user) { nil } + + before do + stub_config(extra: { one_trust_id: 'id' }) + allow(helper).to receive(:current_user).and_return(user) + end + + subject(:one_trust_enabled?) { helper.one_trust_enabled? } + + context 'with ecomm_instrumentation feature flag disabled' do + before do + stub_feature_flags(ecomm_instrumentation: false) + end + + context 'when id is set and no user is set' do + let(:user) { instance_double('User') } + + it { is_expected.to be_falsey } + end + end + + context 'with ecomm_instrumentation feature flag enabled' do + context 'when current user is set' do + let(:user) { instance_double('User') } + + it { is_expected.to be_falsey } + end + + context 'when no id is set' do + before do + stub_config(extra: {}) + end + + it { is_expected.to be_falsey } + end + + context 'when id is set and no user is set' do + it { is_expected.to be_truthy } + end + end + end +end -- GitLab From 634d574e225abc7076f1f26dafbe669ea705ebcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Tue, 12 Oct 2021 01:47:14 -0300 Subject: [PATCH 13/15] Properly sanitize OneTrust id --- app/views/layouts/_one_trust.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_one_trust.html.haml b/app/views/layouts/_one_trust.html.haml index cf0c75704a4fac..4fab017d273cbf 100644 --- a/app/views/layouts/_one_trust.html.haml +++ b/app/views/layouts/_one_trust.html.haml @@ -1,11 +1,13 @@ - if one_trust_enabled? + - one_trust_id = sanitize(extra_config.one_trust_id, scrubber: Rails::Html::TextOnlyScrubber.new) + - = javascript_include_tag "https://cdn.cookielaw.org/consent/#{extra_config.one_trust_id}/OtAutoBlock.js" + = javascript_include_tag "https://cdn.cookielaw.org/consent/#{one_trust_id}/OtAutoBlock.js" = javascript_tag nonce: content_security_policy_nonce do :plain const oneTrustScript = document.createElement('script'); oneTrustScript.src = 'https://cdn.cookielaw.org/scripttemplates/otSDKStub.js'; - oneTrustScript.dataset.domainScript = '#{extra_config.one_trust_id}'; + oneTrustScript.dataset.domainScript = '#{one_trust_id}'; oneTrustScript.nonce = '#{content_security_policy_nonce}' oneTrustScript.charset = 'UTF-8'; oneTrustScript.defer = true; -- GitLab From 460cea1f1d90ce84a6fb918beb2ed57d6f3f03e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Tue, 12 Oct 2021 19:52:36 -0300 Subject: [PATCH 14/15] Add controller test case for OneTrust --- spec/features/users/login_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index d9c919dae3d57a..afbddc6e8e4d61 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -171,6 +171,18 @@ end end + describe 'with OneTrust authentication' do + before do + stub_config(extra: { one_trust_id: SecureRandom.uuid }) + end + + it 'has proper Content-Security-Policy headers' do + visit root_path + + expect(response_headers['Content-Security-Policy']).to include('https://cdn.cookielaw.org https://*.onetrust.com') + end + end + describe 'with two-factor authentication', :js do def enter_code(code) fill_in 'user_otp_attempt', with: code -- GitLab From 8efbd54578b6fa46c561512241634fe2b20bcbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Garc=C3=ADa?= Date: Tue, 12 Oct 2021 19:52:58 -0300 Subject: [PATCH 15/15] Update OneTrust helper spec to be more descriptive --- spec/helpers/one_trust_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/one_trust_helper_spec.rb b/spec/helpers/one_trust_helper_spec.rb index 37f486be78668a..85c38885304e61 100644 --- a/spec/helpers/one_trust_helper_spec.rb +++ b/spec/helpers/one_trust_helper_spec.rb @@ -7,7 +7,7 @@ let(:user) { nil } before do - stub_config(extra: { one_trust_id: 'id' }) + stub_config(extra: { one_trust_id: SecureRandom.uuid }) allow(helper).to receive(:current_user).and_return(user) end -- GitLab