discourse/app/views/layouts/application.html.erb
David Taylor b1f74ab59e
FEATURE: Add experimental option for strict-dynamic CSP (#25664)
The strict-dynamic CSP directive is supported in all our target browsers, and makes for a much simpler configuration. Instead of allowlisting paths, we use a per-request nonce to authorize `<script>` tags, and then those scripts are allowed to load additional scripts (or add additional inline scripts) without restriction.

This becomes especially useful when admins want to add external scripts like Google Tag Manager, or advertising scripts, which then go on to load a ton of other scripts.

All script tags introduced via themes will automatically have the nonce attribute applied, so it should be zero-effort for theme developers. Plugins *may* need some changes if they are inserting their own script tags.

This commit introduces a strict-dynamic-based CSP behind an experimental `content_security_policy_strict_dynamic` site setting.
2024-02-16 11:16:54 +00:00

152 lines
5.4 KiB
Plaintext

<!DOCTYPE html>
<html lang="<%= html_lang %>" class="<%= html_classes %>">
<head>
<meta charset="utf-8">
<title><%= content_for?(:title) ? yield(:title) : SiteSetting.title %></title>
<meta name="description" content="<%= @description_meta || SiteSetting.site_description %>">
<meta name="discourse_theme_id" content="<%= theme_id %>">
<meta name="discourse_current_homepage" content="<%= current_homepage %>">
<%- if GlobalSetting.preload_link_header %>
<%= render partial: "common/discourse_preload_stylesheet" %>
<%- end %>
<%= render partial: "layouts/head" %>
<%= discourse_csrf_tags %>
<%- if SiteSetting.enable_escaped_fragments? %>
<meta name="fragment" content="!">
<%- end %>
<%- if shared_session_key %>
<meta name="shared_session_key" content="<%= shared_session_key %>">
<%- end %>
<%= build_plugin_html 'server:before-script-load' %>
<%- if GlobalSetting.preload_link_header %>
<% add_resource_preload_list(script_asset_path("start-discourse"), "script") %>
<% add_resource_preload_list(script_asset_path("browser-update"), "script") %>
<%- else %>
<link rel="preload" href="<%= script_asset_path "start-discourse" %>" as="script" nonce="<%= csp_nonce_placeholder %>">
<link rel="preload" href="<%= script_asset_path "browser-update" %>" as="script" nonce="<%= csp_nonce_placeholder %>">
<%- end %>
<%= preload_script 'browser-detect' %>
<%= preload_script "vendor" %>
<%= preload_script "discourse" %>
<%- Discourse.find_plugin_js_assets(include_official: allow_plugins?, include_unofficial: allow_third_party_plugins?, request: request).each do |file| %>
<%= preload_script file %>
<%- end %>
<%= preload_script "locales/#{I18n.locale}" %>
<%- if ExtraLocalesController.client_overrides_exist? %>
<%= preload_script_url ExtraLocalesController.url('overrides') %>
<%- end %>
<%- if staff? %>
<%= preload_script_url ExtraLocalesController.url("admin") %>
<%= preload_script "admin" %>
<%- end %>
<%- if admin? %>
<%= preload_script_url ExtraLocalesController.url("wizard") %>
<%- end %>
<%- unless customization_disabled? %>
<%= theme_translations_lookup %>
<%= theme_js_lookup %>
<%= theme_lookup("head_tag") %>
<%- end %>
<%= render_google_tag_manager_head_code %>
<%= render_google_universal_analytics_code %>
<link id="manifest-link" rel="manifest" href=<%= manifest_url %> crossorigin="use-credentials">
<%- if include_ios_native_app_banner? %>
<meta name="apple-itunes-app" content="app-id=<%= SiteSetting.ios_app_id %><%= ios_app_argument %>">
<%- end %>
<%= yield :head %>
<%= build_plugin_html 'server:before-head-close' %>
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
<meta name="discourse/config/environment" content="<%=u discourse_config_environment %>" />
<%- if authentication_data %>
<meta id="data-authentication" data-authentication-data="<%= authentication_data %>">
<%- end %>
</head>
<body class="<%= body_classes %>">
<%- if include_splash_screen? %>
<%= render partial: "common/discourse_splash" %>
<%- end %>
<discourse-assets>
<discourse-assets-stylesheets>
<%= render partial: "common/discourse_stylesheet" %>
</discourse-assets-stylesheets>
<discourse-assets-json>
<div class="hidden" id="data-preloaded" data-preloaded="<%= preloaded_json %>"></div>
</discourse-assets-json>
<discourse-assets-icons></discourse-assets-icons>
</discourse-assets>
<%- if allow_plugins? %>
<%= build_plugin_html 'server:after-body-open' %>
<%- end -%>
<%= render_google_tag_manager_body_code %>
<noscript data-path="<%= request.env['PATH_INFO'] %>">
<%= escape_noscript do %>
<%= render partial: "layouts/noscript_header" %>
<div id="main-outlet" class="wrap" role="main">
<!-- preload-content: -->
<%= yield %>
<!-- :preload-content -->
</div>
<%= render partial: "layouts/noscript_footer" %>
<% end %>
</noscript>
<%- unless customization_disabled? %>
<%= theme_lookup("header") %>
<%- end %>
<%- if allow_plugins? %>
<%= build_plugin_html 'server:header' %>
<%- end %>
<section id='main'>
</section>
<% unless current_user %>
<form id='hidden-login-form' method="post" action="<%=main_app.login_path%>" style="display: none;">
<input name="username" type="text" id="signin_username">
<input name="password" type="password" id="signin_password">
<input name="redirect" type="hidden">
<input type="submit" id="signin-button" value="<%= t 'log_in' %>">
</form>
<% end %>
<script defer src="<%= script_asset_path "start-discourse" %>" nonce="<%= csp_nonce_placeholder %>"></script>
<%= yield :data %>
<script defer src="<%= script_asset_path "browser-update" %>" nonce="<%= csp_nonce_placeholder %>"></script>
<%- unless customization_disabled? %>
<%= theme_lookup("body_tag") %>
<%- end %>
<%- if allow_plugins? %>
<%= build_plugin_html 'server:before-body-close' %>
<%- end %>
<script nonce="<%= csp_nonce_placeholder %>">/* Workaround for https://bugs.webkit.org/show_bug.cgi?id=209261 */</script>
</body>
</html>