Skip to content

Commit

Permalink
Extract class from CSP configuration/initialization (mastodon#26905)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjankowski authored Oct 27, 2023
1 parent 2e6bf60 commit eae5c73
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 17 deletions.
59 changes: 59 additions & 0 deletions app/lib/content_security_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

class ContentSecurityPolicy
def base_host
Rails.configuration.x.web_domain
end

def assets_host
url_from_configured_asset_host || url_from_base_host
end

def media_host
cdn_host_value || assets_host
end

private

def url_from_configured_asset_host
Rails.configuration.action_controller.asset_host
end

def cdn_host_value
s3_alias_host || s3_cloudfront_host || azure_alias_host || s3_hostname_host
end

def url_from_base_host
host_to_url(base_host)
end

def host_to_url(host_string)
uri_from_configuration_and_string(host_string) if host_string.present?
end

def s3_alias_host
host_to_url ENV.fetch('S3_ALIAS_HOST', nil)
end

def s3_cloudfront_host
host_to_url ENV.fetch('S3_CLOUDFRONT_HOST', nil)
end

def azure_alias_host
host_to_url ENV.fetch('AZURE_ALIAS_HOST', nil)
end

def s3_hostname_host
host_to_url ENV.fetch('S3_HOSTNAME', nil)
end

def uri_from_configuration_and_string(host_string)
Addressable::URI.parse("#{host_protocol}://#{host_string}").tap do |uri|
uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/')
end.to_s
end

def host_protocol
Rails.configuration.x.use_https ? 'https' : 'http'
end
end
21 changes: 4 additions & 17 deletions config/initializers/content_security_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,11 @@
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header

def host_to_url(str)
return if str.blank?
require_relative '../../app/lib/content_security_policy'

uri = Addressable::URI.parse("http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}")
uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/')
uri.to_s
end

base_host = Rails.configuration.x.web_domain

assets_host = Rails.configuration.action_controller.asset_host
assets_host ||= host_to_url(base_host)

media_host = host_to_url(ENV['S3_ALIAS_HOST'])
media_host ||= host_to_url(ENV['S3_CLOUDFRONT_HOST'])
media_host ||= host_to_url(ENV['AZURE_ALIAS_HOST'])
media_host ||= host_to_url(ENV['S3_HOSTNAME']) if ENV['S3_ENABLED'] == 'true'
media_host ||= assets_host
policy = ContentSecurityPolicy.new
assets_host = policy.assets_host
media_host = policy.media_host

def sso_host
return unless ENV['ONE_CLICK_SSO_LOGIN'] == 'true'
Expand Down
129 changes: 129 additions & 0 deletions spec/lib/content_security_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# frozen_string_literal: true

require 'rails_helper'

describe ContentSecurityPolicy do
subject { described_class.new }

around do |example|
original_asset_host = Rails.configuration.action_controller.asset_host
original_web_domain = Rails.configuration.x.web_domain
original_use_https = Rails.configuration.x.use_https
example.run
Rails.configuration.action_controller.asset_host = original_asset_host
Rails.configuration.x.web_domain = original_web_domain
Rails.configuration.x.use_https = original_use_https
end

describe '#base_host' do
before { Rails.configuration.x.web_domain = 'host.example' }

it 'returns the configured value for the web domain' do
expect(subject.base_host).to eq 'host.example'
end
end

describe '#assets_host' do
context 'when asset_host is not configured' do
before { Rails.configuration.action_controller.asset_host = nil }

context 'with a configured web domain' do
before { Rails.configuration.x.web_domain = 'host.example' }

context 'when use_https is enabled' do
before { Rails.configuration.x.use_https = true }

it 'returns value from base host with https protocol' do
expect(subject.assets_host).to eq 'https://host.example'
end
end

context 'when use_https is disabled' do
before { Rails.configuration.x.use_https = false }

it 'returns value from base host with http protocol' do
expect(subject.assets_host).to eq 'http://host.example'
end
end
end
end

context 'when asset_host is configured' do
before do
Rails.configuration.action_controller.asset_host = 'https://assets.host.example'
end

it 'returns full value from configured host' do
expect(subject.assets_host).to eq 'https://assets.host.example'
end
end
end

describe '#media_host' do
context 'when there is no configured CDN' do
it 'defaults to using the assets_host value' do
expect(subject.media_host).to eq(subject.assets_host)
end
end

context 'when an S3 alias host is configured' do
around do |example|
ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example' do
example.run
end
end

it 'uses the s3 alias host value' do
expect(subject.media_host).to eq 'https://asset-host.s3-alias.example'
end
end

context 'when an S3 alias host with a trailing path is configured' do
around do |example|
ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example/pathname' do
example.run
end
end

it 'uses the s3 alias host value and preserves the path' do
expect(subject.media_host).to eq 'https://asset-host.s3-alias.example/pathname/'
end
end

context 'when an S3 cloudfront host is configured' do
around do |example|
ClimateControl.modify S3_CLOUDFRONT_HOST: 'asset-host.s3-cloudfront.example' do
example.run
end
end

it 'uses the s3 cloudfront host value' do
expect(subject.media_host).to eq 'https://asset-host.s3-cloudfront.example'
end
end

context 'when an azure alias host is configured' do
around do |example|
ClimateControl.modify AZURE_ALIAS_HOST: 'asset-host.azure-alias.example' do
example.run
end
end

it 'uses the azure alias host value' do
expect(subject.media_host).to eq 'https://asset-host.azure-alias.example'
end
end

context 'when s3_enabled is configured' do
around do |example|
ClimateControl.modify S3_ENABLED: 'true', S3_HOSTNAME: 'asset-host.s3.example' do
example.run
end
end

it 'uses the s3 hostname host value' do
expect(subject.media_host).to eq 'https://asset-host.s3.example'
end
end
end
end

0 comments on commit eae5c73

Please sign in to comment.