omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,667 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "rack/session/abstract/id"
6
+ require "action_controller/metal/exceptions"
7
+ require "active_support/security_utils"
8
+
9
+ module ActionController # :nodoc:
10
+ class InvalidAuthenticityToken < ActionControllerError # :nodoc:
11
+ end
12
+
13
+ class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
14
+ end
15
+
16
+ # # Action Controller Request Forgery Protection
17
+ #
18
+ # Controller actions are protected from Cross-Site Request Forgery (CSRF)
19
+ # attacks by including a token in the rendered HTML for your application. This
20
+ # token is stored as a random string in the session, to which an attacker does
21
+ # not have access. When a request reaches your application, Rails verifies the
22
+ # received token with the token in the session. All requests are checked except
23
+ # GET requests as these should be idempotent. Keep in mind that all
24
+ # session-oriented requests are CSRF protected by default, including JavaScript
25
+ # and HTML requests.
26
+ #
27
+ # Since HTML and JavaScript requests are typically made from the browser, we
28
+ # need to ensure to verify request authenticity for the web browser. We can use
29
+ # session-oriented authentication for these types of requests, by using the
30
+ # `protect_from_forgery` method in our controllers.
31
+ #
32
+ # GET requests are not protected since they don't have side effects like writing
33
+ # to the database and don't leak sensitive information. JavaScript requests are
34
+ # an exception: a third-party site can use a <script> tag to reference a
35
+ # JavaScript URL on your site. When your JavaScript response loads on their
36
+ # site, it executes. With carefully crafted JavaScript on their end, sensitive
37
+ # data in your JavaScript response may be extracted. To prevent this, only
38
+ # XmlHttpRequest (known as XHR or Ajax) requests are allowed to make requests
39
+ # for JavaScript responses.
40
+ #
41
+ # Subclasses of ActionController::Base are protected by default with the
42
+ # `:exception` strategy, which raises an
43
+ # ActionController::InvalidAuthenticityToken error on unverified requests.
44
+ #
45
+ # APIs may want to disable this behavior since they are typically designed to be
46
+ # state-less: that is, the request API client handles the session instead of
47
+ # Rails. One way to achieve this is to use the `:null_session` strategy instead,
48
+ # which allows unverified requests to be handled, but with an empty session:
49
+ #
50
+ # class ApplicationController < ActionController::Base
51
+ # protect_from_forgery with: :null_session
52
+ # end
53
+ #
54
+ # Note that API only applications don't include this module or a session
55
+ # middleware by default, and so don't require CSRF protection to be configured.
56
+ #
57
+ # The token parameter is named `authenticity_token` by default. The name and
58
+ # value of this token must be added to every layout that renders forms by
59
+ # including `csrf_meta_tags` in the HTML `head`.
60
+ #
61
+ # Learn more about CSRF attacks and securing your application in the [Ruby on
62
+ # Rails Security Guide](https://guides.rubyonrails.org/security.html).
63
+ module RequestForgeryProtection
64
+ CSRF_TOKEN = "action_controller.csrf_token"
65
+
66
+ extend ActiveSupport::Concern
67
+
68
+ include AbstractController::Helpers
69
+ include AbstractController::Callbacks
70
+
71
+ included do
72
+ # Sets the token parameter name for RequestForgery. Calling
73
+ # `protect_from_forgery` sets it to `:authenticity_token` by default.
74
+ config_accessor :request_forgery_protection_token
75
+ self.request_forgery_protection_token ||= :authenticity_token
76
+
77
+ # Holds the class which implements the request forgery protection.
78
+ config_accessor :forgery_protection_strategy
79
+ self.forgery_protection_strategy = nil
80
+
81
+ # Controls whether request forgery protection is turned on or not. Turned off by
82
+ # default only in test mode.
83
+ config_accessor :allow_forgery_protection
84
+ self.allow_forgery_protection = true if allow_forgery_protection.nil?
85
+
86
+ # Controls whether a CSRF failure logs a warning. On by default.
87
+ config_accessor :log_warning_on_csrf_failure
88
+ self.log_warning_on_csrf_failure = true
89
+
90
+ # Controls whether the Origin header is checked in addition to the CSRF token.
91
+ config_accessor :forgery_protection_origin_check
92
+ self.forgery_protection_origin_check = false
93
+
94
+ # Controls whether form-action/method specific CSRF tokens are used.
95
+ config_accessor :per_form_csrf_tokens
96
+ self.per_form_csrf_tokens = false
97
+
98
+ # The strategy to use for storing and retrieving CSRF tokens.
99
+ config_accessor :csrf_token_storage_strategy
100
+ self.csrf_token_storage_strategy = SessionStore.new
101
+
102
+ helper_method :form_authenticity_token
103
+ helper_method :protect_against_forgery?
104
+ end
105
+
106
+ module ClassMethods
107
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests
108
+ # are not checked.
109
+ #
110
+ # class ApplicationController < ActionController::Base
111
+ # protect_from_forgery
112
+ # end
113
+ #
114
+ # class FooController < ApplicationController
115
+ # protect_from_forgery except: :index
116
+ # end
117
+ #
118
+ # You can disable forgery protection on a controller using
119
+ # skip_forgery_protection:
120
+ #
121
+ # class BarController < ApplicationController
122
+ # skip_forgery_protection
123
+ # end
124
+ #
125
+ # Valid Options:
126
+ #
127
+ # * `:only` / `:except` - Only apply forgery protection to a subset of
128
+ # actions. For example `only: [ :create, :create_all ]`.
129
+ # * `:if` / `:unless` - Turn off the forgery protection entirely depending on
130
+ # the passed Proc or method reference.
131
+ # * `:prepend` - By default, the verification of the authentication token will
132
+ # be added at the position of the protect_from_forgery call in your
133
+ # application. This means any callbacks added before are run first. This is
134
+ # useful when you want your forgery protection to depend on other callbacks,
135
+ # like authentication methods (Oauth vs Cookie auth).
136
+ #
137
+ # If you need to add verification to the beginning of the callback chain,
138
+ # use `prepend: true`.
139
+ # * `:with` - Set the method to handle unverified request. Note if
140
+ # `default_protect_from_forgery` is true, Rails call protect_from_forgery
141
+ # with `with :exception`.
142
+ #
143
+ #
144
+ # Built-in unverified request handling methods are:
145
+ # * `:exception` - Raises ActionController::InvalidAuthenticityToken
146
+ # exception.
147
+ # * `:reset_session` - Resets the session.
148
+ # * `:null_session` - Provides an empty session during request but doesn't
149
+ # reset it completely. Used as default if `:with` option is not specified.
150
+ #
151
+ #
152
+ # You can also implement custom strategy classes for unverified request
153
+ # handling:
154
+ #
155
+ # class CustomStrategy
156
+ # def initialize(controller)
157
+ # @controller = controller
158
+ # end
159
+ #
160
+ # def handle_unverified_request
161
+ # # Custom behavior for unverfied request
162
+ # end
163
+ # end
164
+ #
165
+ # class ApplicationController < ActionController::Base
166
+ # protect_from_forgery with: CustomStrategy
167
+ # end
168
+ #
169
+ # * `:store` - Set the strategy to store and retrieve CSRF tokens.
170
+ #
171
+ #
172
+ # Built-in session token strategies are:
173
+ # * `:session` - Store the CSRF token in the session. Used as default if
174
+ # `:store` option is not specified.
175
+ # * `:cookie` - Store the CSRF token in an encrypted cookie.
176
+ #
177
+ #
178
+ # You can also implement custom strategy classes for CSRF token storage:
179
+ #
180
+ # class CustomStore
181
+ # def fetch(request)
182
+ # # Return the token from a custom location
183
+ # end
184
+ #
185
+ # def store(request, csrf_token)
186
+ # # Store the token in a custom location
187
+ # end
188
+ #
189
+ # def reset(request)
190
+ # # Delete the stored session token
191
+ # end
192
+ # end
193
+ #
194
+ # class ApplicationController < ActionController::Base
195
+ # protect_from_forgery store: CustomStore.new
196
+ # end
197
+ def protect_from_forgery(options = {})
198
+ options = options.reverse_merge(prepend: false)
199
+
200
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
201
+ self.request_forgery_protection_token ||= :authenticity_token
202
+
203
+ self.csrf_token_storage_strategy = storage_strategy(options[:store] || SessionStore.new)
204
+
205
+ before_action :verify_authenticity_token, options
206
+ append_after_action :verify_same_origin_request
207
+ end
208
+
209
+ # Turn off request forgery protection. This is a wrapper for:
210
+ #
211
+ # skip_before_action :verify_authenticity_token
212
+ #
213
+ # See `skip_before_action` for allowed options.
214
+ def skip_forgery_protection(options = {})
215
+ skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
216
+ end
217
+
218
+ private
219
+ def protection_method_class(name)
220
+ case name
221
+ when :null_session
222
+ ProtectionMethods::NullSession
223
+ when :reset_session
224
+ ProtectionMethods::ResetSession
225
+ when :exception
226
+ ProtectionMethods::Exception
227
+ when Class
228
+ name
229
+ else
230
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
231
+ end
232
+ end
233
+
234
+ def storage_strategy(name)
235
+ case name
236
+ when :session
237
+ SessionStore.new
238
+ when :cookie
239
+ CookieStore.new(:csrf_token)
240
+ else
241
+ return name if is_storage_strategy?(name)
242
+ raise ArgumentError, "Invalid CSRF token storage strategy, use :session, :cookie, or a custom CSRF token storage class."
243
+ end
244
+ end
245
+
246
+ def is_storage_strategy?(object)
247
+ object.respond_to?(:fetch) && object.respond_to?(:store) && object.respond_to?(:reset)
248
+ end
249
+ end
250
+
251
+ module ProtectionMethods
252
+ class NullSession
253
+ def initialize(controller)
254
+ @controller = controller
255
+ end
256
+
257
+ # This is the method that defines the application behavior when a request is
258
+ # found to be unverified.
259
+ def handle_unverified_request
260
+ request = @controller.request
261
+ request.session = NullSessionHash.new(request)
262
+ request.flash = nil
263
+ request.session_options = { skip: true }
264
+ request.cookie_jar = NullCookieJar.build(request, {})
265
+ end
266
+
267
+ private
268
+ class NullSessionHash < Rack::Session::Abstract::SessionHash
269
+ def initialize(req)
270
+ super(nil, req)
271
+ @data = {}
272
+ @loaded = true
273
+ end
274
+
275
+ # no-op
276
+ def destroy; end
277
+
278
+ def exists?
279
+ true
280
+ end
281
+
282
+ def enabled?
283
+ false
284
+ end
285
+ end
286
+
287
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar
288
+ def write(*)
289
+ # nothing
290
+ end
291
+ end
292
+ end
293
+
294
+ class ResetSession
295
+ def initialize(controller)
296
+ @controller = controller
297
+ end
298
+
299
+ def handle_unverified_request
300
+ @controller.reset_session
301
+ end
302
+ end
303
+
304
+ class Exception
305
+ attr_accessor :warning_message
306
+
307
+ def initialize(controller)
308
+ @controller = controller
309
+ end
310
+
311
+ def handle_unverified_request
312
+ raise ActionController::InvalidAuthenticityToken, warning_message
313
+ end
314
+ end
315
+ end
316
+
317
+ class SessionStore
318
+ def fetch(request)
319
+ request.session[:_csrf_token]
320
+ end
321
+
322
+ def store(request, csrf_token)
323
+ request.session[:_csrf_token] = csrf_token
324
+ end
325
+
326
+ def reset(request)
327
+ request.session.delete(:_csrf_token)
328
+ end
329
+ end
330
+
331
+ class CookieStore
332
+ def initialize(cookie = :csrf_token)
333
+ @cookie_name = cookie
334
+ end
335
+
336
+ def fetch(request)
337
+ contents = request.cookie_jar.encrypted[@cookie_name]
338
+ return nil if contents.nil?
339
+
340
+ value = JSON.parse(contents)
341
+ return nil unless value.dig("session_id", "public_id") == request.session.id_was&.public_id
342
+
343
+ value["token"]
344
+ rescue JSON::ParserError
345
+ nil
346
+ end
347
+
348
+ def store(request, csrf_token)
349
+ request.cookie_jar.encrypted.permanent[@cookie_name] = {
350
+ value: {
351
+ token: csrf_token,
352
+ session_id: request.session.id,
353
+ }.to_json,
354
+ httponly: true,
355
+ same_site: :lax,
356
+ }
357
+ end
358
+
359
+ def reset(request)
360
+ request.cookie_jar.delete(@cookie_name)
361
+ end
362
+ end
363
+
364
+ def initialize(...)
365
+ super
366
+ @_marked_for_same_origin_verification = nil
367
+ end
368
+
369
+ def reset_csrf_token(request) # :doc:
370
+ request.env.delete(CSRF_TOKEN)
371
+ csrf_token_storage_strategy.reset(request)
372
+ end
373
+
374
+ def commit_csrf_token(request) # :doc:
375
+ csrf_token = request.env[CSRF_TOKEN]
376
+ csrf_token_storage_strategy.store(request, csrf_token) unless csrf_token.nil?
377
+ end
378
+
379
+ private
380
+ # The actual before_action that is used to verify the CSRF token. Don't override
381
+ # this directly. Provide your own forgery protection strategy instead. If you
382
+ # override, you'll disable same-origin `<script>` verification.
383
+ #
384
+ # Lean on the protect_from_forgery declaration to mark which actions are due for
385
+ # same-origin request verification. If protect_from_forgery is enabled on an
386
+ # action, this before_action flags its after_action to verify that JavaScript
387
+ # responses are for XHR requests, ensuring they follow the browser's same-origin
388
+ # policy.
389
+ def verify_authenticity_token # :doc:
390
+ mark_for_same_origin_verification!
391
+
392
+ if !verified_request?
393
+ logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure
394
+
395
+ handle_unverified_request
396
+ end
397
+ end
398
+
399
+ def handle_unverified_request
400
+ protection_strategy = forgery_protection_strategy.new(self)
401
+
402
+ if protection_strategy.respond_to?(:warning_message)
403
+ protection_strategy.warning_message = unverified_request_warning_message
404
+ end
405
+
406
+ protection_strategy.handle_unverified_request
407
+ end
408
+
409
+ def unverified_request_warning_message
410
+ if valid_request_origin?
411
+ "Can't verify CSRF token authenticity."
412
+ else
413
+ "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
414
+ end
415
+ end
416
+
417
+ CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
418
+ "<script> tag on another site requested protected JavaScript. " \
419
+ "If you know what you're doing, go ahead and disable forgery " \
420
+ "protection on this action to permit cross-origin JavaScript embedding."
421
+ private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
422
+ # :startdoc:
423
+
424
+ # If `verify_authenticity_token` was run (indicating that we have
425
+ # forgery protection enabled for this request) then also verify that we aren't
426
+ # serving an unauthorized cross-origin response.
427
+ def verify_same_origin_request # :doc:
428
+ if marked_for_same_origin_verification? && non_xhr_javascript_response?
429
+ if logger && log_warning_on_csrf_failure
430
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
431
+ end
432
+ raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
433
+ end
434
+ end
435
+
436
+ # GET requests are checked for cross-origin JavaScript after rendering.
437
+ def mark_for_same_origin_verification! # :doc:
438
+ @_marked_for_same_origin_verification = request.get?
439
+ end
440
+
441
+ # If the `verify_authenticity_token` before_action ran, verify that JavaScript
442
+ # responses are only served to same-origin GET requests.
443
+ def marked_for_same_origin_verification? # :doc:
444
+ @_marked_for_same_origin_verification ||= false
445
+ end
446
+
447
+ # Check for cross-origin JavaScript responses.
448
+ def non_xhr_javascript_response? # :doc:
449
+ %r(\A(?:text|application)/javascript).match?(media_type) && !request.xhr?
450
+ end
451
+
452
+ AUTHENTICITY_TOKEN_LENGTH = 32
453
+
454
+ # Returns true or false if a request is verified. Checks:
455
+ #
456
+ # * Is it a GET or HEAD request? GETs should be safe and idempotent
457
+ # * Does the form_authenticity_token match the given token value from the
458
+ # params?
459
+ # * Does the `X-CSRF-Token` header match the form_authenticity_token?
460
+ #
461
+ def verified_request? # :doc:
462
+ !protect_against_forgery? || request.get? || request.head? ||
463
+ (valid_request_origin? && any_authenticity_token_valid?)
464
+ end
465
+
466
+ # Checks if any of the authenticity tokens from the request are valid.
467
+ def any_authenticity_token_valid? # :doc:
468
+ request_authenticity_tokens.any? do |token|
469
+ valid_authenticity_token?(session, token)
470
+ end
471
+ end
472
+
473
+ # Possible authenticity tokens sent in the request.
474
+ def request_authenticity_tokens # :doc:
475
+ [form_authenticity_param, request.x_csrf_token]
476
+ end
477
+
478
+ # Creates the authenticity token for the current request.
479
+ def form_authenticity_token(form_options: {}) # :doc:
480
+ masked_authenticity_token(form_options: form_options)
481
+ end
482
+
483
+ # Creates a masked version of the authenticity token that varies on each
484
+ # request. The masking is used to mitigate SSL attacks like BREACH.
485
+ def masked_authenticity_token(form_options: {})
486
+ action, method = form_options.values_at(:action, :method)
487
+
488
+ raw_token = if per_form_csrf_tokens && action && method
489
+ action_path = normalize_action_path(action)
490
+ per_form_csrf_token(nil, action_path, method)
491
+ else
492
+ global_csrf_token
493
+ end
494
+
495
+ mask_token(raw_token)
496
+ end
497
+
498
+ # Checks the client's masked token to see if it matches the session token.
499
+ # Essentially the inverse of `masked_authenticity_token`.
500
+ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
501
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
502
+ return false
503
+ end
504
+
505
+ begin
506
+ masked_token = decode_csrf_token(encoded_masked_token)
507
+ rescue ArgumentError # encoded_masked_token is invalid Base64
508
+ return false
509
+ end
510
+
511
+ # See if it's actually a masked token or not. In order to deploy this code, we
512
+ # should be able to handle any unmasked tokens that we've issued without error.
513
+
514
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
515
+ # This is actually an unmasked token. This is expected if you have just upgraded
516
+ # to masked tokens, but should stop happening shortly after installing this gem.
517
+ compare_with_real_token masked_token
518
+
519
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
520
+ csrf_token = unmask_token(masked_token)
521
+
522
+ compare_with_global_token(csrf_token) ||
523
+ compare_with_real_token(csrf_token) ||
524
+ valid_per_form_csrf_token?(csrf_token)
525
+ else
526
+ false # Token is malformed.
527
+ end
528
+ end
529
+
530
+ def unmask_token(masked_token) # :doc:
531
+ # Split the token into the one-time pad and the encrypted value and decrypt it.
532
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
533
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
534
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
535
+ end
536
+
537
+ def mask_token(raw_token) # :doc:
538
+ one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
539
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
540
+ masked_token = one_time_pad + encrypted_csrf_token
541
+ encode_csrf_token(masked_token)
542
+ end
543
+
544
+ def compare_with_real_token(token, session = nil) # :doc:
545
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
546
+ end
547
+
548
+ def compare_with_global_token(token, session = nil) # :doc:
549
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
550
+ end
551
+
552
+ def valid_per_form_csrf_token?(token, session = nil) # :doc:
553
+ if per_form_csrf_tokens
554
+ correct_token = per_form_csrf_token(
555
+ session,
556
+ request.path.chomp("/"),
557
+ request.request_method
558
+ )
559
+
560
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
561
+ else
562
+ false
563
+ end
564
+ end
565
+
566
+ def real_csrf_token(_session = nil) # :doc:
567
+ csrf_token = request.env.fetch(CSRF_TOKEN) do
568
+ request.env[CSRF_TOKEN] = csrf_token_storage_strategy.fetch(request) || generate_csrf_token
569
+ end
570
+
571
+ decode_csrf_token(csrf_token)
572
+ end
573
+
574
+ def per_form_csrf_token(session, action_path, method) # :doc:
575
+ csrf_token_hmac(session, [action_path, method.downcase].join("#"))
576
+ end
577
+
578
+ GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
579
+ private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
580
+
581
+ def global_csrf_token(session = nil) # :doc:
582
+ csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
583
+ end
584
+
585
+ def csrf_token_hmac(session, identifier) # :doc:
586
+ OpenSSL::HMAC.digest(
587
+ OpenSSL::Digest::SHA256.new,
588
+ real_csrf_token(session),
589
+ identifier
590
+ )
591
+ end
592
+
593
+ def xor_byte_strings(s1, s2) # :doc:
594
+ s2 = s2.dup
595
+ size = s1.bytesize
596
+ i = 0
597
+ while i < size
598
+ s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
599
+ i += 1
600
+ end
601
+ s2
602
+ end
603
+
604
+ # The form's authenticity parameter. Override to provide your own.
605
+ def form_authenticity_param # :doc:
606
+ params[request_forgery_protection_token]
607
+ end
608
+
609
+ # Checks if the controller allows forgery protection.
610
+ def protect_against_forgery? # :doc:
611
+ allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?)
612
+ end
613
+
614
+ NULL_ORIGIN_MESSAGE = <<~MSG
615
+ The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
616
+ means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
617
+ refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
618
+ best solution is to change your referrer policy to something less strict like same-origin or strict-origin.
619
+ If you cannot change the referrer policy, you can disable origin checking with the
620
+ Rails.application.config.action_controller.forgery_protection_origin_check setting.
621
+ MSG
622
+
623
+ # Checks if the request originated from the same origin by looking at the Origin
624
+ # header.
625
+ def valid_request_origin? # :doc:
626
+ if forgery_protection_origin_check
627
+ # We accept blank origin headers because some user agents don't send it.
628
+ raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
629
+ request.origin.nil? || request.origin == request.base_url
630
+ else
631
+ true
632
+ end
633
+ end
634
+
635
+ def normalize_action_path(action_path) # :doc:
636
+ uri = URI.parse(action_path)
637
+
638
+ if uri.relative? && (action_path.blank? || !action_path.start_with?("/"))
639
+ normalize_relative_action_path(uri.path)
640
+ else
641
+ uri.path.chomp("/")
642
+ end
643
+ end
644
+
645
+ def normalize_relative_action_path(rel_action_path) # :doc:
646
+ uri = URI.parse(request.path)
647
+ # add the action path to the request.path
648
+ uri.path += "/#{rel_action_path}"
649
+ # relative path with "./path"
650
+ uri.path.gsub!("/./", "/")
651
+
652
+ uri.path.chomp("/")
653
+ end
654
+
655
+ def generate_csrf_token
656
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
657
+ end
658
+
659
+ def encode_csrf_token(csrf_token)
660
+ Base64.urlsafe_encode64(csrf_token, padding: false)
661
+ end
662
+
663
+ def decode_csrf_token(encoded_csrf_token)
664
+ Base64.urlsafe_decode64(encoded_csrf_token)
665
+ end
666
+ end
667
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ # # Action Controller Rescue
7
+ #
8
+ # This module is responsible for providing
9
+ # [rescue_from](rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from) to
10
+ # controllers, wrapping actions to handle configured errors, and configuring
11
+ # when detailed exceptions must be shown.
12
+ module Rescue
13
+ extend ActiveSupport::Concern
14
+ include ActiveSupport::Rescuable
15
+
16
+ # Override this method if you want to customize when detailed exceptions must be
17
+ # shown. This method is only called when `consider_all_requests_local` is
18
+ # `false`. By default, it returns `false`, but someone may set it to
19
+ # `request.local?` so local requests in production still show the detailed
20
+ # exception pages.
21
+ def show_detailed_exceptions?
22
+ false
23
+ end
24
+
25
+ private
26
+ def process_action(*)
27
+ super
28
+ rescue Exception => exception
29
+ request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
30
+ rescue_with_handler(exception) || raise
31
+ end
32
+ end
33
+ end