secure_headers 2.2.0 → 3.0.0

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +3 -1
  5. data/CHANGELOG.md +205 -0
  6. data/Gemfile +9 -10
  7. data/Guardfile +12 -0
  8. data/README.md +175 -276
  9. data/Rakefile +2 -36
  10. data/lib/secure_headers/configuration.rb +189 -0
  11. data/lib/secure_headers/headers/content_security_policy.rb +367 -188
  12. data/lib/secure_headers/headers/public_key_pins.rb +43 -57
  13. data/lib/secure_headers/headers/strict_transport_security.rb +21 -47
  14. data/lib/secure_headers/headers/x_content_type_options.rb +19 -32
  15. data/lib/secure_headers/headers/x_download_options.rb +18 -31
  16. data/lib/secure_headers/headers/x_frame_options.rb +24 -32
  17. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -32
  18. data/lib/secure_headers/headers/x_xss_protection.rb +17 -46
  19. data/lib/secure_headers/middleware.rb +15 -0
  20. data/lib/secure_headers/padrino.rb +1 -2
  21. data/lib/secure_headers/railtie.rb +9 -6
  22. data/lib/secure_headers/view_helper.rb +27 -44
  23. data/lib/secure_headers.rb +244 -145
  24. data/secure_headers.gemspec +7 -11
  25. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  26. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +176 -213
  27. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  28. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  29. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +13 -18
  30. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  31. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  32. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  33. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +22 -30
  34. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  35. data/spec/lib/secure_headers_spec.rb +204 -278
  36. data/spec/spec_helper.rb +32 -25
  37. data/upgrading-to-3-0.md +37 -0
  38. metadata +28 -95
  39. data/fixtures/rails_3_2_12/.rspec +0 -1
  40. data/fixtures/rails_3_2_12/Gemfile +0 -6
  41. data/fixtures/rails_3_2_12/README.rdoc +0 -261
  42. data/fixtures/rails_3_2_12/Rakefile +0 -7
  43. data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +0 -4
  44. data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +0 -5
  45. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -5
  46. data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
  47. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -11
  48. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +0 -2
  49. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +0 -1
  50. data/fixtures/rails_3_2_12/config/application.rb +0 -14
  51. data/fixtures/rails_3_2_12/config/boot.rb +0 -6
  52. data/fixtures/rails_3_2_12/config/environment.rb +0 -5
  53. data/fixtures/rails_3_2_12/config/environments/test.rb +0 -37
  54. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +0 -17
  55. data/fixtures/rails_3_2_12/config/routes.rb +0 -4
  56. data/fixtures/rails_3_2_12/config/script_hashes.yml +0 -5
  57. data/fixtures/rails_3_2_12/config.ru +0 -7
  58. data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
  59. data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
  60. data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
  61. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +0 -83
  62. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +0 -54
  63. data/fixtures/rails_3_2_12/spec/spec_helper.rb +0 -15
  64. data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
  65. data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
  66. data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
  67. data/fixtures/rails_3_2_12_no_init/.rspec +0 -1
  68. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -5
  69. data/fixtures/rails_3_2_12_no_init/README.rdoc +0 -261
  70. data/fixtures/rails_3_2_12_no_init/Rakefile +0 -7
  71. data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +0 -4
  72. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -6
  73. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +0 -5
  74. data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
  75. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -12
  76. data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +0 -1
  77. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -0
  78. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -17
  79. data/fixtures/rails_3_2_12_no_init/config/boot.rb +0 -6
  80. data/fixtures/rails_3_2_12_no_init/config/environment.rb +0 -5
  81. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +0 -37
  82. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -4
  83. data/fixtures/rails_3_2_12_no_init/config.ru +0 -4
  84. data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
  85. data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
  86. data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
  87. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +0 -50
  88. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +0 -54
  89. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +0 -5
  90. data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  91. data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  92. data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
  93. data/fixtures/rails_4_1_8/Gemfile +0 -5
  94. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  95. data/fixtures/rails_4_1_8/Rakefile +0 -6
  96. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  97. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  99. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  100. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  101. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  102. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  103. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  104. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  105. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  106. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  107. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  108. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  109. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -17
  110. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  111. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  112. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  113. data/fixtures/rails_4_1_8/config.ru +0 -4
  114. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  115. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  116. data/fixtures/rails_4_1_8/log/.keep +0 -0
  117. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  118. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  119. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  120. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  121. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  122. data/lib/secure_headers/hash_helper.rb +0 -7
  123. data/lib/secure_headers/header.rb +0 -5
  124. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  125. data/lib/secure_headers/version.rb +0 -3
  126. data/lib/tasks/tasks.rake +0 -48
  127. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -47
