secure_headers 2.5.3 → 3.0.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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