secure_headers 2.5.3 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +2 -1
  4. data/Gemfile +9 -16
  5. data/README.md +154 -331
  6. data/Rakefile +2 -36
  7. data/lib/secure_headers/configuration.rb +189 -0
  8. data/lib/secure_headers/headers/content_security_policy.rb +341 -254
  9. data/lib/secure_headers/headers/public_key_pins.rb +43 -58
  10. data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
  11. data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
  12. data/lib/secure_headers/headers/x_download_options.rb +18 -33
  13. data/lib/secure_headers/headers/x_frame_options.rb +24 -34
  14. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
  15. data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
  16. data/lib/secure_headers/middleware.rb +15 -0
  17. data/lib/secure_headers/padrino.rb +1 -2
  18. data/lib/secure_headers/railtie.rb +9 -6
  19. data/lib/secure_headers/view_helper.rb +27 -43
  20. data/lib/secure_headers.rb +254 -61
  21. data/secure_headers.gemspec +7 -12
  22. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  23. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
  24. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  25. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  26. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
  27. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  28. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  29. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  30. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
  31. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  32. data/spec/lib/secure_headers_spec.rb +201 -339
  33. data/spec/spec_helper.rb +30 -30
  34. data/upgrading-to-3-0.md +35 -0
  35. metadata +14 -100
  36. data/fixtures/rails_3_2_22/.rspec +0 -1
  37. data/fixtures/rails_3_2_22/Gemfile +0 -6
  38. data/fixtures/rails_3_2_22/README.rdoc +0 -261
  39. data/fixtures/rails_3_2_22/Rakefile +0 -7
  40. data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
  41. data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
  42. data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
  43. data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
  44. data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
  45. data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
  46. data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
  47. data/fixtures/rails_3_2_22/config/application.rb +0 -14
  48. data/fixtures/rails_3_2_22/config/boot.rb +0 -6
  49. data/fixtures/rails_3_2_22/config/environment.rb +0 -5
  50. data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
  51. data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
  52. data/fixtures/rails_3_2_22/config/routes.rb +0 -4
  53. data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
  54. data/fixtures/rails_3_2_22/config.ru +0 -7
  55. data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
  56. data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
  57. data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
  58. data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
  59. data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
  60. data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
  61. data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
  62. data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
  63. data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
  64. data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
  65. data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
  66. data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
  67. data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
  68. data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
  69. data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
  70. data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
  71. data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
  72. data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
  73. data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
  74. data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
  75. data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
  76. data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
  77. data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
  78. data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
  79. data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
  80. data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
  81. data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
  82. data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
  83. data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
  84. data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
  85. data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
  86. data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
  87. data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  88. data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  89. data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
  90. data/fixtures/rails_4_1_8/Gemfile +0 -5
  91. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  92. data/fixtures/rails_4_1_8/Rakefile +0 -6
  93. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  94. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  95. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  96. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  97. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  99. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  100. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  101. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  102. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  103. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  104. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  105. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  106. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
  107. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  108. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  109. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  110. data/fixtures/rails_4_1_8/config.ru +0 -4
  111. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  112. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  113. data/fixtures/rails_4_1_8/log/.keep +0 -0
  114. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  115. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  116. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  117. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  118. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  119. data/lib/secure_headers/controller_extension.rb +0 -158
  120. data/lib/secure_headers/hash_helper.rb +0 -7
  121. data/lib/secure_headers/header.rb +0 -5
  122. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  123. data/lib/secure_headers/version.rb +0 -3
  124. data/lib/tasks/tasks.rake +0 -48
  125. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
@@ -1,63 +1,62 @@
1
1
  module SecureHeaders
2
- class PublicKeyPinsBuildError < StandardError; end
3
- class PublicKeyPins < Header
4
- module Constants
5
- HPKP_HEADER_NAME = "Public-Key-Pins"
6
- ENV_KEY = 'secure_headers.public_key_pins'
7
- HASH_ALGORITHMS = [:sha256]
8
- DIRECTIVES = [:max_age]
9
- CONFIG_KEY = :hpkp
10
- end
2
+ class PublicKeyPinsConfigError < StandardError; end
3
+ class PublicKeyPins
4
+ HEADER_NAME = "Public-Key-Pins".freeze
5
+ REPORT_ONLY = "Public-Key-Pins-Report-Only".freeze
6
+ HASH_ALGORITHMS = [:sha256].freeze
7
+ CONFIG_KEY = :hpkp
8
+
9
+
11
10
  class << self
