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