omg-actionpack 8.0.0.alpha1

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 (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