actionpack 3.2.22.5 → 5.2.4

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

Potentially problematic release.


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

Files changed (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,27 +1,58 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'action_controller/metal/exceptions'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/session/abstract/id"
4
+ require "action_controller/metal/exceptions"
5
+ require "active_support/security_utils"
6
+ require "active_support/core_ext/string/strip"
3
7
 
4
8
  module ActionController #:nodoc:
5
9
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
6
10
  end
7
11
 
12
+ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
13
+ end
14
+
8
15
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
9
- # by including a token in the rendered html for your application. This token is
16
+ # by including a token in the rendered HTML for your application. This token is
10
17
  # stored as a random string in the session, to which an attacker does not have
11
18
  # access. When a request reaches your application, \Rails verifies the received
12
- # token with the token in the session. Only HTML and JavaScript requests are checked,
13
- # so this will not protect your XML API (presumably you'll have a different
14
- # authentication scheme there anyway). Also, GET requests are not protected as these
15
- # should be idempotent.
19
+ # token with the token in the session. All requests are checked except GET requests
20
+ # as these should be idempotent. Keep in mind that all session-oriented requests
21
+ # should be CSRF protected, including JavaScript and HTML requests.
22
+ #
23
+ # Since HTML and JavaScript requests are typically made from the browser, we
24
+ # need to ensure to verify request authenticity for the web browser. We can
25
+ # use session-oriented authentication for these types of requests, by using
26
+ # the <tt>protect_from_forgery</tt> method in our controllers.
27
+ #
28
+ # GET requests are not protected since they don't have side effects like writing
29
+ # to the database and don't leak sensitive information. JavaScript requests are
30
+ # an exception: a third-party site can use a <script> tag to reference a JavaScript
31
+ # URL on your site. When your JavaScript response loads on their site, it executes.
32
+ # With carefully crafted JavaScript on their end, sensitive data in your JavaScript
33
+ # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
34
+ # Ajax) requests are allowed to make GET requests for JavaScript responses.
35
+ #
36
+ # It's important to remember that XML or JSON requests are also affected and if
37
+ # you're building an API you should change forgery protection method in
38
+ # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
39
+ #
40
+ # class ApplicationController < ActionController::Base
41
+ # protect_from_forgery unless: -> { request.format.json? }
42
+ # end
16
43
  #
17
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
18
- # which checks the token and resets the session if it doesn't match what was expected.
19
- # A call to this method is generated for new \Rails applications by default.
20
- # You can customize the error message by editing public/422.html.
44
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
45
+ # By default <tt>protect_from_forgery</tt> protects your session with
46
+ # <tt>:null_session</tt> method, which provides an empty session
47
+ # during request.
48
+ #
49
+ # We may want to disable CSRF protection for APIs since they are typically
50
+ # designed to be state-less. That is, the request API client will handle
51
+ # the session for you instead of Rails.
21
52
  #
22
53
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
23
54
  # value of this token must be added to every layout that renders forms by including
24
- # <tt>csrf_meta_tags</tt> in the html +head+.
55
+ # <tt>csrf_meta_tags</tt> in the HTML +head+.
25
56
  #
26
57
  # Learn more about CSRF attacks and securing your application in the
27
58
  # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
@@ -37,77 +68,378 @@ module ActionController #:nodoc:
37
68
  config_accessor :request_forgery_protection_token
38
69
  self.request_forgery_protection_token ||= :authenticity_token
39
70
 
71
+ # Holds the class which implements the request forgery protection.
72
+ config_accessor :forgery_protection_strategy
73
+ self.forgery_protection_strategy = nil
74
+
40
75
  # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
41
76
  config_accessor :allow_forgery_protection
42
77
  self.allow_forgery_protection = true if allow_forgery_protection.nil?
43
78
 
79
+ # Controls whether a CSRF failure logs a warning. On by default.
80
+ config_accessor :log_warning_on_csrf_failure
81
+ self.log_warning_on_csrf_failure = true
82
+
83
+ # Controls whether the Origin header is checked in addition to the CSRF token.
84
+ config_accessor :forgery_protection_origin_check
85
+ self.forgery_protection_origin_check = false
86
+
87
+ # Controls whether form-action/method specific CSRF tokens are used.
88
+ config_accessor :per_form_csrf_tokens
89
+ self.per_form_csrf_tokens = false
90
+
91
+ # Controls whether forgery protection is enabled by default.
92
+ config_accessor :default_protect_from_forgery
93
+ self.default_protect_from_forgery = false
94
+
44
95
  helper_method :form_authenticity_token
45
96
  helper_method :protect_against_forgery?
46
97
  end
47
98
 
48
99
  module ClassMethods
49
- # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
100
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
50
101
  #
51
- # Example:
102
+ # class ApplicationController < ActionController::Base
103
+ # protect_from_forgery
104
+ # end
52
105
  #
53
106
  # class FooController < ApplicationController
54
- # protect_from_forgery :except => :index
107
+ # protect_from_forgery except: :index
108
+ # end
55
109
  #
56
- # You can disable csrf protection on controller-by-controller basis:
110
+ # You can disable forgery protection on controller by skipping the verification before_action:
57
111
  #
58
- # skip_before_filter :verify_authenticity_token
112
+ # skip_before_action :verify_authenticity_token
59
113
  #
60
- # It can also be disabled for specific controller actions:
114
+ # Valid Options:
61
115
  #
62
- # skip_before_filter :verify_authenticity_token, :except => [:create]
116
+ # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
117
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
118
+ # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
119
+ # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
120
+ # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
63
121
  #
64
- # Valid Options:
122
+ # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
123
+ # * <tt>:with</tt> - Set the method to handle unverified request.
65
124
  #
66
- # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
125
+ # Valid unverified request handling methods are:
126
+ # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
127
+ # * <tt>:reset_session</tt> - Resets the session.
128
+ # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
67
129
  def protect_from_forgery(options = {})
130
+ options = options.reverse_merge(prepend: false)
131
+
132
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
68
133
  self.request_forgery_protection_token ||= :authenticity_token
69
- prepend_before_filter :verify_authenticity_token, options
134
+ before_action :verify_authenticity_token, options
135
+ append_after_action :verify_same_origin_request
136
+ end
137
+
138
+ # Turn off request forgery protection. This is a wrapper for:
139
+ #
140
+ # skip_before_action :verify_authenticity_token
141
+ #
142
+ # See +skip_before_action+ for allowed options.
143
+ def skip_forgery_protection(options = {})
144
+ skip_before_action :verify_authenticity_token, options
70
145
  end
146
+
147
+ private
148
+
149
+ def protection_method_class(name)
150
+ ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
151
+ rescue NameError
152
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session"
153
+ end
71
154
  end
72
155
 
73
- protected
74
- # The actual before_filter that is used. Modify this to change how you handle unverified requests.
75
- def verify_authenticity_token
76
- unless verified_request?
77
- logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
156
+ module ProtectionMethods
157
+ class NullSession
158
+ def initialize(controller)
159
+ @controller = controller
160
+ end
161
+
162
+ # This is the method that defines the application behavior when a request is found to be unverified.
163
+ def handle_unverified_request
164
+ request = @controller.request
165
+ request.session = NullSessionHash.new(request)
166
+ request.flash = nil
167
+ request.session_options = { skip: true }
168
+ request.cookie_jar = NullCookieJar.build(request, {})
169
+ end
170
+
171
+ private
172
+
173
+ class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
174
+ def initialize(req)
175
+ super(nil, req)
176
+ @data = {}
177
+ @loaded = true
178
+ end
179
+
180
+ # no-op
181
+ def destroy; end
182
+
183
+ def exists?
184
+ true
185
+ end
186
+ end
187
+
188
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
189
+ def write(*)
190
+ # nothing
191
+ end
192
+ end
193
+ end
194
+
195
+ class ResetSession
196
+ def initialize(controller)
197
+ @controller = controller
198
+ end
199
+
200
+ def handle_unverified_request
201
+ @controller.reset_session
202
+ end
203
+ end
204
+
205
+ class Exception
206
+ def initialize(controller)
207
+ @controller = controller
208
+ end
209
+
210
+ def handle_unverified_request
211
+ raise ActionController::InvalidAuthenticityToken
212
+ end
213
+ end
214
+ end
215
+
216
+ private
217
+ # The actual before_action that is used to verify the CSRF token.
218
+ # Don't override this directly. Provide your own forgery protection
219
+ # strategy instead. If you override, you'll disable same-origin
220
+ # <tt><script></tt> verification.
221
+ #
222
+ # Lean on the protect_from_forgery declaration to mark which actions are
223
+ # due for same-origin request verification. If protect_from_forgery is
224
+ # enabled on an action, this before_action flags its after_action to
225
+ # verify that JavaScript responses are for XHR requests, ensuring they
226
+ # follow the browser's same-origin policy.
227
+ def verify_authenticity_token # :doc:
228
+ mark_for_same_origin_verification!
229
+
230
+ if !verified_request?
231
+ if logger && log_warning_on_csrf_failure
232
+ if valid_request_origin?
233
+ logger.warn "Can't verify CSRF token authenticity."
234
+ else
235
+ logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
236
+ end
237
+ end
78
238
  handle_unverified_request
79
239
  end
80
240
  end
81
241
 
82
- # This is the method that defines the application behavior when a request is found to be unverified.
83
- # By default, \Rails resets the session when it finds an unverified request.
84
- def handle_unverified_request
85
- reset_session
242
+ def handle_unverified_request # :doc:
243
+ forgery_protection_strategy.new(self).handle_unverified_request
86
244
  end
87
245
 
246
+ #:nodoc:
247
+ CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
248
+ "<script> tag on another site requested protected JavaScript. " \
249
+ "If you know what you're doing, go ahead and disable forgery " \
250
+ "protection on this action to permit cross-origin JavaScript embedding."
251
+ private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
252
+ # :startdoc:
253
+
254
+ # If +verify_authenticity_token+ was run (indicating that we have
255
+ # forgery protection enabled for this request) then also verify that
256
+ # we aren't serving an unauthorized cross-origin response.
257
+ def verify_same_origin_request # :doc:
258
+ if marked_for_same_origin_verification? && non_xhr_javascript_response?
259
+ if logger && log_warning_on_csrf_failure
260
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
261
+ end
262
+ raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
263
+ end
264
+ end
265
+
266
+ # GET requests are checked for cross-origin JavaScript after rendering.
267
+ def mark_for_same_origin_verification! # :doc:
268
+ @marked_for_same_origin_verification = request.get?
269
+ end
270
+
271
+ # If the +verify_authenticity_token+ before_action ran, verify that
272
+ # JavaScript responses are only served to same-origin GET requests.
273
+ def marked_for_same_origin_verification? # :doc:
274
+ @marked_for_same_origin_verification ||= false
275
+ end
276
+
277
+ # Check for cross-origin JavaScript responses.
278
+ def non_xhr_javascript_response? # :doc:
279
+ content_type =~ %r(\Atext/javascript) && !request.xhr?
280
+ end
281
+
282
+ AUTHENTICITY_TOKEN_LENGTH = 32
283
+
88
284
  # Returns true or false if a request is verified. Checks:
89
285
  #
90
- # * is it a GET request? Gets should be safe and idempotent
286
+ # * Is it a GET or HEAD request? GETs should be safe and idempotent
91
287
  # * Does the form_authenticity_token match the given token value from the params?
92
- # * Does the X-CSRF-Token header match the form_authenticity_token
93
- def verified_request?
94
- !protect_against_forgery? || request.get? ||
95
- form_authenticity_token == params[request_forgery_protection_token] ||
96
- form_authenticity_token == request.headers['X-CSRF-Token']
288
+ # * Does the X-CSRF-Token header match the form_authenticity_token?
289
+ def verified_request? # :doc:
290
+ !protect_against_forgery? || request.get? || request.head? ||
291
+ (valid_request_origin? && any_authenticity_token_valid?)
292
+ end
293
+
294
+ # Checks if any of the authenticity tokens from the request are valid.
295
+ def any_authenticity_token_valid? # :doc:
296
+ request_authenticity_tokens.any? do |token|
297
+ valid_authenticity_token?(session, token)
298
+ end
299
+ end
300
+
301
+ # Possible authenticity tokens sent in the request.
302
+ def request_authenticity_tokens # :doc:
303
+ [form_authenticity_param, request.x_csrf_token]
97
304
  end
98
305
 
99
306
  # Sets the token value for the current session.
100
- def form_authenticity_token
101
- session[:_csrf_token] ||= SecureRandom.base64(32)
307
+ def form_authenticity_token(form_options: {})
308
+ masked_authenticity_token(session, form_options: form_options)
309
+ end
310
+
311
+ # Creates a masked version of the authenticity token that varies
312
+ # on each request. The masking is used to mitigate SSL attacks
313
+ # like BREACH.
314
+ def masked_authenticity_token(session, form_options: {}) # :doc:
315
+ action, method = form_options.values_at(:action, :method)
316
+
317
+ raw_token = if per_form_csrf_tokens && action && method
318
+ action_path = normalize_action_path(action)
319
+ per_form_csrf_token(session, action_path, method)
320
+ else
321
+ real_csrf_token(session)
322
+ end
323
+
324
+ one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
325
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
326
+ masked_token = one_time_pad + encrypted_csrf_token
327
+ Base64.strict_encode64(masked_token)
328
+ end
329
+
330
+ # Checks the client's masked token to see if it matches the
331
+ # session token. Essentially the inverse of
332
+ # +masked_authenticity_token+.
333
+ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
334
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
335
+ return false
336
+ end
337
+
338
+ begin
339
+ masked_token = Base64.strict_decode64(encoded_masked_token)
340
+ rescue ArgumentError # encoded_masked_token is invalid Base64
341
+ return false
342
+ end
343
+
344
+ # See if it's actually a masked token or not. In order to
345
+ # deploy this code, we should be able to handle any unmasked
346
+ # tokens that we've issued without error.
347
+
348
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
349
+ # This is actually an unmasked token. This is expected if
350
+ # you have just upgraded to masked tokens, but should stop
351
+ # happening shortly after installing this gem.
352
+ compare_with_real_token masked_token, session
353
+
354
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
355
+ csrf_token = unmask_token(masked_token)
356
+
357
+ compare_with_real_token(csrf_token, session) ||
358
+ valid_per_form_csrf_token?(csrf_token, session)
359
+ else
360
+ false # Token is malformed.
361
+ end
362
+ end
363
+
364
+ def unmask_token(masked_token) # :doc:
365
+ # Split the token into the one-time pad and the encrypted
366
+ # value and decrypt it.
367
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
368
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
369
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
370
+ end
371
+
372
+ def compare_with_real_token(token, session) # :doc:
373
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
374
+ end
375
+
376
+ def valid_per_form_csrf_token?(token, session) # :doc:
377
+ if per_form_csrf_tokens
378
+ correct_token = per_form_csrf_token(
379
+ session,
380
+ normalize_action_path(request.fullpath),
381
+ request.request_method
382
+ )
383
+
384
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
385
+ else
386
+ false
387
+ end
388
+ end
389
+
390
+ def real_csrf_token(session) # :doc:
391
+ session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
392
+ Base64.strict_decode64(session[:_csrf_token])
393
+ end
394
+
395
+ def per_form_csrf_token(session, action_path, method) # :doc:
396
+ OpenSSL::HMAC.digest(
397
+ OpenSSL::Digest::SHA256.new,
398
+ real_csrf_token(session),
399
+ [action_path, method.downcase].join("#")
400
+ )
401
+ end
402
+
403
+ def xor_byte_strings(s1, s2) # :doc:
404
+ s2_bytes = s2.bytes
405
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
406
+ s2_bytes.pack("C*")
102
407
  end
103
408
 
104
409
  # The form's authenticity parameter. Override to provide your own.
105
- def form_authenticity_param
410
+ def form_authenticity_param # :doc:
106
411
  params[request_forgery_protection_token]
107
412
  end
108
413
 
109
- def protect_against_forgery?
414
+ # Checks if the controller allows forgery protection.
415
+ def protect_against_forgery? # :doc:
110
416
  allow_forgery_protection
111
417
  end
418
+
419
+ NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc
420
+ The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
421
+ means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
422
+ refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
423
+ best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
424
+ If you cannot change the referrer policy, you can disable origin checking with the
425
+ Rails.application.config.action_controller.forgery_protection_origin_check setting.
426
+ MSG
427
+
428
+ # Checks if the request originated from the same origin by looking at the
429
+ # Origin header.
430
+ def valid_request_origin? # :doc:
431
+ if forgery_protection_origin_check
432
+ # We accept blank origin headers because some user agents don't send it.
433
+ raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
434
+ request.origin.nil? || request.origin == request.base_url
435
+ else
436
+ true
437
+ end
438
+ end
439
+
440
+ def normalize_action_path(action_path) # :doc:
441
+ uri = URI.parse(action_path)
442
+ uri.path.chomp("/")
443
+ end
112
444
  end
113
445
  end
@@ -1,25 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController #:nodoc:
2
- # This module is responsible to provide `rescue_from` helpers
3
- # to controllers and configure when detailed exceptions must be
4
+ # This module is responsible for providing +rescue_from+ helpers
5
+ # to controllers and configuring when detailed exceptions must be
4
6
  # shown.
5
7
  module Rescue
6
8
  extend ActiveSupport::Concern
7
9
  include ActiveSupport::Rescuable
8
10
 
9
- def rescue_with_handler(exception)
10
- if (exception.respond_to?(:original_exception) &&
11
- (orig_exception = exception.original_exception) &&
12
- handler_for_rescue(orig_exception))
13
- exception = orig_exception
14
- end
15
- super(exception)
16
- end
17
-
18
11
  # Override this method if you want to customize when detailed
19
12
  # exceptions must be shown. This method is only called when
20
- # consider_all_requests_local is false. By default, it returns
21
- # false, but someone may set it to `request.local?` so local
22
- # requests in production still shows the detailed exception pages.
13
+ # +consider_all_requests_local+ is +false+. By default, it returns
14
+ # +false+, but someone may set it to <tt>request.local?</tt> so local
15
+ # requests in production still show the detailed exception pages.
23
16
  def show_detailed_exceptions?
24
17
  false
25
18
  end
@@ -28,8 +21,8 @@ module ActionController #:nodoc:
28
21
  def process_action(*args)
29
22
  super
30
23
  rescue Exception => exception
31
- request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
32
- rescue_with_handler(exception) || raise(exception)
24
+ request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
25
+ rescue_with_handler(exception) || raise
33
26
  end
34
27
  end
35
28
  end