Public: Set the global default configuration.
Optionally supply a block to override the defaults set by this library.
Returns the newly created config.
# File lib/secure_headers/configuration.rb, line 16 def default(&block) config = new(&block) add_noop_configuration add_configuration(DEFAULT_CONFIG, config) end
Public: retrieve a global configuration object
Returns the configuration with a given name or raises a NotYetConfiguredError if `default` has not been called.
# File lib/secure_headers/configuration.rb, line 43 def get(name = DEFAULT_CONFIG, internal: false) unless internal Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#get` is deprecated. It will be removed in the next major release. Use SecureHeaders::Configuration.dup to retrieve the default config." end if @configurations.nil? raise NotYetConfiguredError, "Default policy not yet supplied" end @configurations[name] end
# File lib/secure_headers/configuration.rb, line 59 def named_append(name, target = nil, &block) @appends ||= {} raise "Provide a configuration block" unless block_given? @appends[name] = block end
# File lib/secure_headers/configuration.rb, line 54 def named_appends(name) @appends ||= {} @appends[name] end
# File lib/secure_headers/configuration.rb, line 138 def initialize(&block) @cookies = self.class.send(:deep_copy_if_hash, Cookie::COOKIE_DEFAULTS) @clear_site_data = nil @csp = nil @csp_report_only = nil @hpkp_report_host = nil @hpkp = nil @hsts = nil @x_content_type_options = nil @x_download_options = nil @x_frame_options = nil @x_permitted_cross_domain_policies = nil @x_xss_protection = nil @expect_certificate_transparency = nil self.hpkp = OPT_OUT self.referrer_policy = OPT_OUT self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT) self.csp_report_only = OPT_OUT instance_eval(&block) if block_given? end
Public: create a named configuration that overrides the default config.
name - use an idenfier for the override config. base - override another existing config, or override the default config if no value is supplied.
Returns: the newly created config
# File lib/secure_headers/configuration.rb, line 30 def override(name, base = DEFAULT_CONFIG, &block) unless get(base, internal: true) raise NotYetConfiguredError, "#{base} policy not yet supplied" end override = @configurations[base].dup override.instance_eval(&block) if block_given? add_configuration(name, override) end
Private: add a valid configuration to the global set of named configs.
config - the config to store name - the lookup value for this config
Raises errors if the config is invalid or if a config named `name` already exists.
Returns the config, if valid
# File lib/secure_headers/configuration.rb, line 76 def add_configuration(name, config) config.validate_config! @configurations ||= {} config.send(:cache_headers!) config.send(:cache_hpkp_report_host) config.freeze @configurations[name] = config end
Private: Automatically add an “opt-out of everything” override.
Returns the noop config
# File lib/secure_headers/configuration.rb, line 88 def add_noop_configuration noop_config = new do |config| ALL_HEADER_CLASSES.each do |klass| config.send("#{klass::CONFIG_KEY}=", OPT_OUT) end end add_configuration(NOOP_CONFIGURATION, noop_config) end
Public: perform a basic deep dup. The shallow copy provided by dup/clone can lead to modifying parent objects.
# File lib/secure_headers/configuration.rb, line 100 def deep_copy(config) return unless config config.each_with_object({}) do |(key, value), hash| hash[key] = if value.is_a?(Array) value.dup else value end end end
Private: convenience method purely DRY things up. The value may not be a hash (e.g. OPT_OUT, nil)
# File lib/secure_headers/configuration.rb, line 113 def deep_copy_if_hash(value) if value.is_a?(Hash) deep_copy(value) else value end end
# File lib/secure_headers/configuration.rb, line 219 def csp=(new_csp) if new_csp.respond_to?(:opt_out?) @csp = new_csp.dup else if new_csp[:report_only] # invalid configuration implies that CSPRO should be set, CSP should not - so opt out raise ArgumentError, "#{Kernel.caller.first}: `#csp=` was supplied a config with report_only: true. Use #csp_report_only=" else @csp = ContentSecurityPolicyConfig.new(new_csp) end end end
Configures the Content-Security-Policy-Report-Only header. `new_csp` cannot contain `report_only: false` or an error will be raised.
NOTE: if csp has not been configured/has the default value when configuring #csp_report_only, the code will assume you mean to only use report-only mode and you will be opted-out of enforce mode.
# File lib/secure_headers/configuration.rb, line 238 def csp_report_only=(new_csp) @csp_report_only = begin if new_csp.is_a?(ContentSecurityPolicyConfig) new_csp.make_report_only elsif new_csp.respond_to?(:opt_out?) new_csp.dup else if new_csp[:report_only] == false # nil is a valid value on which we do not want to raise raise ContentSecurityPolicyConfigError, "`#csp_report_only=` was supplied a config with report_only: false. Use #csp=" else ContentSecurityPolicyReportOnlyConfig.new(new_csp) end end end end
Public: copy everything but the cached headers
Returns a deep-dup'd copy of this configuration.
# File lib/secure_headers/configuration.rb, line 164 def dup copy = self.class.new copy.cookies = self.class.send(:deep_copy_if_hash, @cookies) copy.csp = @csp.dup if @csp copy.csp_report_only = @csp_report_only.dup if @csp_report_only copy.cached_headers = self.class.send(:deep_copy_if_hash, @cached_headers) copy.x_content_type_options = @x_content_type_options copy.hsts = @hsts copy.x_frame_options = @x_frame_options copy.x_xss_protection = @x_xss_protection copy.x_download_options = @x_download_options copy.x_permitted_cross_domain_policies = @x_permitted_cross_domain_policies copy.clear_site_data = @clear_site_data copy.expect_certificate_transparency = @expect_certificate_transparency copy.referrer_policy = @referrer_policy copy.hpkp = @hpkp copy.hpkp_report_host = @hpkp_report_host copy end
# File lib/secure_headers/configuration.rb, line 184 def opt_out(header) send("#{header}=", OPT_OUT) self.cached_headers.delete(header) end
# File lib/secure_headers/configuration.rb, line 189 def update_x_frame_options(value) @x_frame_options = value self.cached_headers[XFrameOptions::CONFIG_KEY] = XFrameOptions.make_header(value) end
Public: validates all configurations values.
Raises various configuration errors if any invalid config is detected.
Returns nothing
# File lib/secure_headers/configuration.rb, line 199 def validate_config! StrictTransportSecurity.validate_config!(@hsts) ContentSecurityPolicy.validate_config!(@csp) ContentSecurityPolicy.validate_config!(@csp_report_only) ReferrerPolicy.validate_config!(@referrer_policy) XFrameOptions.validate_config!(@x_frame_options) XContentTypeOptions.validate_config!(@x_content_type_options) XXssProtection.validate_config!(@x_xss_protection) XDownloadOptions.validate_config!(@x_download_options) XPermittedCrossDomainPolicies.validate_config!(@x_permitted_cross_domain_policies) ClearSiteData.validate_config!(@clear_site_data) ExpectCertificateTransparency.validate_config!(@expect_certificate_transparency) PublicKeyPins.validate_config!(@hpkp) Cookie.validate_config!(@cookies) end
# File lib/secure_headers/configuration.rb, line 260 def cached_headers=(headers) @cached_headers = headers end
# File lib/secure_headers/configuration.rb, line 264 def hpkp=(hpkp) @hpkp = self.class.send(:deep_copy_if_hash, hpkp) end
# File lib/secure_headers/configuration.rb, line 268 def hpkp_report_host=(hpkp_report_host) @hpkp_report_host = hpkp_report_host end
Public: Precompute the header names and values for this configuration. Ensures that headers generated at configure time, not on demand.
Returns the cached headers
# File lib/secure_headers/configuration.rb, line 286 def cache_headers! # generate defaults for the "easy" headers headers = (ALL_HEADERS_BESIDES_CSP).each_with_object({}) do |klass, hash| config = instance_variable_get("@#{klass::CONFIG_KEY}") unless config == OPT_OUT hash[klass::CONFIG_KEY] = klass.make_header(config).freeze end end generate_csp_headers(headers) headers.freeze self.cached_headers = headers end
# File lib/secure_headers/configuration.rb, line 274 def cache_hpkp_report_host has_report_uri = @hpkp && @hpkp != OPT_OUT && @hpkp[:report_uri] self.hpkp_report_host = if has_report_uri parsed_report_uri = URI.parse(@hpkp[:report_uri]) parsed_report_uri.host end end
Private: adds CSP headers for each variation of CSP support.
headers - generated headers are added to this hash namespaced by The
different variations
Returns nothing
# File lib/secure_headers/configuration.rb, line 307 def generate_csp_headers(headers) generate_csp_headers_for_config(headers, ContentSecurityPolicyConfig::CONFIG_KEY, self.csp) generate_csp_headers_for_config(headers, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY, self.csp_report_only) end
# File lib/secure_headers/configuration.rb, line 312 def generate_csp_headers_for_config(headers, header_key, csp_config) unless csp_config.opt_out? headers[header_key] = {} ContentSecurityPolicy::VARIATIONS.each_key do |name| csp = ContentSecurityPolicy.make_header(csp_config, UserAgent.parse(name)) headers[header_key][name] = csp.freeze end end end