Class: SecureHeaders::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/secure_headers/configuration.rb

Defined Under Namespace

Classes: AlreadyConfiguredError, IllegalPolicyModificationError, NotYetConfiguredError

Constant Summary collapse

DEFAULT_CONFIG =
:default
NOOP_OVERRIDE =
"secure_headers_noop_override"
CONFIG_ATTRIBUTES_TO_HEADER_CLASSES =
{
  hsts: StrictTransportSecurity,
  x_frame_options: XFrameOptions,
  x_content_type_options: XContentTypeOptions,
  x_xss_protection: XXssProtection,
  x_download_options: XDownloadOptions,
  x_permitted_cross_domain_policies: XPermittedCrossDomainPolicies,
  referrer_policy: ReferrerPolicy,
  clear_site_data: ClearSiteData,
  expect_certificate_transparency: ExpectCertificateTransparency,
  csp: ContentSecurityPolicy,
  csp_report_only: ContentSecurityPolicy,
  cookies: Cookie,
  reporting_endpoints: ReportingEndpoints,
}.freeze
CONFIG_ATTRIBUTES =
CONFIG_ATTRIBUTES_TO_HEADER_CLASSES.keys.freeze
VALIDATABLE_ATTRIBUTES =

The list of attributes that must respond to a validate_config! method

CONFIG_ATTRIBUTES
HEADERABLE_ATTRIBUTES =

The list of attributes that must respond to a make_header method

(CONFIG_ATTRIBUTES - [:cookies]).freeze
HASH_CONFIG_FILE =
ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&block) ⇒ Configuration

Returns a new instance of Configuration.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/secure_headers/configuration.rb', line 203

def initialize(&block)
  @cookies = self.class.send(:deep_copy_if_hash, Cookie::COOKIE_DEFAULTS)
  @clear_site_data = nil
  @csp = nil
  @csp_report_only = 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
  @reporting_endpoints = nil

  self.referrer_policy = OPT_OUT
  self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT)
  self.csp_report_only = OPT_OUT

  instance_eval(&block) if block_given?
end

Class Method Details

.default(&block) ⇒ Object Also known as: configure

Public: Set the global default configuration.

Optionally supply a block to override the defaults set by this library.

Returns the newly created config. Raises AlreadyConfiguredError if Configuration.disable! has already been called



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/secure_headers/configuration.rb', line 47

def default(&block)
  if disabled?
    raise AlreadyConfiguredError, "Configuration has been disabled, cannot set default"
  end

  if defined?(@default_config)
    raise AlreadyConfiguredError, "Policy already configured"
  end

  # Define a built-in override that clears all configuration options and
  # results in no security headers being set.
  override(NOOP_OVERRIDE, &method(:create_noop_config_block))

  new_config = new(&block).freeze
  new_config.validate_config!
  @default_config = new_config
end

.disable!Object

Public: Disable secure_headers entirely. When disabled, no headers will be set.

Note: This must be called before Configuration.default. Calling it after Configuration.default has been set will raise an AlreadyConfiguredError.

Returns nothing Raises AlreadyConfiguredError if Configuration.default has already been called



19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/secure_headers/configuration.rb', line 19

def disable!
  if defined?(@default_config)
    raise AlreadyConfiguredError, "Configuration already set, cannot disable"
  end

  @disabled = true
  @noop_config = create_noop_config.freeze

  # Ensure the built-in NOOP override is available even if `default` has never been called
  @overrides ||= {}
  unless @overrides.key?(NOOP_OVERRIDE)
    @overrides[NOOP_OVERRIDE] = method(:create_noop_config_block)
  end
end

.disabled?Boolean

Public: Check if secure_headers is disabled

Returns boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/secure_headers/configuration.rb', line 37

def disabled?
  defined?(@disabled) && @disabled
end

.dupObject



101
102
103
# File 'lib/secure_headers/configuration.rb', line 101

def dup
  default_config.dup
end

.named_append(name, &block) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/secure_headers/configuration.rb', line 92

def named_append(name, &block)
  @appends ||= {}
  raise "Provide a configuration block" unless block_given?
  if named_append_or_override_exists?(name)
    raise AlreadyConfiguredError, "Configuration already exists"
  end
  @appends[name] = block
end

.named_appends(name) ⇒ Object