@@ -1,62 +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
- 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
+
10
10
  class << self
11
- def symbol_to_hyphen_case sym
12
- 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]
13
18
  end
14
- end
15
- include Constants
16
19
 
17
- def initialize(config=nil)
18
- @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
19
34
 
20
- @pins = @config.fetch(:pins, nil)
21
- @report_uri = @config.fetch(:report_uri, nil)
22
- @app_name = @config.fetch(:app_name, nil)
23
- @enforce = !!@config.fetch(:enforce, nil)
24
- @include_subdomains = !!@config.fetch(:include_subdomains, nil)
25
- @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)
26
41
  end
27
42
 
28
43
  def name
29
- base = HPKP_HEADER_NAME
30
- if !@enforce
31
- base += "-Report-Only"
44
+ if @report_only
45
+ REPORT_ONLY
46
+ else
47
+ HEADER_NAME
32
48
  end
33
- base
34
49
  end
35
50
 
36
51
  def value
37
52
  header_value = [
38
- generic_directives,
53
+ max_age_directive,
39
54
  pin_directives,
40
55
  report_uri_directive,
41
56
  subdomain_directive
42
57
  ].compact.join('; ').strip
43
58
  end
44
59
 
45
- def validate_config(config)
46
- raise PublicKeyPinsBuildError.new("config must be a hash.") unless config.is_a? Hash
47
-
48
- if !config[:max_age]
49
- raise PublicKeyPinsBuildError.new("max-age is a required directive.")
50
- elsif config[:max_age].to_s !~ /\A\d+\z/
51
- raise PublicKeyPinsBuildError.new("max-age must be a number.
52
- #{config[:max_age]} was supplied.")
53
- elsif config[:pins] && config[:pins].length < 2
54
- raise PublicKeyPinsBuildError.new("A minimum of 2 pins are required.")
55
- end
56
-
57
- config
58
- end
59
-
60
60
  def pin_directives
61
61
  return nil if @pins.nil?
62
62
  @pins.collect do |pin|
@@ -66,28 +66,14 @@ module SecureHeaders
66
66
  end.join('; ')
67
67
  end
68
68
 
69
- def generic_directives
70
- DIRECTIVES.collect do |directive_name|
71
- build_directive(directive_name) if @config[directive_name]
72
- end.join('; ')
73
- end
74
-
75
- def build_directive(key)
76
- "#{self.class.symbol_to_hyphen_case(key)}=#{@config[key]}"
69
+ def max_age_directive
70
+ "max-age=#{@max_age}" if @max_age
77
71
  end
78
72
 
79
73
  def report_uri_directive
80
- return nil if @report_uri.nil?
81
-
82
- if @tag_report_uri
83
- @report_uri = "#{@report_uri}?enforce=#{@enforce}"
84
- @report_uri += "&app_name=#{@app_name}" if @app_name
85
- end
86
-
87
- "report-uri=\"#{@report_uri}\""
74
+ "report-uri=\"#{@report_uri}\"" if @report_uri
88
75
  end
89
76
 
90
-
91
77
  def subdomain_directive
92
78
  @include_subdomains ? 'includeSubDomains' : nil
93
79
  end
@@ -1,53 +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
- end
12
- include Constants
13
-
14
- def initialize(config = nil)
15
- @config = config
16
- validate_config unless @config.nil?
17
- end
18
-
19
- def name
20
- return HSTS_HEADER_NAME
21
- end
22
-
23
- def value
24
- case @config
25
- when String
26
- return @config
27
- when NilClass
28
- 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]
29
19
  end
30
20
 
31
- max_age = @config.fetch(:max_age, HSTS_MAX_AGE)
32
- value = "max-age=" + max_age.to_s
33
- value += "; includeSubdomains" if @config[:include_subdomains]
34
- value += "; preload" if @config[:preload]
35
-
36
- value
37
- end
38
-
39
- private
40
-
41
- def validate_config
42
- if @config.is_a? Hash
43
- if !@config[:max_age]
44
- raise STSBuildError.new("No max-age was supplied.")
45
- elsif @config[:max_age].to_s !~ /\A\d+\z/
46
- raise STSBuildError.new("max-age must be a number. #{@config[:max_age]} was supplied.")
47
- end
48
- else
49
- @config = @config.to_s
50
- 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
51
25
  end
