module SecureHeaders::PolicyManagement::ClassMethods

Public Instance Methods

combine_policies(original, additions) click to toggle source

Public: combine the values from two different configs.

original - the main config additions - values to be merged in

raises an error if the original config is OPT_OUT

  1. for non-source-list values (report_only, block_all_mixed_content, upgrade_insecure_requests),

additions will overwrite the original value.

  1. if a value in additions does not exist in the original config, the

default-src value is included to match original behavior.

  1. if a value in additions does exist in the original config, the two

values are joined.

# File lib/secure_headers/headers/policy_management.rb, line 235
def combine_policies(original, additions)
  if original == OPT_OUT
    raise ContentSecurityPolicyConfigError.new("Attempted to override an opt-out CSP config.")
  end

  original = Configuration.send(:deep_copy, original)
  populate_fetch_source_with_default!(original, additions)
  merge_policy_additions(original, additions)
end
idempotent_additions?(config, additions) click to toggle source

Public: determine if merging additions will cause a change to the actual value of the config.

e.g. config = { script_src: %w(example.org google.com)} and additions = { script_src: %w(google.com)} then idempotent_additions? would return because google.com is already in the config.

# File lib/secure_headers/headers/policy_management.rb, line 216
def idempotent_additions?(config, additions)
  return true if config == OPT_OUT && additions == OPT_OUT
  return false if config == OPT_OUT
  config == combine_policies(config, additions)
end
make_header(config, user_agent) click to toggle source

Public: generate a header name, value array that is user-agent-aware.

Returns a default policy if no configuration is provided, or a header name and value based on the config.

# File lib/secure_headers/headers/policy_management.rb, line 189
def make_header(config, user_agent)
  header = new(config, user_agent)
  [header.name, header.value]
end
ua_to_variation(user_agent) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 245
def ua_to_variation(user_agent)
  family = user_agent.browser
  if family && VARIATIONS.key?(family)
    family
  else
    OTHER
  end
end
validate_config!(config) click to toggle source

Public: Validates each source expression.

Does not validate the invididual values of the source expression (e.g. script_src => h*t*t*p: will not raise an exception)

# File lib/secure_headers/headers/policy_management.rb, line 198
def validate_config!(config)
  return if config.nil? || config == OPT_OUT
  raise ContentSecurityPolicyConfigError.new(":default_src is required") unless config[:default_src]
  config.each do |key, value|
    if META_CONFIGS.include?(key)
      raise ContentSecurityPolicyConfigError.new("#{key} must be a boolean value") unless boolean?(value) || value.nil?
    else
      validate_directive!(key, value)
    end
  end
end

Private Instance Methods

boolean?(source_expression) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 348
def boolean?(source_expression)
  source_expression.is_a?(TrueClass) || source_expression.is_a?(FalseClass)
end
ensure_array_of_strings!(directive, source_expression) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 334
def ensure_array_of_strings!(directive, source_expression)
  unless source_expression.is_a?(Array) && source_expression.compact.all? { |v| v.is_a?(String) }
    raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of strings")
  end
end
ensure_valid_directive!(directive) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 328
def ensure_valid_directive!(directive)
  unless ContentSecurityPolicy::ALL_DIRECTIVES.include?(directive)
    raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
  end
end
ensure_valid_sources!(directive, source_expression) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 340
def ensure_valid_sources!(directive, source_expression)
  source_expression.each do |source_expression|
    if ContentSecurityPolicy::DEPRECATED_SOURCE_VALUES.include?(source_expression)
      raise ContentSecurityPolicyConfigError.new("#{directive} contains an invalid keyword source (#{source_expression}). This value must be single quoted.")
    end
  end
end
merge_policy_additions(original, additions) click to toggle source

merge the two hashes. combine (instead of overwrite) the array values when each hash contains a value for a given key.

# File lib/secure_headers/headers/policy_management.rb, line 258
def merge_policy_additions(original, additions)
  original.merge(additions) do |directive, lhs, rhs|
    if source_list?(directive)
      (lhs.to_a + rhs.to_a).compact.uniq
    else
      rhs
    end
  end.reject { |_, value| value.nil? || value == [] } # this mess prevents us from adding empty directives.
end
nonce_added?(original, additions) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 286
def nonce_added?(original, additions)
  [:script_nonce, :style_nonce].each do |nonce|
    if additions[nonce] && !original[nonce]
      return true
    end
  end
end
populate_fetch_source_with_default!(original, additions) click to toggle source

For each directive in additions that does not exist in the original config, copy the default-src value to the original config. This modifies the original hash.

# File lib/secure_headers/headers/policy_management.rb, line 270
def populate_fetch_source_with_default!(original, additions)
  # in case we would be appending to an empty directive, fill it with the default-src value
  additions.keys.each do |directive|
    if !original[directive] && ((source_list?(directive) && FETCH_SOURCES.include?(directive)) || nonce_added?(original, additions))
      if nonce_added?(original, additions)
        inferred_directive = directive.to_s.gsub(/_nonce/, "_src").to_sym
        unless original[inferred_directive] || NON_FETCH_SOURCES.include?(inferred_directive)
          original[inferred_directive] = original[:default_src]
        end
      else
        original[directive] = original[:default_src]
      end
    end
  end
end
source_list?(directive) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 294
def source_list?(directive)
  DIRECTIVE_VALUE_TYPES[directive] == :source_list
end
validate_directive!(directive, source_expression) click to toggle source

Private: Validates that the configuration has a valid type, or that it is a valid source expression.

# File lib/secure_headers/headers/policy_management.rb, line 300
def validate_directive!(directive, source_expression)
  case ContentSecurityPolicy::DIRECTIVE_VALUE_TYPES[directive]
  when :boolean
    unless boolean?(source_expression)
      raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean value")
    end
  when :string
    unless source_expression.is_a?(String)
      raise ContentSecurityPolicyConfigError.new("#{directive} Must be a string. Found #{config.class}: #{config} value")
    end
  else
    validate_source_expression!(directive, source_expression)
  end
end
validate_source_expression!(directive, source_expression) click to toggle source

Private: validates that a source expression:

  1. has a valid name

  2. is an array of strings

  3. does not contain any depreated, now invalid values (inline, eval, self, none)

Does not validate the invididual values of the source expression (e.g. script_src => h*t*t*p: will not raise an exception)

# File lib/secure_headers/headers/policy_management.rb, line 322
def validate_source_expression!(directive, source_expression)
  ensure_valid_directive!(directive)
  ensure_array_of_strings!(directive, source_expression)
  ensure_valid_sources!(directive, source_expression)
end