Class: SecureHeaders::ContentSecurityPolicy

Inherits:
Header
  • Object
show all
Includes:
Constants
Defined in:
lib/secure_headers/headers/content_security_policy.rb,
lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb

Defined Under Namespace

Modules: Constants Classes: ScriptHashMiddleware

Constant Summary

Constants included from Constants

Constants::ALL_DIRECTIVES, Constants::CHROME_DIRECTIVES, Constants::CONFIG_KEY, Constants::DEFAULT_CSP_HEADER, Constants::DIRECTIVES_1_0, Constants::DIRECTIVES_2_0, Constants::DIRECTIVES_3_0, Constants::DIRECTIVES_DRAFT, Constants::ENV_KEY, Constants::FIREFOX_DIRECTIVES, Constants::FIREFOX_UNSUPPORTED_DIRECTIVES, Constants::HEADER_NAME, Constants::SAFARI_DIRECTIVES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil, options = {}) ⇒ ContentSecurityPolicy

options param contains :controller used for setting instance variables for nonces/hashes :ssl_request used to determine if http_additions should be used :ua the user agent (or just use Firefox/Chrome/MSIE/etc)

:report used to determine what :ssl_request, :ua, and :request_uri are set to



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/secure_headers/headers/content_security_policy.rb', line 125

def initialize(config=nil, options={})
  return unless config

  if options[:request]
    options = options.merge(self.class.options_from_request(options[:request]))
  end

  @controller = options[:controller]
  @ua = options[:ua]
  @ssl_request = !!options.delete(:ssl)
  @request_uri = options.delete(:request_uri)
  @http_additions = config.delete(:http_additions)
  @disable_img_src_data_uri = !!config.delete(:disable_img_src_data_uri)
  @tag_report_uri = !!config.delete(:tag_report_uri)
  @script_hashes = config.delete(:script_hashes) || []
  @app_name = config.delete(:app_name)
  @app_name = @app_name.call(@controller) if @app_name.respond_to?(:call)
  @enforce = config.delete(:enforce)
  @enforce = @enforce.call(@controller) if @enforce.respond_to?(:call)
  @enforce = !!@enforce

  # Config values can be string, array, or lamdba values
  @config = config.inject({}) do |hash, (key, value)|
    config_val = value.respond_to?(:call) ? value.call(@controller) : value
    if ALL_DIRECTIVES.include?(key.to_sym) # directives need to be normalized to arrays of strings
      config_val = config_val.split if config_val.is_a? String
      if config_val.is_a?(Array)
        config_val = config_val.map do |val|
          translate_dir_value(val)
        end.flatten.uniq
      end
    elsif key != :script_hash_middleware
      raise ArgumentError.new("Unknown directive supplied: #{key}")
    end

    hash[key] = config_val
    hash
  end

  # normalize and tag the report-uri
  if @config[:report_uri]
    @config[:report_uri] = @config[:report_uri].map do |report_uri|
      if report_uri.start_with?('//')
        report_uri = if @ssl_request
                       "https:" + report_uri
                     else
                       "http:" + report_uri
                     end
      end

      if @tag_report_uri
        report_uri = "#{report_uri}?enforce=#{@enforce}"
        report_uri += "&app_name=#{@app_name}" if @app_name
      end
      report_uri
    end
  end

  add_script_hashes if @script_hashes.any?
  strip_unsupported_directives
end

Instance Attribute Details

#ssl_request ⇒ Object (readonly) Also known as: ssl_request?

Returns the value of attribute ssl_request.



75
76
77
# File 'lib/secure_headers/headers/content_security_policy.rb', line 75

def ssl_request
  @ssl_request
end

Class Method Details

.add_to_env(request, controller, config) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/secure_headers/headers/content_security_policy.rb', line 87

def add_to_env(request, controller, config)
  set_nonce(controller)
  options = options_from_request(request).merge(:controller => controller)
  request.env[Constants::ENV_KEY] = {
    :config => config,
    :options => options,
  }
end

.from_json(*json_configs) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/secure_headers/headers/content_security_policy.rb', line 227

def self.from_json(*json_configs)
  json_configs.inject({}) do |combined_config, one_config|
    config = JSON.parse(one_config).inject({}) do |hash, (key, value)|
      hash[key.gsub(/(\w+)-(\w+)/, "\\1_\\2").to_sym] = value
      hash
    end
    combined_config.merge(config) do |_, lhs, rhs|
      lhs | rhs
    end
  end
end

.generate_nonce ⇒ Object



79
80
81
# File 'lib/secure_headers/headers/content_security_policy.rb', line 79

def generate_nonce
  SecureRandom.base64(32).chomp
end

.options_from_request(request) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/secure_headers/headers/content_security_policy.rb', line 96

def options_from_request(request)
  {
    :ssl => request.ssl?,
    :ua => request.env['HTTP_USER_AGENT'],
    :request_uri => request_uri_from_request(request),
  }
end

.request_uri_from_request(request) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/secure_headers/headers/content_security_policy.rb', line 104

def request_uri_from_request(request)
  if request.respond_to?(:original_url)
    # rails 3.1+
    request.original_url
  else
    # rails 2/3.0
    request.url
  end
end

.set_nonce(controller, nonce = generate_nonce) ⇒ Object



83
84
85
# File 'lib/secure_headers/headers/content_security_policy.rb', line 83

def set_nonce(controller, nonce = generate_nonce)
  controller.instance_variable_set(:@content_security_policy_nonce, nonce)
end

.symbol_to_hyphen_case(sym) ⇒ Object



114
115
116
# File 'lib/secure_headers/headers/content_security_policy.rb', line 114

def symbol_to_hyphen_case sym
  sym.to_s.gsub('_', '-')
end

Instance Method Details

#name ⇒ Object

Returns the name to use for the header. Either "Content-Security-Policy" or "Content-Security-Policy-Report-Only"



198
199
200
201
202
203
204
# File 'lib/secure_headers/headers/content_security_policy.rb', line 198

def name
  base = HEADER_NAME
  if !@enforce
    base += "-Report-Only"
  end
  base
end

#nonce ⇒ Object

Return or initialize the nonce value used for this header. If a reference to a controller is passed in the config, this method will check if a nonce has already been set and use it.



191
192
193
# File 'lib/secure_headers/headers/content_security_policy.rb', line 191

def nonce
  @nonce ||= @controller.instance_variable_get(:@content_security_policy_nonce) || self.class.generate_nonce
end

#to_json ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/secure_headers/headers/content_security_policy.rb', line 217

def to_json
  build_value
  @config.inject({}) do |hash, (key, value)|
    if ALL_DIRECTIVES.include?(key)
      hash[key.to_s.gsub(/(\w+)_(\w+)/, "\\1-\\2")] = value
    end
    hash
  end.to_json
end

#value ⇒ Object

Return the value of the CSP header



208
209
210
211
212
213
214
215
# File 'lib/secure_headers/headers/content_security_policy.rb', line 208

def value
  return @config if @config.is_a?(String)
  if @config
    build_value
  else
    DEFAULT_CSP_HEADER
  end
end