discourse/lib/content_security_policy/builder.rb
David Taylor 174a8b431b
DEV: Support passing relative URLs CSP builder (#19176)
Raw paths like `/test/path` are not supported natively in the CSP. This commit prepends the site's base URL to these paths. This allows plugins to add 'local' assets to the CSP without needing to hardcode the site's hostname.
2022-11-24 11:27:47 +00:00

89 lines
1.9 KiB
Ruby

# frozen_string_literal: true
require 'content_security_policy/default'
class ContentSecurityPolicy
class Builder
EXTENDABLE_DIRECTIVES = %i[
base_uri
frame_ancestors
manifest_src
object_src
script_src
worker_src
].freeze
# Make extending these directives no-op, until core includes them in default CSP
TO_BE_EXTENDABLE = %i[
connect_src
default_src
font_src
form_action
frame_src
img_src
media_src
prefetch_src
style_src
].freeze
def initialize(base_url:)
@directives = Default.new(base_url: base_url).directives
@base_url = base_url
end
def <<(extension)
return unless valid_extension?(extension)
extension.each { |directive, sources| extend_directive(normalize_directive(directive), sources) }
end
def build
policy = ActionDispatch::ContentSecurityPolicy.new
@directives.each do |directive, sources|
if sources.is_a?(Array)
policy.public_send(directive, *sources)
else
policy.public_send(directive, sources)
end
end
policy.build
end
private
def normalize_directive(directive)
directive.to_s.gsub('-', '_').to_sym
end
def normalize_source(source)
if source.starts_with?("/")
"#{@base_url}#{source}"
else
source
end
rescue URI::ParseError
source
end
def extend_directive(directive, sources)
return unless extendable?(directive)
@directives[directive] ||= []
sources = Array(sources).map { |s| normalize_source(s) }
@directives[directive].concat(sources)
@directives[directive].delete(:none) if @directives[directive].count > 1
end
def extendable?(directive)
EXTENDABLE_DIRECTIVES.include?(directive)
end
def valid_extension?(extension)
extension.is_a?(Hash)
end
end
end