52
26
  end
53
27
  end
@@ -1,40 +1,27 @@
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
- end
9
- include Constants
10
-
11
- def initialize(config=nil)
12
- @config = config
13
- validate_config unless @config.nil?
14
- end
4
+ class XContentTypeOptions
5
+ HEADER_NAME = "X-Content-Type-Options"
6
+ DEFAULT_VALUE = "nosniff"
7
+ CONFIG_KEY = :x_content_type_options
15
8
 
16
- def name
17
- X_CONTENT_TYPE_OPTIONS_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
- @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]
28
16
  end
29
- end
30
-
31
- private
32
17
 
33
- def validate_config
34
- value = @config.is_a?(Hash) ? @config[:value] : @config
35
- unless value.casecmp(DEFAULT_VALUE) == 0
36
- 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
37
24
  end
38
25
  end
39
26
  end
40
- end
27
+ end
@@ -1,38 +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
- end
8
- include Constants
9
-
10
- def initialize(config = nil)
11
- @config = config
12
- validate_config unless @config.nil?
13
- 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
14
7
 
15
- def name
16
- XDO_HEADER_NAME
17
- end
18
-
19
- def value
20
- case @config
21
- when NilClass
22
- DEFAULT_VALUE
23
- when String
24
- @config
25
- else
26
- @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]
27
15
  end
28
- end
29
-
30
- private
31
16
 
32
- def validate_config
33
- value = @config.is_a?(Hash) ? @config[:value] : @config
34
- unless value.casecmp(DEFAULT_VALUE) == 0
35
- 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
36
23
  end
37
24
  end
38
25
  end
@@ -1,39 +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
- end
9
- include Constants
10
-
11
- def initialize(config = nil)
12
- @config = config
13
- validate_config unless @config.nil?
14
- 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
15
12
 
16
- def name
17
- XFO_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
- @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]
28
21
  end
29
- end
30
-
31
- private
32
22
 
33
- def validate_config
34
- value = @config.is_a?(Hash) ? @config[:value] : @config
35
- unless value =~ VALID_XFO_HEADER
36
- 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
37
29
  end
38
30
  end
39
31
  end
@@ -1,39 +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
- end
9
- include Constants
10
-
11
- def initialize(config = nil)
12
- @config = config
13
- validate_config unless @config.nil?
14
- 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
15
8
 
16
- def name
17
- XPCDP_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
- @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]
28
16
  end
29
- end
30
-
31
- private
32
17
 
33
- def validate_config
34
- value = @config.is_a?(Hash) ? @config[:value] : @config
35
- unless VALID_POLICIES.include?(value.downcase)
36
- 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
37
24
  end
38
25
  end
39
26
  end
@@ -1,53 +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
- end
9
- include Constants
10
-
11
- def initialize(config=nil)
12
- @config = config
13
- validate_config unless @config.nil?
14
- end
15
-
16
- def name
17
- X_XSS_PROTECTION_HEADER_NAME
18
- 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
19
8
 
20
- def value
21
- case @config
22
- when NilClass
23
- DEFAULT_VALUE
24
- when String
25
- @config
26
- else
27
- value = @config[:value].to_s
28
- value += "; mode=#{@config[:mode]}" if @config[:mode]
29
- value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
30
- 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]
31
16
  end
32
- end
33
-
34
- private
35
-
36
- def validate_config
37
- if @config.is_a? Hash
38
- if !@config[:value]
39
- raise XXssProtectionBuildError.new(":value key is missing")
40
- elsif @config[:value]
41
- unless [0,1].include?(@config[:value].to_i)
42
- raise XXssProtectionBuildError.new(":value must be 1 or 0")
43
- end
44
17
 
45
- if @config[:mode] && @config[:mode].casecmp('block') != 0
46
- raise XXssProtectionBuildError.new(":mode must nil or 'block'")
47
- end
48
- end
49
- elsif @config.is_a? String
50
- 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
51
22
  end
52
23
  end
53
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
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
32
+ include SecureHeaders
30
33
  end
31
34
  end
32
35
  end