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,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/hash/slice"
6
+ require "active_support/core_ext/hash/except"
7
+ require "active_support/core_ext/module/anonymous"
8
+ require "action_dispatch/http/mime_type"
9
+
10
+ module ActionController
11
+ # # Action Controller Params Wrapper
12
+ #
13
+ # Wraps the parameters hash into a nested hash. This will allow clients to
14
+ # submit requests without having to specify any root elements.
15
+ #
16
+ # This functionality is enabled by default for JSON, and can be customized by
17
+ # setting the format array:
18
+ #
19
+ # class ApplicationController < ActionController::Base
20
+ # wrap_parameters format: [:json, :xml]
21
+ # end
22
+ #
23
+ # You could also turn it on per controller:
24
+ #
25
+ # class UsersController < ApplicationController
26
+ # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
27
+ # end
28
+ #
29
+ # If you enable `ParamsWrapper` for `:json` format, instead of having to send
30
+ # JSON parameters like this:
31
+ #
32
+ # {"user": {"name": "Konata"}}
33
+ #
34
+ # You can send parameters like this:
35
+ #
36
+ # {"name": "Konata"}
37
+ #
38
+ # And it will be wrapped into a nested hash with the key name matching the
39
+ # controller's name. For example, if you're posting to `UsersController`, your
40
+ # new `params` hash will look like this:
41
+ #
42
+ # {"name" => "Konata", "user" => {"name" => "Konata"}}
43
+ #
44
+ # You can also specify the key in which the parameters should be wrapped to, and
45
+ # also the list of attributes it should wrap by using either `:include` or
46
+ # `:exclude` options like this:
47
+ #
48
+ # class UsersController < ApplicationController
49
+ # wrap_parameters :person, include: [:username, :password]
50
+ # end
51
+ #
52
+ # On Active Record models with no `:include` or `:exclude` option set, it will
53
+ # only wrap the parameters returned by the class method `attribute_names`.
54
+ #
55
+ # If you're going to pass the parameters to an `ActiveModel` object (such as
56
+ # `User.new(params[:user])`), you might consider passing the model class to the
57
+ # method instead. The `ParamsWrapper` will actually try to determine the list of
58
+ # attribute names from the model and only wrap those attributes:
59
+ #
60
+ # class UsersController < ApplicationController
61
+ # wrap_parameters Person
62
+ # end
63
+ #
64
+ # You still could pass `:include` and `:exclude` to set the list of attributes
65
+ # you want to wrap.
66
+ #
67
+ # By default, if you don't specify the key in which the parameters would be
68
+ # wrapped to, `ParamsWrapper` will actually try to determine if there's a model
69
+ # related to it or not. This controller, for example:
70
+ #
71
+ # class Admin::UsersController < ApplicationController
72
+ # end
73
+ #
74
+ # will try to check if `Admin::User` or `User` model exists, and use it to
75
+ # determine the wrapper key respectively. If both models don't exist, it will
76
+ # then fall back to use `user` as the key.
77
+ #
78
+ # To disable this functionality for a controller:
79
+ #
80
+ # class UsersController < ApplicationController
81
+ # wrap_parameters false
82
+ # end
83
+ module ParamsWrapper
84
+ extend ActiveSupport::Concern
85
+
86
+ EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
87
+
88
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
89
+ def self.from_hash(hash)
90
+ name = hash[:name]
91
+ format = Array(hash[:format])
92
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
93
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
94
+ new name, format, include, exclude, nil, nil
95
+ end
96
+
97
+ def initialize(name, format, include, exclude, klass, model) # :nodoc:
98
+ super
99
+ @mutex = Mutex.new
100
+ @include_set = include
101
+ @name_set = name
102
+ end
103
+
104
+ def model
105
+ super || self.model = _default_wrap_model
106
+ end
107
+
108
+ def include
109
+ return super if @include_set
110
+
111
+ m = model
112
+ @mutex.synchronize do
113
+ return super if @include_set
114
+
115
+ @include_set = true
116
+
117
+ unless super || exclude
118
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
119
+ self.include = m.attribute_names
120
+
121
+ if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty?
122
+ self.include += m.stored_attributes.values.flatten.map(&:to_s)
123
+ end
124
+
125
+ if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any?
126
+ self.include += m.attribute_aliases.keys
127
+ end
128
+
129
+ if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any?
130
+ self.include += m.nested_attributes_options.keys.map do |key|
131
+ (+key.to_s).concat("_attributes")
132
+ end
133
+ end
134
+
135
+ self.include
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def name
142
+ return super if @name_set
143
+
144
+ m = model
145
+ @mutex.synchronize do
146
+ return super if @name_set
147
+
148
+ @name_set = true
149
+
150
+ unless super || klass.anonymous?
151
+ self.name = m ? m.to_s.demodulize.underscore :
152
+ klass.controller_name.singularize
153
+ end
154
+ end
155
+ end
156
+
157
+ private
158
+ # Determine the wrapper model from the controller's name. By convention, this
159
+ # could be done by trying to find the defined model that has the same singular
160
+ # name as the controller. For example, `UsersController` will try to find if the
161
+ # `User` model exists.
162
+ #
163
+ # This method also does namespace lookup. Foo::Bar::UsersController will try to
164
+ # find Foo::Bar::User, Foo::User and finally User.
165
+ def _default_wrap_model
166
+ return nil if klass.anonymous?
167
+ model_name = klass.name.delete_suffix("Controller").classify
168
+
169
+ begin
170
+ if model_klass = model_name.safe_constantize
171
+ model_klass
172
+ else
173
+ namespaces = model_name.split("::")
174
+ namespaces.delete_at(-2)
175
+ break if namespaces.last == model_name
176
+ model_name = namespaces.join("::")
177
+ end
178
+ end until model_klass
179
+
180
+ model_klass
181
+ end
182
+ end
183
+
184
+ included do
185
+ class_attribute :_wrapper_options, default: Options.from_hash(format: [])
186
+ end
187
+
188
+ module ClassMethods
189
+ def _set_wrapper_options(options)
190
+ self._wrapper_options = Options.from_hash(options)
191
+ end
192
+
193
+ # Sets the name of the wrapper key, or the model which `ParamsWrapper` would use
194
+ # to determine the attribute names from.
195
+ #
196
+ # #### Examples
197
+ # wrap_parameters format: :xml
198
+ # # enables the parameter wrapper for XML format
199
+ #
200
+ # wrap_parameters :person
201
+ # # wraps parameters into +params[:person]+ hash
202
+ #
203
+ # wrap_parameters Person
204
+ # # wraps parameters by determining the wrapper key from Person class
205
+ # # (+person+, in this case) and the list of attribute names
206
+ #
207
+ # wrap_parameters include: [:username, :title]
208
+ # # wraps only +:username+ and +:title+ attributes from parameters.
209
+ #
210
+ # wrap_parameters false
211
+ # # disables parameters wrapping for this controller altogether.
212
+ #
213
+ # #### Options
214
+ # * `:format` - The list of formats in which the parameters wrapper will be
215
+ # enabled.
216
+ # * `:include` - The list of attribute names which parameters wrapper will
217
+ # wrap into a nested hash.
218
+ # * `:exclude` - The list of attribute names which parameters wrapper will
219
+ # exclude from a nested hash.
220
+ #
221
+ def wrap_parameters(name_or_model_or_options, options = {})
222
+ model = nil
223
+
224
+ case name_or_model_or_options
225
+ when Hash
226
+ options = name_or_model_or_options
227
+ when false
228
+ options = options.merge(format: [])
229
+ when Symbol, String
230
+ options = options.merge(name: name_or_model_or_options)
231
+ else
232
+ model = name_or_model_or_options
233
+ end
234
+
235
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
236
+ opts.model = model
237
+ opts.klass = self
238
+
239
+ self._wrapper_options = opts
240
+ end
241
+
242
+ # Sets the default wrapper key or model which will be used to determine wrapper
243
+ # key and attribute names. Called automatically when the module is inherited.
244
+ def inherited(klass)
245
+ if klass._wrapper_options.format.any?
246
+ params = klass._wrapper_options.dup
247
+ params.klass = klass
248
+ klass._wrapper_options = params
249
+ end
250
+ super
251
+ end
252
+ end
253
+
254
+ private
255
+ # Performs parameters wrapping upon the request. Called automatically by the
256
+ # metal call stack.
257
+ def process_action(*)
258
+ _perform_parameter_wrapping if _wrapper_enabled?
259
+ super
260
+ end
261
+
262
+ # Returns the wrapper key which will be used to store wrapped parameters.
263
+ def _wrapper_key
264
+ _wrapper_options.name
265
+ end
266
+
267
+ # Returns the list of enabled formats.
268
+ def _wrapper_formats
269
+ _wrapper_options.format
270
+ end
271
+
272
+ # Returns the list of parameters which will be selected for wrapped.
273
+ def _wrap_parameters(parameters)
274
+ { _wrapper_key => _extract_parameters(parameters) }
275
+ end
276
+
277
+ def _extract_parameters(parameters)
278
+ if include_only = _wrapper_options.include
279
+ parameters.slice(*include_only)
280
+ elsif _wrapper_options.exclude
281
+ exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS
282
+ parameters.except(*exclude)
283
+ else
284
+ parameters.except(*EXCLUDE_PARAMETERS)
285
+ end
286
+ end
287
+
288
+ # Checks if we should perform parameters wrapping.
289
+ def _wrapper_enabled?
290
+ return false unless request.has_content_type?
291
+
292
+ ref = request.content_mime_type.ref
293
+
294
+ _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
295
+ rescue ActionDispatch::Http::Parameters::ParseError
296
+ false
297
+ end
298
+
299
+ def _perform_parameter_wrapping
300
+ wrapped_hash = _wrap_parameters request.request_parameters
301
+ wrapped_keys = request.request_parameters.keys
302
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
303
+
304
+ # This will make the wrapped hash accessible from controller and view.
305
+ request.parameters.merge! wrapped_hash
306
+ request.request_parameters.merge! wrapped_hash
307
+
308
+ # This will display the wrapped hash in the log file.
309
+ request.filtered_parameters.merge! wrapped_filtered_hash
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ module PermissionsPolicy
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ # Overrides parts of the globally configured `Feature-Policy` header:
11
+ #
12
+ # class PagesController < ApplicationController
13
+ # permissions_policy do |policy|
14
+ # policy.geolocation "https://example.com"
15
+ # end
16
+ # end
17
+ #
18
+ # Options can be passed similar to `before_action`. For example, pass `only:
19
+ # :index` to override the header on the index action only:
20
+ #
21
+ # class PagesController < ApplicationController
22
+ # permissions_policy(only: :index) do |policy|
23
+ # policy.camera :self
24
+ # end
25
+ # end
26
+ #
27
+ def permissions_policy(**options, &block)
28
+ before_action(options) do
29
+ if block_given?
30
+ policy = request.permissions_policy.clone
31
+ instance_exec(policy, &block)
32
+ request.permissions_policy = policy
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ module RateLimiting
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ # Applies a rate limit to all actions or those specified by the normal
11
+ # `before_action` filters with `only:` and `except:`.
12
+ #
13
+ # The maximum number of requests allowed is specified `to:` and constrained to
14
+ # the window of time given by `within:`.
15
+ #
16
+ # Rate limits are by default unique to the ip address making the request, but
17
+ # you can provide your own identity function by passing a callable in the `by:`
18
+ # parameter. It's evaluated within the context of the controller processing the
19
+ # request.
20
+ #
21
+ # Requests that exceed the rate limit are refused with a `429 Too Many Requests`
22
+ # response. You can specialize this by passing a callable in the `with:`
23
+ # parameter. It's evaluated within the context of the controller processing the
24
+ # request.
25
+ #
26
+ # Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to
27
+ # `config.action_controller.cache_store`, which itself defaults to the global
28
+ # `config.cache_store`. If you don't want to store rate limits in the same
29
+ # datastore as your general caches, you can pass a custom store in the `store`
30
+ # parameter.
31
+ #
32
+ # Examples:
33
+ #
34
+ # class SessionsController < ApplicationController
35
+ # rate_limit to: 10, within: 3.minutes, only: :create
36
+ # end
37
+ #
38
+ # class SignupsController < ApplicationController
39
+ # rate_limit to: 1000, within: 10.seconds,
40
+ # by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
41
+ # end
42
+ #
43
+ # class APIController < ApplicationController
44
+ # RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
45
+ # rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
46
+ # end
47
+ def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
48
+ before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
49
+ end
50
+ end
51
+
52
+ private
53
+ def rate_limiting(to:, within:, by:, with:, store:)
54
+ count = store.increment("rate-limit:#{controller_path}:#{instance_exec(&by)}", 1, expires_in: within)
55
+ if count && count > to
56
+ ActiveSupport::Notifications.instrument("rate_limit.action_controller", request: request) do
57
+ instance_exec(&with)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController
6
+ module Redirecting
7
+ extend ActiveSupport::Concern
8
+
9
+ include AbstractController::Logger
10
+ include ActionController::UrlFor
11
+
12
+ class UnsafeRedirectError < StandardError; end
13
+
14
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/
15
+
16
+ included do
17
+ mattr_accessor :raise_on_open_redirects, default: false
18
+ end
19
+
20
+ # Redirects the browser to the target specified in `options`. This parameter can
21
+ # be any one of:
22
+ #
23
+ # * `Hash` - The URL will be generated by calling url_for with the `options`.
24
+ # * `Record` - The URL will be generated by calling url_for with the
25
+ # `options`, which will reference a named URL for that record.
26
+ # * `String` starting with `protocol://` (like `http://`) or a protocol
27
+ # relative reference (like `//`) - Is passed straight through as the target
28
+ # for redirection.
29
+ # * `String` not containing a protocol - The current protocol and host is
30
+ # prepended to the string.
31
+ # * `Proc` - A block that will be executed in the controller's context. Should
32
+ # return any option accepted by `redirect_to`.
33
+ #
34
+ #
35
+ # ### Examples
36
+ #
37
+ # redirect_to action: "show", id: 5
38
+ # redirect_to @post
39
+ # redirect_to "http://www.rubyonrails.org"
40
+ # redirect_to "/images/screenshot.jpg"
41
+ # redirect_to posts_url
42
+ # redirect_to proc { edit_post_url(@post) }
43
+ #
44
+ # The redirection happens as a `302 Found` header unless otherwise specified
45
+ # using the `:status` option:
46
+ #
47
+ # redirect_to post_url(@post), status: :found
48
+ # redirect_to action: 'atom', status: :moved_permanently
49
+ # redirect_to post_url(@post), status: 301
50
+ # redirect_to action: 'atom', status: 302
51
+ #
52
+ # The status code can either be a standard [HTTP Status
53
+ # code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
54
+ # symbol representing the downcased, underscored and symbolized description.
55
+ # Note that the status code must be a 3xx HTTP code, or redirection will not
56
+ # occur.
57
+ #
58
+ # If you are using XHR requests other than GET or POST and redirecting after the
59
+ # request then some browsers will follow the redirect using the original request
60
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
61
+ # around this you can return a `303 See Other` status code which will be
62
+ # followed using a GET request.
63
+ #
64
+ # redirect_to posts_url, status: :see_other
65
+ # redirect_to action: 'index', status: 303
66
+ #
67
+ # It is also possible to assign a flash message as part of the redirection.
68
+ # There are two special accessors for the commonly used flash names `alert` and
69
+ # `notice` as well as a general purpose `flash` bucket.
70
+ #
71
+ # redirect_to post_url(@post), alert: "Watch it, mister!"
72
+ # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
73
+ # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
74
+ # redirect_to({ action: 'atom' }, alert: "Something serious happened")
75
+ #
76
+ # Statements after `redirect_to` in our controller get executed, so
77
+ # `redirect_to` doesn't stop the execution of the function. To terminate the
78
+ # execution of the function immediately after the `redirect_to`, use return.
79
+ #
80
+ # redirect_to post_url(@post) and return
81
+ #
82
+ # ### Open Redirect protection
83
+ #
84
+ # By default, Rails protects against redirecting to external hosts for your
85
+ # app's safety, so called open redirects. Note: this was a new default in Rails
86
+ # 7.0, after upgrading opt-in by uncommenting the line with
87
+ # `raise_on_open_redirects` in
88
+ # `config/initializers/new_framework_defaults_7_0.rb`
89
+ #
90
+ # Here #redirect_to automatically validates the potentially-unsafe URL:
91
+ #
92
+ # redirect_to params[:redirect_url]
93
+ #
94
+ # Raises UnsafeRedirectError in the case of an unsafe redirect.
95
+ #
96
+ # To allow any external redirects pass `allow_other_host: true`, though using a
97
+ # user-provided param in that case is unsafe.
98
+ #
99
+ # redirect_to "https://rubyonrails.org", allow_other_host: true
100
+ #
101
+ # See #url_from for more information on what an internal and safe URL is, or how
102
+ # to fall back to an alternate redirect URL in the unsafe case.
103
+ def redirect_to(options = {}, response_options = {})
104
+ raise ActionControllerError.new("Cannot redirect to nil!") unless options
105
+ raise AbstractController::DoubleRenderError if response_body
106
+
107
+ allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
108
+
109
+ self.status = _extract_redirect_to_status(options, response_options)
110
+
111
+ redirect_to_location = _compute_redirect_to_location(request, options)
112
+ _ensure_url_is_http_header_safe(redirect_to_location)
113
+
114
+ self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
115
+ self.response_body = ""
116
+ end
117
+
118
+ # Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
119
+ # location is supplied as a keyword argument instead of the first positional
120
+ # argument.
121
+ def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
122
+ redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
123
+ end
124
+
125
+ # Redirects the browser to the page that issued the request (the referrer) if
126
+ # possible, otherwise redirects to the provided default fallback location.
127
+ #
128
+ # The referrer information is pulled from the HTTP `Referer` (sic) header on the
129
+ # request. This is an optional header and its presence on the request is subject
130
+ # to browser security settings and user preferences. If the request is missing
131
+ # this header, the `fallback_location` will be used.
132
+ #
133
+ # redirect_back_or_to({ action: "show", id: 5 })
134
+ # redirect_back_or_to @post
135
+ # redirect_back_or_to "http://www.rubyonrails.org"
136
+ # redirect_back_or_to "/images/screenshot.jpg"
137
+ # redirect_back_or_to posts_url
138
+ # redirect_back_or_to proc { edit_post_url(@post) }
139
+ # redirect_back_or_to '/', allow_other_host: false
140
+ #
141
+ # #### Options
142
+ # * `:allow_other_host` - Allow or disallow redirection to the host that is
143
+ # different to the current host, defaults to true.
144
+ #
145
+ #
146
+ # All other options that can be passed to #redirect_to are accepted as options,
147
+ # and the behavior is identical.
148
+ def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
149
+ if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
150
+ redirect_to request.referer, allow_other_host: allow_other_host, **options
151
+ else
152
+ # The method level `allow_other_host` doesn't apply in the fallback case, omit
153
+ # and let the `redirect_to` handling take over.
154
+ redirect_to fallback_location, **options
155
+ end
156
+ end
157
+
158
+ def _compute_redirect_to_location(request, options) # :nodoc:
159
+ case options
160
+ # The scheme name consist of a letter followed by any combination of letters,
161
+ # digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
162
+ # terminated by a colon (":"). See
163
+ # https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
164
+ # starts with a double slash "//".
165
+ when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
166
+ options.to_str
167
+ when String
168
+ request.protocol + request.host_with_port + options
169
+ when Proc
170
+ _compute_redirect_to_location request, instance_eval(&options)
171
+ else
172
+ url_for(options)
173
+ end.delete("\0\r\n")
174
+ end
175
+ module_function :_compute_redirect_to_location
176
+ public :_compute_redirect_to_location
177
+
178
+ # Verifies the passed `location` is an internal URL that's safe to redirect to
179
+ # and returns it, or nil if not. Useful to wrap a params provided redirect URL
180
+ # and fall back to an alternate URL to redirect to:
181
+ #
182
+ # redirect_to url_from(params[:redirect_url]) || root_url
183
+ #
184
+ # The `location` is considered internal, and safe, if it's on the same host as
185
+ # `request.host`:
186
+ #
187
+ # # If request.host is example.com:
188
+ # url_from("https://example.com/profile") # => "https://example.com/profile"
189
+ # url_from("http://example.com/profile") # => "http://example.com/profile"
190
+ # url_from("http://evil.com/profile") # => nil
191
+ #
192
+ # Subdomains are considered part of the host:
193
+ #
194
+ # # If request.host is on https://example.com or https://app.example.com, you'd get:
195
+ # url_from("https://dev.example.com/profile") # => nil
196
+ #
197
+ # NOTE: there's a similarity with
198
+ # [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
199
+ # an internal URL from various options from within the app, e.g.
200
+ # `url_for(@post)`. However, #url_from is meant to take an external parameter to
201
+ # verify as in `url_from(params[:redirect_url])`.
202
+ def url_from(location)
203
+ location = location.presence
204
+ location if location && _url_host_allowed?(location)
205
+ end
206
+
207
+ private
208
+ def _allow_other_host
209
+ !raise_on_open_redirects
210
+ end
211
+
212
+ def _extract_redirect_to_status(options, response_options)
213
+ if options.is_a?(Hash) && options.key?(:status)
214
+ Rack::Utils.status_code(options.delete(:status))
215
+ elsif response_options.key?(:status)
216
+ Rack::Utils.status_code(response_options[:status])
217
+ else
218
+ 302
219
+ end
220
+ end
221
+
222
+ def _enforce_open_redirect_protection(location, allow_other_host:)
223
+ if allow_other_host || _url_host_allowed?(location)
224
+ location
225
+ else
226
+ raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
227
+ end
228
+ end
229
+
230
+ def _url_host_allowed?(url)
231
+ host = URI(url.to_s).host
232
+
233
+ return true if host == request.host
234
+ return false unless host.nil?
235
+ return false unless url.to_s.start_with?("/")
236
+ !url.to_s.start_with?("//")
237
+ rescue ArgumentError, URI::Error
238
+ false
239
+ end
240
+
241
+ def _ensure_url_is_http_header_safe(url)
242
+ # Attempt to comply with the set of valid token characters defined for an HTTP
243
+ # header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
244
+ if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
245
+ msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
246
+ "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
247
+ raise UnsafeRedirectError, msg
248
+ end
249
+ end
250
+ end
251
+ end