actionpack 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +379 -462
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller.rb +0 -2
- data/lib/abstract_controller/base.rb +17 -32
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +27 -22
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/action_controller.rb +4 -3
- data/lib/action_controller/api.rb +146 -0
- data/lib/action_controller/base.rb +6 -10
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/caching/fragments.rb +48 -3
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +1 -10
- data/lib/action_controller/metal.rb +89 -62
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +65 -24
- data/lib/action_controller/metal/cookies.rb +0 -2
- data/lib/action_controller/metal/data_streaming.rb +2 -22
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +6 -6
- data/lib/action_controller/metal/head.rb +14 -7
- data/lib/action_controller/metal/helpers.rb +9 -5
- data/lib/action_controller/metal/http_authentication.rb +37 -38
- data/lib/action_controller/metal/implicit_render.rb +23 -6
- data/lib/action_controller/metal/instrumentation.rb +0 -1
- data/lib/action_controller/metal/live.rb +17 -55
- data/lib/action_controller/metal/mime_responds.rb +17 -37
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +10 -8
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
- data/lib/action_controller/metal/rescue.rb +2 -4
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +231 -78
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +267 -363
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +23 -26
- data/lib/action_dispatch/http/filter_parameters.rb +6 -8
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +28 -11
- data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
- data/lib/action_dispatch/http/mime_type.rb +92 -61
- data/lib/action_dispatch/http/mime_types.rb +1 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +45 -41
- data/lib/action_dispatch/http/request.rb +146 -82
- data/lib/action_dispatch/http/response.rb +180 -99
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +34 -28
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +37 -41
- data/lib/action_dispatch/journey/route.rb +71 -17
- data/lib/action_dispatch/journey/router.rb +5 -6
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/cookies.rb +184 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
- data/lib/action_dispatch/middleware/flash.rb +61 -45
- data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
- data/lib/action_dispatch/middleware/params_parser.rb +30 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +2 -4
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +93 -36
- data/lib/action_dispatch/middleware/stack.rb +43 -48
- data/lib/action_dispatch/middleware/static.rb +52 -40
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/request/session.rb +66 -34
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing.rb +3 -8
- data/lib/action_dispatch/routing/inspector.rb +6 -30
- data/lib/action_dispatch/routing/mapper.rb +447 -322
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +124 -227
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +27 -9
- data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
- data/lib/action_dispatch/testing/integration.rb +237 -76
- data/lib/action_dispatch/testing/test_process.rb +5 -5
- data/lib/action_dispatch/testing/test_request.rb +12 -21
- data/lib/action_dispatch/testing/test_response.rb +1 -4
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +26 -25
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,9 +1,29 @@
|
|
1
|
+
require 'active_support/core_ext/string/filters'
|
2
|
+
|
1
3
|
module ActionController
|
2
4
|
module Rendering
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
|
6
8
|
|
9
|
+
module ClassMethods
|
10
|
+
# Documentation at ActionController::Renderer#render
|
11
|
+
delegate :render, to: :renderer
|
12
|
+
|
13
|
+
# Returns a renderer instance (inherited from ActionController::Renderer)
|
14
|
+
# for the controller.
|
15
|
+
attr_reader :renderer
|
16
|
+
|
17
|
+
def setup_renderer! # :nodoc:
|
18
|
+
@renderer = Renderer.for(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inherited(klass)
|
22
|
+
klass.setup_renderer!
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
7
27
|
# Before processing, set the request formats in current controller formats.
|
8
28
|
def process_action(*) #:nodoc:
|
9
29
|
self.formats = request.formats.map(&:ref).compact
|
@@ -42,13 +62,13 @@ module ActionController
|
|
42
62
|
nil
|
43
63
|
end
|
44
64
|
|
45
|
-
def
|
46
|
-
|
65
|
+
def _set_html_content_type
|
66
|
+
self.content_type = Mime[:html].to_s
|
67
|
+
end
|
47
68
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
self.content_type ||= format.to_s
|
69
|
+
def _set_rendered_content_type(format)
|
70
|
+
unless response.content_type
|
71
|
+
self.content_type = format.to_s
|
52
72
|
end
|
53
73
|
end
|
54
74
|
|
@@ -63,11 +83,23 @@ module ActionController
|
|
63
83
|
def _normalize_options(options) #:nodoc:
|
64
84
|
_normalize_text(options)
|
65
85
|
|
86
|
+
if options[:text]
|
87
|
+
ActiveSupport::Deprecation.warn <<-WARNING.squish
|
88
|
+
`render :text` is deprecated because it does not actually render a
|
89
|
+
`text/plain` response. Switch to `render plain: 'plain text'` to
|
90
|
+
render as `text/plain`, `render html: '<strong>HTML</strong>'` to
|
91
|
+
render as `text/html`, or `render body: 'raw'` to match the deprecated
|
92
|
+
behavior and render with the default Content-Type, which is
|
93
|
+
`text/plain`.
|
94
|
+
WARNING
|
95
|
+
end
|
96
|
+
|
66
97
|
if options[:html]
|
67
98
|
options[:html] = ERB::Util.html_escape(options[:html])
|
68
99
|
end
|
69
100
|
|
70
101
|
if options.delete(:nothing)
|
102
|
+
ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.")
|
71
103
|
options[:body] = nil
|
72
104
|
end
|
73
105
|
|
@@ -13,9 +13,14 @@ module ActionController #:nodoc:
|
|
13
13
|
# by including a token in the rendered HTML for your application. This token is
|
14
14
|
# stored as a random string in the session, to which an attacker does not have
|
15
15
|
# access. When a request reaches your application, \Rails verifies the received
|
16
|
-
# token with the token in the session.
|
17
|
-
#
|
18
|
-
#
|
16
|
+
# token with the token in the session. All requests are checked except GET requests
|
17
|
+
# as these should be idempotent. Keep in mind that all session-oriented requests
|
18
|
+
# should be CSRF protected, including JavaScript and HTML requests.
|
19
|
+
#
|
20
|
+
# Since HTML and JavaScript requests are typically made from the browser, we
|
21
|
+
# need to ensure to verify request authenticity for the web browser. We can
|
22
|
+
# use session-oriented authentication for these types of requests, by using
|
23
|
+
# the `protect_from_forgery` method in our controllers.
|
19
24
|
#
|
20
25
|
# GET requests are not protected since they don't have side effects like writing
|
21
26
|
# to the database and don't leak sensitive information. JavaScript requests are
|
@@ -26,22 +31,21 @@ module ActionController #:nodoc:
|
|
26
31
|
# Ajax) requests are allowed to make GET requests for JavaScript responses.
|
27
32
|
#
|
28
33
|
# It's important to remember that XML or JSON requests are also affected and if
|
29
|
-
# you're building an API you
|
34
|
+
# you're building an API you should change forgery protection method in
|
35
|
+
# <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
|
30
36
|
#
|
31
37
|
# class ApplicationController < ActionController::Base
|
32
|
-
# protect_from_forgery
|
33
|
-
# skip_before_action :verify_authenticity_token, if: :json_request?
|
34
|
-
#
|
35
|
-
# protected
|
36
|
-
#
|
37
|
-
# def json_request?
|
38
|
-
# request.format.json?
|
39
|
-
# end
|
38
|
+
# protect_from_forgery unless: -> { request.format.json? }
|
40
39
|
# end
|
41
40
|
#
|
42
|
-
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method
|
43
|
-
#
|
44
|
-
#
|
41
|
+
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
|
42
|
+
# By default <tt>protect_from_forgery</tt> protects your session with
|
43
|
+
# <tt>:null_session</tt> method, which provides an empty session
|
44
|
+
# during request.
|
45
|
+
#
|
46
|
+
# We may want to disable CSRF protection for APIs since they are typically
|
47
|
+
# designed to be state-less. That is, the request API client will handle
|
48
|
+
# the session for you instead of Rails.
|
45
49
|
#
|
46
50
|
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
47
51
|
# value of this token must be added to every layout that renders forms by including
|
@@ -73,6 +77,10 @@ module ActionController #:nodoc:
|
|
73
77
|
config_accessor :log_warning_on_csrf_failure
|
74
78
|
self.log_warning_on_csrf_failure = true
|
75
79
|
|
80
|
+
# Controls whether the Origin header is checked in addition to the CSRF token.
|
81
|
+
config_accessor :forgery_protection_origin_check
|
82
|
+
self.forgery_protection_origin_check = false
|
83
|
+
|
76
84
|
helper_method :form_authenticity_token
|
77
85
|
helper_method :protect_against_forgery?
|
78
86
|
end
|
@@ -86,13 +94,21 @@ module ActionController #:nodoc:
|
|
86
94
|
#
|
87
95
|
# class FooController < ApplicationController
|
88
96
|
# protect_from_forgery except: :index
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# You can disable forgery protection on controller by skipping the verification before_action:
|
89
100
|
#
|
90
|
-
# You can disable CSRF protection on controller by skipping the verification before_action:
|
91
101
|
# skip_before_action :verify_authenticity_token
|
92
102
|
#
|
93
103
|
# Valid Options:
|
94
104
|
#
|
95
|
-
# * <tt>:only/:except</tt> -
|
105
|
+
# * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
|
106
|
+
# * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
|
107
|
+
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
|
108
|
+
# protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
|
109
|
+
# when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
|
110
|
+
#
|
111
|
+
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
96
112
|
# * <tt>:with</tt> - Set the method to handle unverified request.
|
97
113
|
#
|
98
114
|
# Valid unverified request handling methods are:
|
@@ -100,9 +116,11 @@ module ActionController #:nodoc:
|
|
100
116
|
# * <tt>:reset_session</tt> - Resets the session.
|
101
117
|
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
|
102
118
|
def protect_from_forgery(options = {})
|
119
|
+
options = options.reverse_merge(prepend: false)
|
120
|
+
|
103
121
|
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
|
104
122
|
self.request_forgery_protection_token ||= :authenticity_token
|
105
|
-
|
123
|
+
before_action :verify_authenticity_token, options
|
106
124
|
append_after_action :verify_same_origin_request
|
107
125
|
end
|
108
126
|
|
@@ -124,17 +142,17 @@ module ActionController #:nodoc:
|
|
124
142
|
# This is the method that defines the application behavior when a request is found to be unverified.
|
125
143
|
def handle_unverified_request
|
126
144
|
request = @controller.request
|
127
|
-
request.session = NullSessionHash.new(request
|
128
|
-
request.
|
129
|
-
request.
|
130
|
-
request.
|
145
|
+
request.session = NullSessionHash.new(request)
|
146
|
+
request.flash = nil
|
147
|
+
request.session_options = { skip: true }
|
148
|
+
request.cookie_jar = NullCookieJar.build(request, {})
|
131
149
|
end
|
132
150
|
|
133
151
|
protected
|
134
152
|
|
135
153
|
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
|
136
|
-
def initialize(
|
137
|
-
super(nil,
|
154
|
+
def initialize(req)
|
155
|
+
super(nil, req)
|
138
156
|
@data = {}
|
139
157
|
@loaded = true
|
140
158
|
end
|
@@ -148,14 +166,6 @@ module ActionController #:nodoc:
|
|
148
166
|
end
|
149
167
|
|
150
168
|
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
|
151
|
-
def self.build(request)
|
152
|
-
key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
|
153
|
-
host = request.host
|
154
|
-
secure = request.ssl?
|
155
|
-
|
156
|
-
new(key_generator, host, secure, options_for_env({}))
|
157
|
-
end
|
158
|
-
|
159
169
|
def write(*)
|
160
170
|
# nothing
|
161
171
|
end
|
@@ -246,13 +256,24 @@ module ActionController #:nodoc:
|
|
246
256
|
|
247
257
|
# Returns true or false if a request is verified. Checks:
|
248
258
|
#
|
249
|
-
# *
|
259
|
+
# * Is it a GET or HEAD request? Gets should be safe and idempotent
|
250
260
|
# * Does the form_authenticity_token match the given token value from the params?
|
251
261
|
# * Does the X-CSRF-Token header match the form_authenticity_token
|
252
262
|
def verified_request?
|
253
263
|
!protect_against_forgery? || request.get? || request.head? ||
|
254
|
-
|
255
|
-
|
264
|
+
(valid_request_origin? && any_authenticity_token_valid?)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Checks if any of the authenticity tokens from the request are valid.
|
268
|
+
def any_authenticity_token_valid?
|
269
|
+
request_authenticity_tokens.any? do |token|
|
270
|
+
valid_authenticity_token?(session, token)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Possible authenticity tokens sent in the request.
|
275
|
+
def request_authenticity_tokens
|
276
|
+
[form_authenticity_param, request.x_csrf_token]
|
256
277
|
end
|
257
278
|
|
258
279
|
# Sets the token value for the current session.
|
@@ -330,5 +351,16 @@ module ActionController #:nodoc:
|
|
330
351
|
def protect_against_forgery?
|
331
352
|
allow_forgery_protection
|
332
353
|
end
|
354
|
+
|
355
|
+
# Checks if the request originated from the same origin by looking at the
|
356
|
+
# Origin header.
|
357
|
+
def valid_request_origin?
|
358
|
+
if forgery_protection_origin_check
|
359
|
+
# We accept blank origin headers because some user agents don't send it.
|
360
|
+
request.origin.nil? || request.origin == request.base_url
|
361
|
+
else
|
362
|
+
true
|
363
|
+
end
|
364
|
+
end
|
333
365
|
end
|
334
366
|
end
|
@@ -7,10 +7,8 @@ module ActionController #:nodoc:
|
|
7
7
|
include ActiveSupport::Rescuable
|
8
8
|
|
9
9
|
def rescue_with_handler(exception)
|
10
|
-
if (exception.
|
11
|
-
|
12
|
-
handler_for_rescue(orig_exception))
|
13
|
-
exception = orig_exception
|
10
|
+
if exception.cause && handler_for_rescue(exception.cause)
|
11
|
+
exception = exception.cause
|
14
12
|
end
|
15
13
|
super(exception)
|
16
14
|
end
|
@@ -110,9 +110,9 @@ module ActionController #:nodoc:
|
|
110
110
|
# This means that, if you have <code>yield :title</code> in your layout
|
111
111
|
# and you want to use streaming, you would have to render the whole template
|
112
112
|
# (and eventually trigger all queries) before streaming the title and all
|
113
|
-
# assets, which kills the purpose of streaming. For this
|
114
|
-
#
|
115
|
-
#
|
113
|
+
# assets, which kills the purpose of streaming. For this purpose, you can use
|
114
|
+
# a helper called +provide+ that does the same as +content_for+ but tells the
|
115
|
+
# layout to stop searching for other entries and continue rendering.
|
116
116
|
#
|
117
117
|
# For instance, the template above using +provide+ would be:
|
118
118
|
#
|
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
|
|
199
199
|
def _process_options(options) #:nodoc:
|
200
200
|
super
|
201
201
|
if options[:stream]
|
202
|
-
if
|
202
|
+
if request.version == "HTTP/1.0"
|
203
203
|
options.delete(:stream)
|
204
204
|
else
|
205
205
|
headers["Cache-Control"] ||= "no-cache"
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'active_support/core_ext/hash/transform_values'
|
2
3
|
require 'active_support/core_ext/array/wrap'
|
3
4
|
require 'active_support/core_ext/string/filters'
|
4
|
-
require 'active_support/deprecation'
|
5
5
|
require 'active_support/rescuable'
|
6
6
|
require 'action_dispatch/http/upload'
|
7
|
+
require 'rack/test'
|
7
8
|
require 'stringio'
|
8
9
|
require 'set'
|
9
10
|
|
@@ -12,9 +13,9 @@ module ActionController
|
|
12
13
|
#
|
13
14
|
# params = ActionController::Parameters.new(a: {})
|
14
15
|
# params.fetch(:b)
|
15
|
-
# # => ActionController::ParameterMissing: param
|
16
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty: b
|
16
17
|
# params.require(:a)
|
17
|
-
# # => ActionController::ParameterMissing: param
|
18
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty: a
|
18
19
|
class ParameterMissing < KeyError
|
19
20
|
attr_reader :param # :nodoc:
|
20
21
|
|
@@ -98,17 +99,18 @@ module ActionController
|
|
98
99
|
# environment they should only be set once at boot-time and never mutated at
|
99
100
|
# runtime.
|
100
101
|
#
|
101
|
-
# <tt>ActionController::Parameters</tt>
|
102
|
-
# <tt
|
103
|
-
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
|
102
|
+
# You can fetch values of <tt>ActionController::Parameters</tt> using either
|
103
|
+
# <tt>:key</tt> or <tt>"key"</tt>.
|
104
104
|
#
|
105
105
|
# params = ActionController::Parameters.new(key: 'value')
|
106
106
|
# params[:key] # => "value"
|
107
107
|
# params["key"] # => "value"
|
108
|
-
class Parameters
|
108
|
+
class Parameters
|
109
109
|
cattr_accessor :permit_all_parameters, instance_accessor: false
|
110
110
|
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
|
111
111
|
|
112
|
+
delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
|
113
|
+
|
112
114
|
# By default, never raise an UnpermittedParameters exception if these
|
113
115
|
# params are present. The default includes both 'controller' and 'action'
|
114
116
|
# because they are added by Rails and should be of no concern. One way
|
@@ -120,7 +122,7 @@ module ActionController
|
|
120
122
|
self.always_permitted_parameters = %w( controller action )
|
121
123
|
|
122
124
|
def self.const_missing(const_name)
|
123
|
-
super unless const_name == :NEVER_UNPERMITTED_PARAMS
|
125
|
+
return super unless const_name == :NEVER_UNPERMITTED_PARAMS
|
124
126
|
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
125
127
|
`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
|
126
128
|
Use `ActionController::Parameters.always_permitted_parameters` instead.
|
@@ -145,13 +147,24 @@ module ActionController
|
|
145
147
|
# params = ActionController::Parameters.new(name: 'Francesco')
|
146
148
|
# params.permitted? # => true
|
147
149
|
# Person.new(params) # => #<Person id: nil, name: "Francesco">
|
148
|
-
def initialize(
|
149
|
-
|
150
|
+
def initialize(parameters = {})
|
151
|
+
@parameters = parameters.with_indifferent_access
|
150
152
|
@permitted = self.class.permit_all_parameters
|
151
153
|
end
|
152
154
|
|
153
|
-
# Returns
|
154
|
-
#
|
155
|
+
# Returns true if another +Parameters+ object contains the same content and
|
156
|
+
# permitted flag, or other Hash-like object contains the same content. This
|
157
|
+
# override is in place so you can perform a comparison with `Hash`.
|
158
|
+
def ==(other_hash)
|
159
|
+
if other_hash.respond_to?(:permitted?)
|
160
|
+
super
|
161
|
+
else
|
162
|
+
@parameters == other_hash
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
|
167
|
+
# representation of this parameter with all unpermitted keys removed.
|
155
168
|
#
|
156
169
|
# params = ActionController::Parameters.new({
|
157
170
|
# name: 'Senjougahara Hitagi',
|
@@ -163,28 +176,27 @@ module ActionController
|
|
163
176
|
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
|
164
177
|
def to_h
|
165
178
|
if permitted?
|
166
|
-
|
179
|
+
convert_parameters_to_hashes(@parameters)
|
167
180
|
else
|
168
181
|
slice(*self.class.always_permitted_parameters).permit!.to_h
|
169
182
|
end
|
170
183
|
end
|
171
184
|
|
172
|
-
# Returns an unsafe, unfiltered
|
185
|
+
# Returns an unsafe, unfiltered
|
186
|
+
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
|
187
|
+
# parameter.
|
173
188
|
def to_unsafe_h
|
174
|
-
|
189
|
+
convert_parameters_to_hashes(@parameters)
|
175
190
|
end
|
176
191
|
alias_method :to_unsafe_hash, :to_unsafe_h
|
177
192
|
|
178
193
|
# Convert all hashes in values into parameters, then yield each pair like
|
179
194
|
# the same way as <tt>Hash#each_pair</tt>
|
180
195
|
def each_pair(&block)
|
181
|
-
|
182
|
-
convert_hashes_to_parameters(key, value)
|
196
|
+
@parameters.each_pair do |key, value|
|
197
|
+
yield key, convert_hashes_to_parameters(key, value)
|
183
198
|
end
|
184
|
-
|
185
|
-
super
|
186
199
|
end
|
187
|
-
|
188
200
|
alias_method :each, :each_pair
|
189
201
|
|
190
202
|
# Attribute that keeps track of converted arrays, if any, to avoid double
|
@@ -231,19 +243,58 @@ module ActionController
|
|
231
243
|
self
|
232
244
|
end
|
233
245
|
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
246
|
+
# This method accepts both a single key and an array of keys.
|
247
|
+
#
|
248
|
+
# When passed a single key, if it exists and its associated value is
|
249
|
+
# either present or the singleton +false+, returns said value:
|
237
250
|
#
|
238
251
|
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
|
239
252
|
# # => {"name"=>"Francesco"}
|
240
253
|
#
|
254
|
+
# Otherwise raises <tt>ActionController::ParameterMissing</tt>:
|
255
|
+
#
|
256
|
+
# ActionController::Parameters.new.require(:person)
|
257
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
258
|
+
#
|
241
259
|
# ActionController::Parameters.new(person: nil).require(:person)
|
242
|
-
# #
|
260
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
261
|
+
#
|
262
|
+
# ActionController::Parameters.new(person: "\t").require(:person)
|
263
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
243
264
|
#
|
244
265
|
# ActionController::Parameters.new(person: {}).require(:person)
|
245
|
-
# #
|
266
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
267
|
+
#
|
268
|
+
# When given an array of keys, the method tries to require each one of them
|
269
|
+
# in order. If it succeeds, an array with the respective return values is
|
270
|
+
# returned:
|
271
|
+
#
|
272
|
+
# params = ActionController::Parameters.new(user: { ... }, profile: { ... })
|
273
|
+
# user_params, profile_params = params.require(:user, :profile)
|
274
|
+
#
|
275
|
+
# Otherwise, the method reraises the first exception found:
|
276
|
+
#
|
277
|
+
# params = ActionController::Parameters.new(user: {}, profile: {})
|
278
|
+
# user_params, profile_params = params.require(:user, :profile)
|
279
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty: user
|
280
|
+
#
|
281
|
+
# Technically this method can be used to fetch terminal values:
|
282
|
+
#
|
283
|
+
# # CAREFUL
|
284
|
+
# params = ActionController::Parameters.new(person: { name: 'Finn' })
|
285
|
+
# name = params.require(:person).require(:name) # CAREFUL
|
286
|
+
#
|
287
|
+
# but take into account that at some point those ones have to be permitted:
|
288
|
+
#
|
289
|
+
# def person_params
|
290
|
+
# params.require(:person).permit(:name).tap do |person_params|
|
291
|
+
# person_params.require(:name) # SAFER
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# for example.
|
246
296
|
def require(key)
|
297
|
+
return key.map { |k| require(k) } if key.is_a?(Array)
|
247
298
|
value = self[key]
|
248
299
|
if value.present? || value == false
|
249
300
|
value
|
@@ -271,7 +322,7 @@ module ActionController
|
|
271
322
|
#
|
272
323
|
# params.permit(:name)
|
273
324
|
#
|
274
|
-
# +:name+ passes it is a key of +params+ whose associated value is of type
|
325
|
+
# +:name+ passes if it is a key of +params+ whose associated value is of type
|
275
326
|
# +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
|
276
327
|
# +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
|
277
328
|
# +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
|
@@ -348,7 +399,13 @@ module ActionController
|
|
348
399
|
# params[:person] # => {"name"=>"Francesco"}
|
349
400
|
# params[:none] # => nil
|
350
401
|
def [](key)
|
351
|
-
convert_hashes_to_parameters(key,
|
402
|
+
convert_hashes_to_parameters(key, @parameters[key])
|
403
|
+
end
|
404
|
+
|
405
|
+
# Assigns a value to a given +key+. The given key may still get filtered out
|
406
|
+
# when +permit+ is called.
|
407
|
+
def []=(key, value)
|
408
|
+
@parameters[key] = value
|
352
409
|
end
|
353
410
|
|
354
411
|
# Returns a parameter for the given +key+. If the +key+
|
@@ -359,13 +416,19 @@ module ActionController
|
|
359
416
|
#
|
360
417
|
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
|
361
418
|
# params.fetch(:person) # => {"name"=>"Francesco"}
|
362
|
-
# params.fetch(:none) # => ActionController::ParameterMissing: param
|
419
|
+
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
|
363
420
|
# params.fetch(:none, 'Francesco') # => "Francesco"
|
364
421
|
# params.fetch(:none) { 'Francesco' } # => "Francesco"
|
365
|
-
def fetch(key, *args)
|
366
|
-
|
367
|
-
|
368
|
-
|
422
|
+
def fetch(key, *args, &block)
|
423
|
+
convert_value_to_parameters(
|
424
|
+
@parameters.fetch(key) {
|
425
|
+
if block_given?
|
426
|
+
yield
|
427
|
+
else
|
428
|
+
args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
|
429
|
+
end
|
430
|
+
}
|
431
|
+
)
|
369
432
|
end
|
370
433
|
|
371
434
|
# Returns a new <tt>ActionController::Parameters</tt> instance that
|
@@ -376,7 +439,24 @@ module ActionController
|
|
376
439
|
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
|
377
440
|
# params.slice(:d) # => {}
|
378
441
|
def slice(*keys)
|
379
|
-
new_instance_with_inherited_permitted_status(
|
442
|
+
new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
|
443
|
+
end
|
444
|
+
|
445
|
+
# Returns current <tt>ActionController::Parameters</tt> instance which
|
446
|
+
# contains only the given +keys+.
|
447
|
+
def slice!(*keys)
|
448
|
+
@parameters.slice!(*keys)
|
449
|
+
self
|
450
|
+
end
|
451
|
+
|
452
|
+
# Returns a new <tt>ActionController::Parameters</tt> instance that
|
453
|
+
# filters out the given +keys+.
|
454
|
+
#
|
455
|
+
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
456
|
+
# params.except(:a, :b) # => {"c"=>3}
|
457
|
+
# params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
|
458
|
+
def except(*keys)
|
459
|
+
new_instance_with_inherited_permitted_status(@parameters.except(*keys))
|
380
460
|
end
|
381
461
|
|
382
462
|
# Removes and returns the key/value pairs matching the given keys.
|
@@ -385,7 +465,7 @@ module ActionController
|
|
385
465
|
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
|
386
466
|
# params # => {"c"=>3}
|
387
467
|
def extract!(*keys)
|
388
|
-
new_instance_with_inherited_permitted_status(
|
468
|
+
new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
|
389
469
|
end
|
390
470
|
|
391
471
|
# Returns a new <tt>ActionController::Parameters</tt> with the results of
|
@@ -394,36 +474,80 @@ module ActionController
|
|
394
474
|
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
395
475
|
# params.transform_values { |x| x * 2 }
|
396
476
|
# # => {"a"=>2, "b"=>4, "c"=>6}
|
397
|
-
def transform_values
|
398
|
-
if
|
399
|
-
new_instance_with_inherited_permitted_status(
|
477
|
+
def transform_values(&block)
|
478
|
+
if block
|
479
|
+
new_instance_with_inherited_permitted_status(
|
480
|
+
@parameters.transform_values(&block)
|
481
|
+
)
|
400
482
|
else
|
401
|
-
|
483
|
+
@parameters.transform_values
|
402
484
|
end
|
403
485
|
end
|
404
486
|
|
405
|
-
#
|
406
|
-
#
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
487
|
+
# Performs values transformation and returns the altered
|
488
|
+
# <tt>ActionController::Parameters</tt> instance.
|
489
|
+
def transform_values!(&block)
|
490
|
+
@parameters.transform_values!(&block)
|
491
|
+
self
|
492
|
+
end
|
493
|
+
|
494
|
+
# Returns a new <tt>ActionController::Parameters</tt> instance with the
|
495
|
+
# results of running +block+ once for every key. The values are unchanged.
|
496
|
+
def transform_keys(&block)
|
497
|
+
if block
|
498
|
+
new_instance_with_inherited_permitted_status(
|
499
|
+
@parameters.transform_keys(&block)
|
500
|
+
)
|
411
501
|
else
|
412
|
-
|
502
|
+
@parameters.transform_keys
|
413
503
|
end
|
414
504
|
end
|
415
505
|
|
506
|
+
# Performs keys transformation and returns the altered
|
507
|
+
# <tt>ActionController::Parameters</tt> instance.
|
508
|
+
def transform_keys!(&block)
|
509
|
+
@parameters.transform_keys!(&block)
|
510
|
+
self
|
511
|
+
end
|
512
|
+
|
416
513
|
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
|
417
514
|
# to key. If the key is not found, returns the default value. If the
|
418
515
|
# optional code block is given and the key is not found, pass in the key
|
419
516
|
# and return the result of block.
|
420
517
|
def delete(key, &block)
|
421
|
-
|
518
|
+
convert_value_to_parameters(@parameters.delete(key))
|
519
|
+
end
|
520
|
+
|
521
|
+
# Returns a new instance of <tt>ActionController::Parameters</tt> with only
|
522
|
+
# items that the block evaluates to true.
|
523
|
+
def select(&block)
|
524
|
+
new_instance_with_inherited_permitted_status(@parameters.select(&block))
|
422
525
|
end
|
423
526
|
|
424
527
|
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
|
425
528
|
def select!(&block)
|
426
|
-
|
529
|
+
@parameters.select!(&block)
|
530
|
+
self
|
531
|
+
end
|
532
|
+
alias_method :keep_if, :select!
|
533
|
+
|
534
|
+
# Returns a new instance of <tt>ActionController::Parameters</tt> with items
|
535
|
+
# that the block evaluates to true removed.
|
536
|
+
def reject(&block)
|
537
|
+
new_instance_with_inherited_permitted_status(@parameters.reject(&block))
|
538
|
+
end
|
539
|
+
|
540
|
+
# Removes items that the block evaluates to true and returns self.
|
541
|
+
def reject!(&block)
|
542
|
+
@parameters.reject!(&block)
|
543
|
+
self
|
544
|
+
end
|
545
|
+
alias_method :delete_if, :reject!
|
546
|
+
|
547
|
+
# Returns values that were assigned to the given +keys+. Note that all the
|
548
|
+
# +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
|
549
|
+
def values_at(*keys)
|
550
|
+
convert_value_to_parameters(@parameters.values_at(*keys))
|
427
551
|
end
|
428
552
|
|
429
553
|
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
|
@@ -440,11 +564,30 @@ module ActionController
|
|
440
564
|
end
|
441
565
|
end
|
442
566
|
|
567
|
+
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
|
568
|
+
# +other_hash+ merges into current hash.
|
569
|
+
def merge(other_hash)
|
570
|
+
new_instance_with_inherited_permitted_status(
|
571
|
+
@parameters.merge(other_hash)
|
572
|
+
)
|
573
|
+
end
|
574
|
+
|
575
|
+
# This is required by ActiveModel attribute assignment, so that user can
|
576
|
+
# pass +Parameters+ to a mass assignment methods in a model. It should not
|
577
|
+
# matter as we are using +HashWithIndifferentAccess+ internally.
|
578
|
+
def stringify_keys # :nodoc:
|
579
|
+
dup
|
580
|
+
end
|
581
|
+
|
443
582
|
protected
|
444
583
|
def permitted=(new_permitted)
|
445
584
|
@permitted = new_permitted
|
446
585
|
end
|
447
586
|
|
587
|
+
def fields_for_style?
|
588
|
+
@parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
|
589
|
+
end
|
590
|
+
|
448
591
|
private
|
449
592
|
def new_instance_with_inherited_permitted_status(hash)
|
450
593
|
self.class.new(hash).tap do |new_instance|
|
@@ -452,40 +595,56 @@ module ActionController
|
|
452
595
|
end
|
453
596
|
end
|
454
597
|
|
455
|
-
def
|
598
|
+
def convert_parameters_to_hashes(value)
|
599
|
+
case value
|
600
|
+
when Array
|
601
|
+
value.map { |v| convert_parameters_to_hashes(v) }
|
602
|
+
when Hash
|
603
|
+
value.transform_values do |v|
|
604
|
+
convert_parameters_to_hashes(v)
|
605
|
+
end.with_indifferent_access
|
606
|
+
when Parameters
|
607
|
+
value.to_h
|
608
|
+
else
|
609
|
+
value
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
def convert_hashes_to_parameters(key, value)
|
456
614
|
converted = convert_value_to_parameters(value)
|
457
|
-
|
615
|
+
@parameters[key] = converted unless converted.equal?(value)
|
458
616
|
converted
|
459
617
|
end
|
460
618
|
|
461
619
|
def convert_value_to_parameters(value)
|
462
|
-
|
620
|
+
case value
|
621
|
+
when Array
|
622
|
+
return value if converted_arrays.member?(value)
|
463
623
|
converted = value.map { |_| convert_value_to_parameters(_) }
|
464
624
|
converted_arrays << converted
|
465
625
|
converted
|
466
|
-
|
467
|
-
value
|
468
|
-
else
|
626
|
+
when Hash
|
469
627
|
self.class.new(value)
|
628
|
+
else
|
629
|
+
value
|
470
630
|
end
|
471
631
|
end
|
472
632
|
|
473
633
|
def each_element(object)
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
object.
|
479
|
-
|
480
|
-
|
481
|
-
|
634
|
+
case object
|
635
|
+
when Array
|
636
|
+
object.grep(Parameters).map { |el| yield el }.compact
|
637
|
+
when Parameters
|
638
|
+
if object.fields_for_style?
|
639
|
+
hash = object.class.new
|
640
|
+
object.each { |k,v| hash[k] = yield v }
|
641
|
+
hash
|
642
|
+
else
|
643
|
+
yield object
|
644
|
+
end
|
482
645
|
end
|
483
646
|
end
|
484
647
|
|
485
|
-
def fields_for_style?(object)
|
486
|
-
object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
|
487
|
-
end
|
488
|
-
|
489
648
|
def unpermitted_parameters!(params)
|
490
649
|
unpermitted_keys = unpermitted_keys(params)
|
491
650
|
if unpermitted_keys.any?
|
@@ -547,14 +706,8 @@ module ActionController
|
|
547
706
|
end
|
548
707
|
|
549
708
|
def array_of_permitted_scalars?(value)
|
550
|
-
if value.is_a?(Array)
|
551
|
-
value
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
def array_of_permitted_scalars_filter(params, key)
|
556
|
-
if has_key?(key) && array_of_permitted_scalars?(self[key])
|
557
|
-
params[key] = self[key]
|
709
|
+
if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
|
710
|
+
yield value
|
558
711
|
end
|
559
712
|
end
|
560
713
|
|
@@ -565,17 +718,17 @@ module ActionController
|
|
565
718
|
# Slicing filters out non-declared keys.
|
566
719
|
slice(*filter.keys).each do |key, value|
|
567
720
|
next unless value
|
721
|
+
next unless has_key? key
|
568
722
|
|
569
723
|
if filter[key] == EMPTY_ARRAY
|
570
724
|
# Declaration { comment_ids: [] }.
|
571
|
-
|
725
|
+
array_of_permitted_scalars?(self[key]) do |val|
|
726
|
+
params[key] = val
|
727
|
+
end
|
572
728
|
else
|
573
729
|
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
574
730
|
params[key] = each_element(value) do |element|
|
575
|
-
|
576
|
-
element = self.class.new(element) unless element.respond_to?(:permit)
|
577
|
-
element.permit(*Array.wrap(filter[key]))
|
578
|
-
end
|
731
|
+
element.permit(*Array.wrap(filter[key]))
|
579
732
|
end
|
580
733
|
end
|
581
734
|
end
|