FEATURE: Implement nonces for Google Tag Manager integration (#12531)

This commit is contained in:
Penar Musaraj 2021-03-26 11:19:31 -04:00 committed by GitHub
parent d858c7680a
commit 5096920500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 29 additions and 8 deletions

View File

@ -2,6 +2,7 @@
(function () { (function () {
const gtmDataElement = document.getElementById("data-google-tag-manager"); const gtmDataElement = document.getElementById("data-google-tag-manager");
const dataLayerJson = JSON.parse(gtmDataElement.dataset.dataLayer); const dataLayerJson = JSON.parse(gtmDataElement.dataset.dataLayer);
const gtmNonce = gtmDataElement.dataset.nonce;
// dataLayer declaration needs to precede the container snippet // dataLayer declaration needs to precede the container snippet
// https://developers.google.com/tag-manager/devguide#adding-data-layer-variables-to-a-page // https://developers.google.com/tag-manager/devguide#adding-data-layer-variables-to-a-page
@ -12,7 +13,9 @@
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 'https://www.googletagmanager.com/gtm.js?id='+i+dl;
j.setAttribute("nonce", gtmNonce);
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer',gtmDataElement.dataset.containerId); })(window,document,'script','dataLayer',gtmDataElement.dataset.containerId);
/* eslint-enable */ /* eslint-enable */
})(); })();

View File

@ -35,6 +35,10 @@ module ApplicationHelper
google_universal_analytics_json google_universal_analytics_json
end end
def self.google_tag_manager_nonce
@gtm_nonce ||= SecureRandom.hex
end
def shared_session_key def shared_session_key
if SiteSetting.long_polling_base_url != '/' && current_user if SiteSetting.long_polling_base_url != '/' && current_user
sk = "shared_session_key" sk = "shared_session_key"

View File

@ -8,14 +8,10 @@ module CommonHelper
end end
def render_google_tag_manager_head_code def render_google_tag_manager_head_code
if Rails.env.production? && SiteSetting.gtm_container_id.present? render partial: "common/google_tag_manager_head" if SiteSetting.gtm_container_id.present?
render partial: "common/google_tag_manager_head"
end
end end
def render_google_tag_manager_body_code def render_google_tag_manager_body_code
if Rails.env.production? && SiteSetting.gtm_container_id.present? render partial: "common/google_tag_manager_body" if SiteSetting.gtm_container_id.present?
render partial: "common/google_tag_manager_body"
end
end end
end end

View File

@ -1,5 +1,6 @@
<meta id="data-google-tag-manager" <meta id="data-google-tag-manager"
data-data-layer="<%= google_tag_manager_json %>" data-data-layer="<%= google_tag_manager_json %>"
data-nonce="<%= ApplicationHelper.google_tag_manager_nonce %>"
data-container-id="<%= SiteSetting.gtm_container_id %>" /> data-container-id="<%= SiteSetting.gtm_container_id %>" />
<%= preload_script 'google-tag-manager' %> <%= preload_script 'google-tag-manager' %>

View File

@ -60,7 +60,10 @@ class ContentSecurityPolicy
# we need analytics.js still as gtag/js is a script wrapper for it # we need analytics.js still as gtag/js is a script wrapper for it
sources << 'https://www.google-analytics.com/analytics.js' if SiteSetting.ga_universal_tracking_code.present? sources << 'https://www.google-analytics.com/analytics.js' if SiteSetting.ga_universal_tracking_code.present?
sources << 'https://www.googletagmanager.com/gtag/js' if SiteSetting.ga_universal_tracking_code.present? && SiteSetting.ga_version == "v4_gtag" sources << 'https://www.googletagmanager.com/gtag/js' if SiteSetting.ga_universal_tracking_code.present? && SiteSetting.ga_version == "v4_gtag"
sources << 'https://www.googletagmanager.com/gtm.js' if SiteSetting.gtm_container_id.present? if SiteSetting.gtm_container_id.present?
sources << 'https://www.googletagmanager.com/gtm.js'
sources << "'nonce-#{ApplicationHelper.google_tag_manager_nonce}'"
end
end end
end end

View File

@ -94,6 +94,7 @@ describe ContentSecurityPolicy do
script_srcs = parse(policy)['script-src'] script_srcs = parse(policy)['script-src']
expect(script_srcs).to include('https://www.googletagmanager.com/gtm.js') expect(script_srcs).to include('https://www.googletagmanager.com/gtm.js')
expect(script_srcs.to_s).to include('nonce-')
end end
it 'allowlists CDN assets when integrated' do it 'allowlists CDN assets when integrated' do

View File

@ -637,6 +637,19 @@ RSpec.describe ApplicationController do
expect(response.headers).to_not include('Content-Security-Policy-Report-Only') expect(response.headers).to_not include('Content-Security-Policy-Report-Only')
end end
it 'when GTM is enabled it adds the same nonce to the policy and the GTM tag' do
SiteSetting.content_security_policy = true
SiteSetting.gtm_container_id = 'GTM-ABCDEF'
get '/latest'
nonce = ApplicationHelper.google_tag_manager_nonce
expect(response.headers).to include('Content-Security-Policy')
script_src = parse(response.headers['Content-Security-Policy'])['script-src']
expect(script_src.to_s).to include(nonce)
expect(response.body).to include(nonce)
end
def parse(csp_string) def parse(csp_string)
csp_string.split(';').map do |policy| csp_string.split(';').map do |policy|
directive, *sources = policy.split directive, *sources = policy.split