12
- def symbol_to_hyphen_case sym
13
- sym.to_s.gsub('_', '-')
11
+ # Public: make an hpkp header name, value pair
12
+ #
13
+ # Returns nil if not configured, returns header name and value if configured.
14
+ def make_header(config)
15
+ return if config.nil?
16
+ header = new(config)
17
+ [header.name, header.value]
14
18
  end
15
- end
16
- include Constants
17
19
 
18
- def initialize(config=nil)
19
- @config = validate_config(config)
20
+ def validate_config!(config)
21
+ return if config.nil? || config == OPT_OUT
22
+ raise PublicKeyPinsConfigError.new("config must be a hash.") unless config.is_a? Hash
23
+
24
+ if !config[:max_age]
25
+ raise PublicKeyPinsConfigError.new("max-age is a required directive.")
26
+ elsif config[:max_age].to_s !~ /\A\d+\z/
27
+ raise PublicKeyPinsConfigError.new("max-age must be a number.
28
+ #{config[:max_age]} was supplied.")
29
+ elsif config[:pins] && config[:pins].length < 2
30
+ raise PublicKeyPinsConfigError.new("A minimum of 2 pins are required.")
31
+ end
32
+ end
33
+ end
20
34
 
21
- @pins = @config.fetch(:pins, nil)
22
- @report_uri = @config.fetch(:report_uri, nil)
23
- @app_name = @config.fetch(:app_name, nil)
24
- @enforce = !!@config.fetch(:enforce, nil)
25
- @include_subdomains = !!@config.fetch(:include_subdomains, nil)
26
- @tag_report_uri = !!@config.fetch(:tag_report_uri, nil)
35
+ def initialize(config)
36
+ @max_age = config.fetch(:max_age, nil)
37
+ @pins = config.fetch(:pins, nil)
38
+ @report_uri = config.fetch(:report_uri, nil)
39
+ @report_only = !!config.fetch(:report_only, nil)
40
+ @include_subdomains = !!config.fetch(:include_subdomains, nil)
27
41
  end
28
42
 
29
43
  def name
30
- base = HPKP_HEADER_NAME
31
- if !@enforce
32
- base += "-Report-Only"
44
+ if @report_only
45
+ REPORT_ONLY
46
+ else
47
+ HEADER_NAME
33
48
  end
34
- base
35
49
  end
36
50
 
37
51
  def value
38
52
  header_value = [
39
- generic_directives,
53
+ max_age_directive,
40
54
  pin_directives,
41
55
  report_uri_directive,
42
56
  subdomain_directive
43
57
  ].compact.join('; ').strip
44
58
  end
45
59
 
46
- def validate_config(config)
47
- raise PublicKeyPinsBuildError.new("config must be a hash.") unless config.is_a? Hash
48
-
49
- if !config[:max_age]
50
- raise PublicKeyPinsBuildError.new("max-age is a required directive.")
51
- elsif config[:max_age].to_s !~ /\A\d+\z/
52
- raise PublicKeyPinsBuildError.new("max-age must be a number.
53
- #{config[:max_age]} was supplied.")
54
- elsif config[:pins] && config[:pins].length < 2
55
- raise PublicKeyPinsBuildError.new("A minimum of 2 pins are required.")
56
- end
57
-
58
- config
59
- end
60
-
61
60
  def pin_directives
62
61
  return nil if @pins.nil?
63
62
  @pins.collect do |pin|
@@ -67,28 +66,14 @@ module SecureHeaders
67
66
  end.join('; ')
68
67
  end
69
68
 
70
- def generic_directives
71
- DIRECTIVES.collect do |directive_name|
72
- build_directive(directive_name) if @config[directive_name]
73
- end.join('; ')
74
- end
75
-
76
- def build_directive(key)
77
- "#{self.class.symbol_to_hyphen_case(key)}=#{@config[key]}"
69
+ def max_age_directive
70
+ "max-age=#{@max_age}" if @max_age
78
71
  end
79
72
 
80
73
  def report_uri_directive
81
- return nil if @report_uri.nil?
82
-
83
- if @tag_report_uri
84
- @report_uri = "#{@report_uri}?enforce=#{@enforce}"
85
- @report_uri += "&app_name=#{@app_name}" if @app_name
86
- end
87
-
88
- "report-uri=\"#{@report_uri}\""
74
+ "report-uri=\"#{@report_uri}\"" if @report_uri
89
75
  end
90
76
 
91
-
92
77
  def subdomain_directive
93
78
  @include_subdomains ? 'includeSubDomains' : nil
94
79
  end
@@ -1,55 +1,27 @@
1
1
  module SecureHeaders
2
- class STSBuildError < StandardError; end
3
-
4
- class StrictTransportSecurity < Header
5
- module Constants
6
- HSTS_HEADER_NAME = 'Strict-Transport-Security'
7
- HSTS_MAX_AGE = "631138519"
8
- DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
9
- VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
10
- MESSAGE = "The config value supplied for the HSTS header was invalid."
11
- CONFIG_KEY = :hsts
12
- end
13
- include Constants
14
-
15
- def initialize(config = nil)
16
- @config = config
17
- validate_config unless @config.nil?
18
- end
19
-
20
- def name
21
- return HSTS_HEADER_NAME
22
- end
23
-
24
- def value
25
- case @config
26
- when String
27
- return @config
28
- when NilClass
29
- return DEFAULT_VALUE
2
+ class STSConfigError < StandardError; end
3
+
4
+ class StrictTransportSecurity
5
+ HEADER_NAME = 'Strict-Transport-Security'
6
+ HSTS_MAX_AGE = "631138519"
7
+ DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
8
+ VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
9
+ MESSAGE = "The config value supplied for the HSTS header was invalid. Must match #{VALID_STS_HEADER}"
10
+ CONFIG_KEY = :hsts
11
+
12
+ class << self
13
+ # Public: generate an hsts header name, value pair.
14
+ #
15
+ # Returns a default header if no configuration is provided, or a
16
+ # header name and value based on the config.
17
+ def make_header(config = nil)
18
+ [HEADER_NAME, config || DEFAULT_VALUE]
30
19
  end
31
20
 
32
- max_age = @config.fetch(:max_age, HSTS_MAX_AGE)
33
- value = "max-age=" + max_age.to_s
34
- value += "; includeSubdomains" if @config[:include_subdomains]
35
- value += "; preload" if @config[:preload]
36
-
37
- value
38
- end
39
-
40
- private
41
-
42
- def validate_config
43
- if @config.is_a? Hash
44
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for StrictTransportSecurity config"
45
- if !@config[:max_age]
46
- raise STSBuildError.new("No max-age was supplied.")
47
- elsif @config[:max_age].to_s !~ /\A\d+\z/
48
- raise STSBuildError.new("max-age must be a number. #{@config[:max_age]} was supplied.")
49
- end
50
- else
51
- @config = @config.to_s
52
- raise STSBuildError.new(MESSAGE) unless @config =~ VALID_STS_HEADER
21
+ def validate_config!(config)
22
+ return if config.nil? || config == OPT_OUT
23
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config} #{config.class}") unless config.is_a?(String)
24
+ raise STSConfigError.new(MESSAGE) unless config =~ VALID_STS_HEADER
53
25
  end
54
26
  end
55
27
  end
@@ -1,41 +1,26 @@
1
1
  module SecureHeaders
2
- class XContentTypeOptionsBuildError < StandardError; end
2
+ class XContentTypeOptionsConfigError < StandardError; end
3
3
  # IE only
4
- class XContentTypeOptions < Header
5
- module Constants
6
- X_CONTENT_TYPE_OPTIONS_HEADER_NAME = "X-Content-Type-Options"
7
- DEFAULT_VALUE = "nosniff"
8
- CONFIG_KEY = :x_content_type_options
9
- end
10
- include Constants
11
-
12
- def initialize(config=nil)
13
- @config = config
14
- validate_config unless @config.nil?
15
- end
4
+ class XContentTypeOptions
5
+ HEADER_NAME = "X-Content-Type-Options"
6
+ DEFAULT_VALUE = "nosniff"
7
+ CONFIG_KEY = :x_content_type_options
16
8
 
17
- def name
18
- X_CONTENT_TYPE_OPTIONS_HEADER_NAME
19
- end
20
-
21
- def value
22
- case @config
23
- when NilClass
24
- DEFAULT_VALUE
25
- when String
26
- @config
27
- else
28
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XContentTypeOptions config"
29
- @config[:value]
9
+ class << self
10
+ # Public: generate an X-Content-Type-Options header.
11
+ #
12
+ # Returns a default header if no configuration is provided, or a
13
+ # header name and value based on the config.
14
+ def make_header(config = nil)
15
+ [HEADER_NAME, config || DEFAULT_VALUE]
30
16
  end
31
- end
32
-
33
- private
34
17
 
35
- def validate_config
36
- value = @config.is_a?(Hash) ? @config[:value] : @config
37
- unless value.casecmp(DEFAULT_VALUE) == 0
38
- raise XContentTypeOptionsBuildError.new("Value can only be nil or 'nosniff'")
18
+ def validate_config!(config)
19
+ return if config.nil? || config == OPT_OUT
20
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
21
+ unless config.casecmp(DEFAULT_VALUE) == 0
22
+ raise XContentTypeOptionsConfigError.new("Value can only be nil or 'nosniff'")
23
+ end
39
24
  end
40
25
  end
41
26
  end
@@ -1,40 +1,25 @@
1
1
  module SecureHeaders
2
- class XDOBuildError < StandardError; end
3
- class XDownloadOptions < Header
4
- module Constants
5
- XDO_HEADER_NAME = "X-Download-Options"
6
- DEFAULT_VALUE = 'noopen'
7
- CONFIG_KEY = :x_download_options
8
- end
9
- include Constants
10
-
11
- def initialize(config = nil)
12
- @config = config
13
- validate_config unless @config.nil?
14
- end
2
+ class XDOConfigError < StandardError; end
3
+ class XDownloadOptions
4
+ HEADER_NAME = "X-Download-Options"
5
+ DEFAULT_VALUE = 'noopen'
6
+ CONFIG_KEY = :x_download_options
15
7
 
16
- def name
17
- XDO_HEADER_NAME
18
- end
19
-
20
- def value
21
- case @config
22
- when NilClass
23
- DEFAULT_VALUE
24
- when String
25
- @config
26
- else
27
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XDownloadOptions config"
28
- @config[:value]
8
+ class << self
9
+ # Public: generate an X-Download-Options header.
10
+ #
11
+ # Returns a default header if no configuration is provided, or a
12
+ # header name and value based on the config.
13
+ def make_header(config = nil)
14
+ [HEADER_NAME, config || DEFAULT_VALUE]
29
15
  end
30
- end
31
-
32
- private
33
16
 
34
- def validate_config
35
- value = @config.is_a?(Hash) ? @config[:value] : @config
36
- unless value.casecmp(DEFAULT_VALUE) == 0
37
- raise XDOBuildError.new("Value can only be nil or 'noopen'")
17
+ def validate_config!(config)
18
+ return if config.nil? || config == OPT_OUT
19
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
20
+ unless config.casecmp(DEFAULT_VALUE) == 0
21
+ raise XDOConfigError.new("Value can only be nil or 'noopen'")
22
+ end
38
23
  end
39
24
  end
40
25
  end
@@ -1,41 +1,31 @@
1
1
  module SecureHeaders
2
- class XFOBuildError < StandardError; end
3
- class XFrameOptions < Header
4
- module Constants
5
- XFO_HEADER_NAME = "X-Frame-Options"
6
- DEFAULT_VALUE = 'SAMEORIGIN'
7
- VALID_XFO_HEADER = /\A(SAMEORIGIN\z|DENY\z|ALLOW-FROM[:\s])/i
8
- CONFIG_KEY = :x_frame_options
9
- end
10
- include Constants
11
-
12
- def initialize(config = nil)
13
- @config = config
14
- validate_config unless @config.nil?
15
- end
2
+ class XFOConfigError < StandardError; end
3
+ class XFrameOptions
4
+ HEADER_NAME = "X-Frame-Options"
5
+ CONFIG_KEY = :x_frame_options
6
+ SAMEORIGIN = "sameorigin"
7
+ DENY = "deny"
8
+ ALLOW_FROM = "allow-from"
9
+ ALLOW_ALL = "allowall"
10
+ DEFAULT_VALUE = SAMEORIGIN
11
+ VALID_XFO_HEADER = /\A(#{SAMEORIGIN}\z|#{DENY}\z|#{ALLOW_ALL}\z|#{ALLOW_FROM}[:\s])/i
16
12
 
17
- def name
18
- XFO_HEADER_NAME
19
- end
20
-
21
- def value
22
- case @config
23
- when NilClass
24
- DEFAULT_VALUE
25
- when String
26
- @config
27
- else
28
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XFrameOptions config"
29
- @config[:value]
13
+ class << self
14
+ # Public: generate an X-Frame-Options header.
15
+ #
16
+ # Returns a default header if no configuration is provided, or a
17
+ # header name and value based on the config.
18
+ def make_header(config = nil)
19
+ return if config == OPT_OUT
20
+ [HEADER_NAME, config || DEFAULT_VALUE]
30
21
  end
31
- end
32
-
33
- private
34
22
 
35
- def validate_config
36
- value = @config.is_a?(Hash) ? @config[:value] : @config
37
- unless value =~ VALID_XFO_HEADER
38
- raise XFOBuildError.new("Value must be SAMEORIGIN|DENY|ALLOW-FROM:")
23
+ def validate_config!(config)
24
+ return if config.nil? || config == OPT_OUT
25
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
26
+ unless config =~ VALID_XFO_HEADER
27
+ raise XFOConfigError.new("Value must be SAMEORIGIN|DENY|ALLOW-FROM:|ALLOWALL")
28
+ end
39
29
  end
40
30
  end
41
31
  end
@@ -1,41 +1,26 @@
1
1
  module SecureHeaders
2
- class XPCDPBuildError < StandardError; end
3
- class XPermittedCrossDomainPolicies < Header
4
- module Constants
5
- XPCDP_HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
6
- DEFAULT_VALUE = 'none'
7
- VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
8
- CONFIG_KEY = :x_permitted_cross_domain_policies
9
- end
10
- include Constants
11
-
12
- def initialize(config = nil)
13
- @config = config
14
- validate_config unless @config.nil?
15
- end
2
+ class XPCDPConfigError < StandardError; end
3
+ class XPermittedCrossDomainPolicies
4
+ HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
5
+ DEFAULT_VALUE = 'none'
6
+ VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
7
+ CONFIG_KEY = :x_permitted_cross_domain_policies
16
8
 
17
- def name
18
- XPCDP_HEADER_NAME
19
- end
20
-
21
- def value
22
- case @config
23
- when NilClass
24
- DEFAULT_VALUE
25
- when String
26
- @config
27
- else
28
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XPermittedCrossDomainPolicies config"
29
- @config[:value]
9
+ class << self
10
+ # Public: generate an X-Permitted-Cross-Domain-Policies header.
11
+ #
12
+ # Returns a default header if no configuration is provided, or a
13
+ # header name and value based on the config.
14
+ def make_header(config = nil)
15
+ [HEADER_NAME, config || DEFAULT_VALUE]
30
16
  end
31
- end
32
-
33
- private
34
17
 
35
- def validate_config
36
- value = @config.is_a?(Hash) ? @config[:value] : @config
37
- unless VALID_POLICIES.include?(value.downcase)
38
- raise XPCDPBuildError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
18
+ def validate_config!(config)
19
+ return if config.nil? || config == OPT_OUT
20
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
21
+ unless VALID_POLICIES.include?(config.downcase)
22
+ raise XPCDPConfigError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
23
+ end
39
24
  end
40
25
  end
41
26
  end
@@ -1,55 +1,24 @@
1
1
  module SecureHeaders
2
- class XXssProtectionBuildError < StandardError; end
3
- class XXssProtection < Header
4
- module Constants
5
- X_XSS_PROTECTION_HEADER_NAME = 'X-XSS-Protection'
6
- DEFAULT_VALUE = "1"
7
- VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
8
- CONFIG_KEY = :x_xss_protection
9
- end
10
- include Constants
11
-
12
- def initialize(config=nil)
13
- @config = config
14
- validate_config unless @config.nil?
15
- end
16
-
17
- def name
18
- X_XSS_PROTECTION_HEADER_NAME
19
- end
2
+ class XXssProtectionConfigError < StandardError; end
3
+ class XXssProtection
4
+ HEADER_NAME = 'X-XSS-Protection'
5
+ DEFAULT_VALUE = "1; mode=block"
6
+ VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
7
+ CONFIG_KEY = :x_xss_protection
20
8
 
21
- def value
22
- case @config
23
- when NilClass
24
- DEFAULT_VALUE
25
- when String
26
- @config
27
- else
28
- warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XXssProtection config"
29
- value = @config[:value].to_s
30
- value += "; mode=#{@config[:mode]}" if @config[:mode]
31
- value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
32
- value
9
+ class << self
10
+ # Public: generate an X-Xss-Protection header.
11
+ #
12
+ # Returns a default header if no configuration is provided, or a
13
+ # header name and value based on the config.
14
+ def make_header(config = nil)
15
+ [HEADER_NAME, config || DEFAULT_VALUE]
33
16
  end
34
- end
35
-
36
- private
37
-
38
- def validate_config
39
- if @config.is_a? Hash
40
- if !@config[:value]
41
- raise XXssProtectionBuildError.new(":value key is missing")
42
- elsif @config[:value]
43
- unless [0,1].include?(@config[:value].to_i)
44
- raise XXssProtectionBuildError.new(":value must be 1 or 0")
45
- end
46
17
 
47
- if @config[:mode] && @config[:mode].casecmp('block') != 0
48
- raise XXssProtectionBuildError.new(":mode must nil or 'block'")
49
- end
50
- end
51
- elsif @config.is_a? String
52
- raise XXssProtectionBuildError.new("Invalid format (see VALID_X_XSS_HEADER)") unless @config =~ VALID_X_XSS_HEADER
18
+ def validate_config!(config)
19
+ return if config.nil? || config == OPT_OUT
20
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
21
+ raise XXssProtectionConfigError.new("Invalid format (see VALID_X_XSS_HEADER)") unless config.to_s =~ VALID_X_XSS_HEADER
53
22
  end
54
23
  end
55
24
  end
@@ -0,0 +1,15 @@
1
+ module SecureHeaders
2
+ class Middleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ # merges the hash of headers into the current header set.
8
+ def call(env)
9
+ req = Rack::Request.new(env)
10
+ status, headers, response = @app.call(env)
11
+ headers.merge!(SecureHeaders.header_hash_for(req))
12
+ [status, headers, response]
13
+ end
14
+ end
15
+ end
@@ -5,10 +5,9 @@ module SecureHeaders
5
5
  # Main class that register this extension.
6
6
  #
7
7
  def registered(app)
8
- app.extend SecureHeaders::ClassMethods
9
8
  app.helpers SecureHeaders::InstanceMethods
10
9
  end
11
- alias :included :registered
10
+ alias_method :included, :registered
12
11
  end
13
12
  end
14
13
  end
@@ -1,24 +1,27 @@
1
1
  # rails 3.1+
2
2
  if defined?(Rails::Railtie)
3
3
  module SecureHeaders
4
- class Railtie < Rails::Engine
5
- isolate_namespace ::SecureHeaders if defined? isolate_namespace # rails 3.0
4
+ class Railtie < Rails::Railtie
5
+ isolate_namespace SecureHeaders if defined? isolate_namespace # rails 3.0
6
6
  conflicting_headers = ['X-Frame-Options', 'X-XSS-Protection', 'X-Content-Type-Options',
7
7
  'X-Permitted-Cross-Domain-Policies', 'X-Download-Options',
8
8
  'X-Content-Type-Options', 'Strict-Transport-Security',
9
9
  'Content-Security-Policy', 'Content-Security-Policy-Report-Only',
10
- 'X-Permitted-Cross-Domain-Policies','Public-Key-Pins','Public-Key-Pins-Report-Only']
10
+ 'X-Permitted-Cross-Domain-Policies', 'Public-Key-Pins', 'Public-Key-Pins-Report-Only']
11
+
12
+ initializer "secure_headers.middleware" do
13
+ Rails.application.config.middleware.use SecureHeaders::Middleware
14
+ end
11
15
 
12
16
  initializer "secure_headers.action_controller" do
13
17
  ActiveSupport.on_load(:action_controller) do
14
- include ::SecureHeaders::ControllerExtension
18
+ include SecureHeaders
15
19
 
16
20
  unless Rails.application.config.action_dispatch.default_headers.nil?
17
21
  conflicting_headers.each do |header|
18
22
  Rails.application.config.action_dispatch.default_headers.delete(header)
19
23
  end
20
24
  end
21
-
22
25
  end
23
26
  end
24
27
  end
@@ -26,7 +29,7 @@ if defined?(Rails::Railtie)
26
29
  else
27
30
  module ActionController
28
31
  class Base
29
- include ::SecureHeaders::ControllerExtension
32
+ include SecureHeaders
30
33
  end
31
34
  end
32
35
  end