diff --git a/app/controllers/concerns/one_trust_csp.rb b/app/controllers/concerns/one_trust_csp.rb new file mode 100644 index 0000000000000000000000000000000000000000..4e98ec586ca887572b8b331c01ceafb34ad1e9f0 --- /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 fe800de5dd8b84e237899c93eee28f93dd82dad1..840d7d3ca35170abba4dea686349828cfd8fb2d5 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' diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 4fcf82c605b3177e1bd910cb3171f420fa5c527c..bbd7e5d57253eea9d41f9008d3851dd9cc9c2dea 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] diff --git a/app/helpers/one_trust_helper.rb b/app/helpers/one_trust_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..9f92a73a4d41845fac7dc574262b131e61747d3e --- /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/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 4ec3fcde337be629979cd4a84448aa141ea1caad..87108c8ea78cfb68e74979c7014047a52af3e852 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 74f3e3e7e34f709095073beefa5a8085cc415473..da6232b2a2bd0a1723aa505577795119b0f4e4dd 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 0000000000000000000000000000000000000000..4fab017d273cbf3396bf7c7217ff367c4855bcf6 --- /dev/null +++ b/app/views/layouts/_one_trust.html.haml @@ -0,0 +1,16 @@ +- 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/#{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 = '#{one_trust_id}'; + oneTrustScript.nonce = '#{content_security_policy_nonce}' + oneTrustScript.charset = 'UTF-8'; + oneTrustScript.defer = true; + document.head.appendChild(oneTrustScript); + + function OptanonWrapper() { } diff --git a/config/feature_flags/ops/ecomm_instrumentation.yml b/config/feature_flags/ops/ecomm_instrumentation.yml new file mode 100644 index 0000000000000000000000000000000000000000..e35937fa34492a6bb3dea6bf9410603bbe5fd7d0 --- /dev/null +++ b/config/feature_flags/ops/ecomm_instrumentation.yml @@ -0,0 +1,8 @@ +--- +name: ecomm_instrumentation +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71243 +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 89f2d82c6291cd3b5d7a574a1b8ece4cc01babb1..3d2acce9a691b635219d9f438713195ba1eec1e0 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/ee/app/controllers/trial_registrations_controller.rb b/ee/app/controllers/trial_registrations_controller.rb index 9da8a7f0402545e4d02d99d20727b104ec134746..568684cd663112385546b677634063a08cd5de22 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 diff --git a/ee/app/views/trial_registrations/new.html.haml b/ee/app/views/trial_registrations/new.html.haml index f3e585c6e8989ffa4a4ae3b86430c2bcccb8660d..2b5df5791fe234475bfb19c34cf02c9573190c54 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/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index d9c919dae3d57acd03b7992b84edb976e0afec49..afbddc6e8e4d617fcd1814bccefc14dc259e9073 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 diff --git a/spec/helpers/one_trust_helper_spec.rb b/spec/helpers/one_trust_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..85c38885304e617066fb3c31f5c991ef3c7daf25 --- /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: SecureRandom.uuid }) + 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