87
88
89
90
# File 'lib/secure_headers/configuration.rb', line 87

def named_appends(name)
  @appends ||= {}
  @appends[name]
end

.override(name, &block) ⇒ Object

Public: create a named configuration that overrides the default config.

name - use an identifier for the override config. base - override another existing config, or override the default config if no value is supplied.

Returns: the newly created config



73
74
75
76
77
78
79
80
# File 'lib/secure_headers/configuration.rb', line 73

def override(name, &block)
  @overrides ||= {}
  raise "Provide a configuration block" unless block_given?
  if named_append_or_override_exists?(name)
    raise AlreadyConfiguredError, "Configuration already exists"
  end
  @overrides[name] = block
end

.overrides(name) ⇒ Object



82
83
84
85
# File 'lib/secure_headers/configuration.rb', line 82

def overrides(name)
  @overrides ||= {}
  @overrides[name]
end

Instance Method Details

#csp=(new_csp) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/secure_headers/configuration.rb', line 293

def csp=(new_csp)
  case new_csp
  when OPT_OUT
    @csp = new_csp
  when ContentSecurityPolicyConfig
    @csp = new_csp
  when Hash
    @csp = ContentSecurityPolicyConfig.new(new_csp)
  else
    raise ArgumentError, "Must provide either an existing CSP config or a CSP config hash"
  end
end

#csp_report_only=(new_csp) ⇒ Object

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.



312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/secure_headers/configuration.rb', line 312

def csp_report_only=(new_csp)
  case new_csp
  when OPT_OUT
    @csp_report_only = new_csp
  when ContentSecurityPolicyReportOnlyConfig
    @csp_report_only = new_csp.dup
  when ContentSecurityPolicyConfig
    @csp_report_only = new_csp.make_report_only
  when Hash
    @csp_report_only = ContentSecurityPolicyReportOnlyConfig.new(new_csp)
  else
    raise ArgumentError, "Must provide either an existing CSP config or a CSP config hash"
  end
end

#dupObject

Public: copy everything

Returns a deep-dup’d copy of this configuration.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/secure_headers/configuration.rb', line 227

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.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.reporting_endpoints = self.class.send(:deep_copy_if_hash, @reporting_endpoints)
  copy
end

#generate_headersObject



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/secure_headers/configuration.rb', line 257

def generate_headers
  headers = {}
  HEADERABLE_ATTRIBUTES.each do |attr|
    klass = CONFIG_ATTRIBUTES_TO_HEADER_CLASSES[attr]
    header_name, value = klass.make_header(instance_variable_get("@#{attr}"))
    if header_name && value
      headers[header_name] = value
    end
  end
  headers
end

#opt_out(header) ⇒ Object



269
270
271
# File 'lib/secure_headers/configuration.rb', line 269

def opt_out(header)
  send("#{header}=", OPT_OUT)
end

#override(name = nil, &block) ⇒ Object

Public: Apply a named override to the current config

Returns self



248
249
250
251
252
253
254
255
# File 'lib/secure_headers/configuration.rb', line 248

def override(name = nil, &block)
  if override = self.class.overrides(name)
    instance_eval(&override)
  else
    raise ArgumentError.new("no override by the name of #{name} has been configured")
  end
  self
end

#secure_cookies=(secure_cookies) ⇒ Object

Raises:

  • (ArgumentError)


289
290
291
# File 'lib/secure_headers/configuration.rb', line 289

def secure_cookies=(secure_cookies)
  raise ArgumentError, "#{Kernel.caller.first}: `#secure_cookies=` is no longer supported. Please use `#cookies=` to configure secure cookies instead."
end

#update_x_frame_options(value) ⇒ Object



273
274
275
# File 'lib/secure_headers/configuration.rb', line 273

def update_x_frame_options(value)
  @x_frame_options = value
end

#validate_config!Object

Public: validates all configurations values.

Raises various configuration errors if any invalid config is detected.

Returns nothing



282
283
284
285
286
287
# File 'lib/secure_headers/configuration.rb', line 282

def validate_config!
  VALIDATABLE_ATTRIBUTES.each do |attr|
    klass = CONFIG_ATTRIBUTES_TO_HEADER_CLASSES[attr]
    klass.validate_config!(instance_variable_get("@#{attr}"))
  end
end