actionpack 4.1.7 → 4.2.11
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 +404 -451
- data/README.rdoc +7 -2
- data/lib/abstract_controller/base.rb +16 -6
- data/lib/abstract_controller/callbacks.rb +28 -51
- data/lib/abstract_controller/helpers.rb +11 -4
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +7 -1
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/action_controller/base.rb +3 -2
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/caching.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +26 -26
- data/lib/action_controller/metal/conditional_get.rb +37 -12
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/force_ssl.rb +1 -1
- data/lib/action_controller/metal/head.rb +7 -3
- data/lib/action_controller/metal/http_authentication.rb +20 -10
- data/lib/action_controller/metal/instrumentation.rb +8 -5
- data/lib/action_controller/metal/live.rb +57 -6
- data/lib/action_controller/metal/mime_responds.rb +25 -246
- data/lib/action_controller/metal/params_wrapper.rb +5 -5
- data/lib/action_controller/metal/rack_delegation.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +14 -8
- data/lib/action_controller/metal/renderers.rb +29 -11
- data/lib/action_controller/metal/rendering.rb +2 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
- data/lib/action_controller/metal/streaming.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +129 -14
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/metal.rb +12 -11
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +4 -0
- data/lib/action_controller/test_case.rb +119 -75
- data/lib/action_controller.rb +1 -1
- data/lib/action_dispatch/http/cache.rb +5 -4
- data/lib/action_dispatch/http/filter_parameters.rb +2 -2
- data/lib/action_dispatch/http/headers.rb +43 -9
- data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
- data/lib/action_dispatch/http/mime_type.rb +18 -4
- data/lib/action_dispatch/http/parameter_filter.rb +1 -1
- data/lib/action_dispatch/http/parameters.rb +11 -26
- data/lib/action_dispatch/http/request.rb +37 -11
- data/lib/action_dispatch/http/response.rb +74 -23
- data/lib/action_dispatch/http/upload.rb +9 -8
- data/lib/action_dispatch/http/url.rb +89 -70
- data/lib/action_dispatch/journey/formatter.rb +34 -18
- data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
- data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
- data/lib/action_dispatch/journey/nodes/node.rb +4 -0
- data/lib/action_dispatch/journey/parser.rb +52 -60
- data/lib/action_dispatch/journey/parser.y +11 -10
- data/lib/action_dispatch/journey/path/pattern.rb +16 -19
- data/lib/action_dispatch/journey/route.rb +4 -19
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router/utils.rb +1 -1
- data/lib/action_dispatch/journey/router.rb +53 -77
- data/lib/action_dispatch/journey/routes.rb +4 -0
- data/lib/action_dispatch/journey/scanner.rb +5 -5
- data/lib/action_dispatch/journey/visitors.rb +81 -92
- data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/callbacks.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +34 -34
- data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
- data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
- data/lib/action_dispatch/middleware/flash.rb +13 -7
- data/lib/action_dispatch/middleware/params_parser.rb +1 -1
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
- data/lib/action_dispatch/middleware/ssl.rb +1 -1
- data/lib/action_dispatch/middleware/static.rb +75 -39
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
- data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
- data/lib/action_dispatch/railtie.rb +2 -0
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +5 -12
- data/lib/action_dispatch/routing/mapper.rb +414 -283
- data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
- data/lib/action_dispatch/routing/redirection.rb +10 -12
- data/lib/action_dispatch/routing/route_set.rb +300 -173
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
- data/lib/action_dispatch/routing/url_for.rb +17 -5
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
- data/lib/action_dispatch/testing/assertions/response.rb +2 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +28 -20
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +55 -13
- data/lib/action_controller/metal/responder.rb +0 -297
@@ -5,8 +5,8 @@ require 'active_support/core_ext/struct'
|
|
5
5
|
require 'action_dispatch/http/mime_type'
|
6
6
|
|
7
7
|
module ActionController
|
8
|
-
# Wraps the parameters hash into a nested hash. This will allow clients to
|
9
|
-
#
|
8
|
+
# Wraps the parameters hash into a nested hash. This will allow clients to
|
9
|
+
# submit requests without having to specify any root elements.
|
10
10
|
#
|
11
11
|
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
|
12
12
|
# and can be customized. If you are upgrading to \Rails 3.1, this file will
|
@@ -16,7 +16,7 @@ module ActionController
|
|
16
16
|
# a non-empty array:
|
17
17
|
#
|
18
18
|
# class UsersController < ApplicationController
|
19
|
-
# wrap_parameters format: [:json, :xml]
|
19
|
+
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
20
20
|
# end
|
21
21
|
#
|
22
22
|
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
|
@@ -244,7 +244,7 @@ module ActionController
|
|
244
244
|
request.parameters.merge! wrapped_hash
|
245
245
|
request.request_parameters.merge! wrapped_hash
|
246
246
|
|
247
|
-
# This will
|
247
|
+
# This will display the wrapped hash in the log file
|
248
248
|
request.filtered_parameters.merge! wrapped_filtered_hash
|
249
249
|
end
|
250
250
|
super
|
@@ -252,7 +252,7 @@ module ActionController
|
|
252
252
|
|
253
253
|
private
|
254
254
|
|
255
|
-
# Returns the wrapper key which will
|
255
|
+
# Returns the wrapper key which will be used to stored wrapped parameters.
|
256
256
|
def _wrapper_key
|
257
257
|
_wrapper_options.name
|
258
258
|
end
|
@@ -6,7 +6,7 @@ module ActionController
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
delegate :headers, :status=, :location=, :content_type=,
|
9
|
-
:status, :location, :content_type, :
|
9
|
+
:status, :location, :content_type, :response_code, :to => "@_response"
|
10
10
|
|
11
11
|
def dispatch(action, request)
|
12
12
|
set_response!(request)
|
@@ -14,7 +14,7 @@ module ActionController
|
|
14
14
|
include ActionController::RackDelegation
|
15
15
|
include ActionController::UrlFor
|
16
16
|
|
17
|
-
# Redirects the browser to the target specified in +options+. This parameter can
|
17
|
+
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
18
18
|
#
|
19
19
|
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
20
20
|
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
@@ -24,6 +24,8 @@ module ActionController
|
|
24
24
|
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
25
25
|
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
|
26
26
|
#
|
27
|
+
# === Examples:
|
28
|
+
#
|
27
29
|
# redirect_to action: "show", id: 5
|
28
30
|
# redirect_to post
|
29
31
|
# redirect_to "http://www.rubyonrails.org"
|
@@ -32,7 +34,7 @@ module ActionController
|
|
32
34
|
# redirect_to :back
|
33
35
|
# redirect_to proc { edit_post_url(@post) }
|
34
36
|
#
|
35
|
-
# The redirection happens as a "302 Found" header unless otherwise specified
|
37
|
+
# The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
|
36
38
|
#
|
37
39
|
# redirect_to post_url(@post), status: :found
|
38
40
|
# redirect_to action: 'atom', status: :moved_permanently
|
@@ -60,19 +62,21 @@ module ActionController
|
|
60
62
|
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
61
63
|
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
62
64
|
#
|
63
|
-
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
64
|
-
#
|
65
|
+
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
66
|
+
# <tt>ActionController::RedirectBackError</tt> will be raised. You
|
67
|
+
# may specify some fallback behavior for this case by rescuing
|
68
|
+
# <tt>ActionController::RedirectBackError</tt>.
|
65
69
|
def redirect_to(options = {}, response_status = {}) #:doc:
|
66
70
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
67
71
|
raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
|
68
72
|
raise AbstractController::DoubleRenderError if response_body
|
69
73
|
|
70
74
|
self.status = _extract_redirect_to_status(options, response_status)
|
71
|
-
self.location = _compute_redirect_to_location(options)
|
72
|
-
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.
|
75
|
+
self.location = _compute_redirect_to_location(request, options)
|
76
|
+
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
|
73
77
|
end
|
74
78
|
|
75
|
-
def _compute_redirect_to_location(options) #:nodoc:
|
79
|
+
def _compute_redirect_to_location(request, options) #:nodoc:
|
76
80
|
case options
|
77
81
|
# The scheme name consist of a letter followed by any combination of
|
78
82
|
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
@@ -86,11 +90,13 @@ module ActionController
|
|
86
90
|
when :back
|
87
91
|
request.headers["Referer"] or raise RedirectBackError
|
88
92
|
when Proc
|
89
|
-
_compute_redirect_to_location options.call
|
93
|
+
_compute_redirect_to_location request, options.call
|
90
94
|
else
|
91
95
|
url_for(options)
|
92
96
|
end.delete("\0\r\n")
|
93
97
|
end
|
98
|
+
module_function :_compute_redirect_to_location
|
99
|
+
public :_compute_redirect_to_location
|
94
100
|
|
95
101
|
private
|
96
102
|
def _extract_redirect_to_status(options, response_status)
|
@@ -6,6 +6,11 @@ module ActionController
|
|
6
6
|
Renderers.add(key, &block)
|
7
7
|
end
|
8
8
|
|
9
|
+
# See <tt>Renderers.remove</tt>
|
10
|
+
def self.remove_renderer(key)
|
11
|
+
Renderers.remove(key)
|
12
|
+
end
|
13
|
+
|
9
14
|
class MissingRenderer < LoadError
|
10
15
|
def initialize(format)
|
11
16
|
super "No renderer defined for format: #{format}"
|
@@ -29,23 +34,28 @@ module ActionController
|
|
29
34
|
end
|
30
35
|
|
31
36
|
def render_to_body(options)
|
32
|
-
|
37
|
+
_render_to_body_with_renderer(options) || super
|
33
38
|
end
|
34
39
|
|
35
|
-
def
|
40
|
+
def _render_to_body_with_renderer(options)
|
36
41
|
_renderers.each do |name|
|
37
42
|
if options.key?(name)
|
38
43
|
_process_options(options)
|
39
|
-
|
44
|
+
method_name = Renderers._render_with_renderer_method_name(name)
|
45
|
+
return send(method_name, options.delete(name), options)
|
40
46
|
end
|
41
47
|
end
|
42
48
|
nil
|
43
49
|
end
|
44
50
|
|
45
|
-
#
|
46
|
-
# Default
|
51
|
+
# A Set containing renderer names that correspond to available renderer procs.
|
52
|
+
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
|
47
53
|
RENDERERS = Set.new
|
48
54
|
|
55
|
+
def self._render_with_renderer_method_name(key)
|
56
|
+
"_render_with_renderer_#{key}"
|
57
|
+
end
|
58
|
+
|
49
59
|
# Adds a new renderer to call within controller actions.
|
50
60
|
# A renderer is invoked by passing its name as an option to
|
51
61
|
# <tt>AbstractController::Rendering#render</tt>. To create a renderer
|
@@ -73,16 +83,24 @@ module ActionController
|
|
73
83
|
# respond_to do |format|
|
74
84
|
# format.html
|
75
85
|
# format.csv { render csv: @csvable, filename: @csvable.name }
|
76
|
-
#
|
86
|
+
# end
|
77
87
|
# end
|
78
|
-
# To use renderers and their mime types in more concise ways, see
|
79
|
-
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
|
80
|
-
# <tt>ActionController::MimeResponds#respond_with</tt>
|
81
88
|
def self.add(key, &block)
|
82
|
-
define_method(
|
89
|
+
define_method(_render_with_renderer_method_name(key), &block)
|
83
90
|
RENDERERS << key.to_sym
|
84
91
|
end
|
85
92
|
|
93
|
+
# This method is the opposite of add method.
|
94
|
+
#
|
95
|
+
# Usage:
|
96
|
+
#
|
97
|
+
# ActionController::Renderers.remove(:csv)
|
98
|
+
def self.remove(key)
|
99
|
+
RENDERERS.delete(key.to_sym)
|
100
|
+
method_name = _render_with_renderer_method_name(key)
|
101
|
+
remove_method(method_name) if method_defined?(method_name)
|
102
|
+
end
|
103
|
+
|
86
104
|
module All
|
87
105
|
extend ActiveSupport::Concern
|
88
106
|
include Renderers
|
@@ -96,7 +114,7 @@ module ActionController
|
|
96
114
|
json = json.to_json(options) unless json.kind_of?(String)
|
97
115
|
|
98
116
|
if options[:callback].present?
|
99
|
-
if
|
117
|
+
if content_type.nil? || content_type == Mime::JSON
|
100
118
|
self.content_type = Mime::JS
|
101
119
|
end
|
102
120
|
|
@@ -67,8 +67,8 @@ module ActionController
|
|
67
67
|
options[:html] = ERB::Util.html_escape(options[:html])
|
68
68
|
end
|
69
69
|
|
70
|
-
if options.delete(:nothing)
|
71
|
-
options[:body] =
|
70
|
+
if options.delete(:nothing)
|
71
|
+
options[:body] = nil
|
72
72
|
end
|
73
73
|
|
74
74
|
if options[:status]
|
@@ -86,10 +86,6 @@ module ActionController
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
def _any_render_format_is_nil?(options)
|
90
|
-
RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
|
91
|
-
end
|
92
|
-
|
93
89
|
# Process controller specific options, as status, content-type and location.
|
94
90
|
def _process_options(options) #:nodoc:
|
95
91
|
status, content_type, location = options.values_at(:status, :content_type, :location)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/session/abstract/id'
|
2
2
|
require 'action_controller/metal/exceptions'
|
3
|
+
require 'active_support/security_utils'
|
3
4
|
|
4
5
|
module ActionController #:nodoc:
|
5
6
|
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
|
@@ -9,7 +10,7 @@ module ActionController #:nodoc:
|
|
9
10
|
end
|
10
11
|
|
11
12
|
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
12
|
-
# by including a token in the rendered
|
13
|
+
# by including a token in the rendered HTML for your application. This token is
|
13
14
|
# stored as a random string in the session, to which an attacker does not have
|
14
15
|
# access. When a request reaches your application, \Rails verifies the received
|
15
16
|
# token with the token in the session. Only HTML and JavaScript requests are checked,
|
@@ -44,7 +45,7 @@ module ActionController #:nodoc:
|
|
44
45
|
#
|
45
46
|
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
46
47
|
# value of this token must be added to every layout that renders forms by including
|
47
|
-
# <tt>csrf_meta_tags</tt> in the
|
48
|
+
# <tt>csrf_meta_tags</tt> in the HTML +head+.
|
48
49
|
#
|
49
50
|
# Learn more about CSRF attacks and securing your application in the
|
50
51
|
# {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
|
@@ -68,12 +69,16 @@ module ActionController #:nodoc:
|
|
68
69
|
config_accessor :allow_forgery_protection
|
69
70
|
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
70
71
|
|
72
|
+
# Controls whether a CSRF failure logs a warning. On by default.
|
73
|
+
config_accessor :log_warning_on_csrf_failure
|
74
|
+
self.log_warning_on_csrf_failure = true
|
75
|
+
|
71
76
|
helper_method :form_authenticity_token
|
72
77
|
helper_method :protect_against_forgery?
|
73
78
|
end
|
74
79
|
|
75
80
|
module ClassMethods
|
76
|
-
# Turn on request forgery protection. Bear in mind that
|
81
|
+
# Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
|
77
82
|
#
|
78
83
|
# class ApplicationController < ActionController::Base
|
79
84
|
# protect_from_forgery
|
@@ -193,7 +198,9 @@ module ActionController #:nodoc:
|
|
193
198
|
mark_for_same_origin_verification!
|
194
199
|
|
195
200
|
if !verified_request?
|
196
|
-
logger
|
201
|
+
if logger && log_warning_on_csrf_failure
|
202
|
+
logger.warn "Can't verify CSRF token authenticity"
|
203
|
+
end
|
197
204
|
handle_unverified_request
|
198
205
|
end
|
199
206
|
end
|
@@ -202,6 +209,7 @@ module ActionController #:nodoc:
|
|
202
209
|
forgery_protection_strategy.new(self).handle_unverified_request
|
203
210
|
end
|
204
211
|
|
212
|
+
#:nodoc:
|
205
213
|
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
|
206
214
|
"<script> tag on another site requested protected JavaScript. " \
|
207
215
|
"If you know what you're doing, go ahead and disable forgery " \
|
@@ -234,6 +242,8 @@ module ActionController #:nodoc:
|
|
234
242
|
content_type =~ %r(\Atext/javascript) && !request.xhr?
|
235
243
|
end
|
236
244
|
|
245
|
+
AUTHENTICITY_TOKEN_LENGTH = 32
|
246
|
+
|
237
247
|
# Returns true or false if a request is verified. Checks:
|
238
248
|
#
|
239
249
|
# * is it a GET or HEAD request? Gets should be safe and idempotent
|
@@ -241,13 +251,74 @@ module ActionController #:nodoc:
|
|
241
251
|
# * Does the X-CSRF-Token header match the form_authenticity_token
|
242
252
|
def verified_request?
|
243
253
|
!protect_against_forgery? || request.get? || request.head? ||
|
244
|
-
|
245
|
-
|
254
|
+
valid_authenticity_token?(session, form_authenticity_param) ||
|
255
|
+
valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
|
246
256
|
end
|
247
257
|
|
248
258
|
# Sets the token value for the current session.
|
249
259
|
def form_authenticity_token
|
250
|
-
session
|
260
|
+
masked_authenticity_token(session)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Creates a masked version of the authenticity token that varies
|
264
|
+
# on each request. The masking is used to mitigate SSL attacks
|
265
|
+
# like BREACH.
|
266
|
+
def masked_authenticity_token(session)
|
267
|
+
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
|
268
|
+
encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
|
269
|
+
masked_token = one_time_pad + encrypted_csrf_token
|
270
|
+
Base64.strict_encode64(masked_token)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Checks the client's masked token to see if it matches the
|
274
|
+
# session token. Essentially the inverse of
|
275
|
+
# +masked_authenticity_token+.
|
276
|
+
def valid_authenticity_token?(session, encoded_masked_token)
|
277
|
+
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
|
278
|
+
return false
|
279
|
+
end
|
280
|
+
|
281
|
+
begin
|
282
|
+
masked_token = Base64.strict_decode64(encoded_masked_token)
|
283
|
+
rescue ArgumentError # encoded_masked_token is invalid Base64
|
284
|
+
return false
|
285
|
+
end
|
286
|
+
|
287
|
+
# See if it's actually a masked token or not. In order to
|
288
|
+
# deploy this code, we should be able to handle any unmasked
|
289
|
+
# tokens that we've issued without error.
|
290
|
+
|
291
|
+
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
|
292
|
+
# This is actually an unmasked token. This is expected if
|
293
|
+
# you have just upgraded to masked tokens, but should stop
|
294
|
+
# happening shortly after installing this gem
|
295
|
+
compare_with_real_token masked_token, session
|
296
|
+
|
297
|
+
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
298
|
+
# Split the token into the one-time pad and the encrypted
|
299
|
+
# value and decrypt it
|
300
|
+
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
301
|
+
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
302
|
+
csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
303
|
+
|
304
|
+
compare_with_real_token csrf_token, session
|
305
|
+
|
306
|
+
else
|
307
|
+
false # Token is malformed
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def compare_with_real_token(token, session)
|
312
|
+
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
|
313
|
+
end
|
314
|
+
|
315
|
+
def real_csrf_token(session)
|
316
|
+
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
317
|
+
Base64.strict_decode64(session[:_csrf_token])
|
318
|
+
end
|
319
|
+
|
320
|
+
def xor_byte_strings(s1, s2)
|
321
|
+
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
|
251
322
|
end
|
252
323
|
|
253
324
|
# The form's authenticity parameter. Override to provide your own.
|
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
|
|
183
183
|
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
|
184
184
|
# Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
|
185
185
|
#
|
186
|
-
# If you are using Unicorn with
|
186
|
+
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
|
187
187
|
# Streaming should work out of the box on Rainbows.
|
188
188
|
#
|
189
189
|
# ==== Passenger
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'active_support/core_ext/hash/indifferent_access'
|
2
2
|
require 'active_support/core_ext/array/wrap'
|
3
|
+
require 'active_support/core_ext/string/filters'
|
4
|
+
require 'active_support/deprecation'
|
3
5
|
require 'active_support/rescuable'
|
4
6
|
require 'action_dispatch/http/upload'
|
5
7
|
require 'stringio'
|
@@ -22,24 +24,26 @@ module ActionController
|
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
# Raised when a supplied parameter is not expected
|
27
|
+
# Raised when a supplied parameter is not expected and
|
28
|
+
# ActionController::Parameters.action_on_unpermitted_parameters
|
29
|
+
# is set to <tt>:raise</tt>.
|
26
30
|
#
|
27
31
|
# params = ActionController::Parameters.new(a: "123", b: "456")
|
28
32
|
# params.permit(:c)
|
29
|
-
# # => ActionController::UnpermittedParameters: found
|
33
|
+
# # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
|
30
34
|
class UnpermittedParameters < IndexError
|
31
35
|
attr_reader :params # :nodoc:
|
32
36
|
|
33
37
|
def initialize(params) # :nodoc:
|
34
38
|
@params = params
|
35
|
-
super("found unpermitted
|
39
|
+
super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
43
|
# == Action Controller \Parameters
|
40
44
|
#
|
41
45
|
# Allows to choose which attributes should be whitelisted for mass updating
|
42
|
-
# and thus prevent accidentally exposing that which shouldn
|
46
|
+
# and thus prevent accidentally exposing that which shouldn't be exposed.
|
43
47
|
# Provides two methods for this purpose: #require and #permit. The former is
|
44
48
|
# used to mark parameters as required. The latter is used to set the parameter
|
45
49
|
# as permitted and limit which attributes should be allowed for mass updating.
|
@@ -90,7 +94,11 @@ module ActionController
|
|
90
94
|
# params.permit(:c)
|
91
95
|
# # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
|
92
96
|
#
|
93
|
-
#
|
97
|
+
# Please note that these options *are not thread-safe*. In a multi-threaded
|
98
|
+
# environment they should only be set once at boot-time and never mutated at
|
99
|
+
# runtime.
|
100
|
+
#
|
101
|
+
# <tt>ActionController::Parameters</tt> inherits from
|
94
102
|
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
|
95
103
|
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
|
96
104
|
#
|
@@ -101,9 +109,25 @@ module ActionController
|
|
101
109
|
cattr_accessor :permit_all_parameters, instance_accessor: false
|
102
110
|
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
|
103
111
|
|
104
|
-
#
|
105
|
-
# are present.
|
106
|
-
|
112
|
+
# By default, never raise an UnpermittedParameters exception if these
|
113
|
+
# params are present. The default includes both 'controller' and 'action'
|
114
|
+
# because they are added by Rails and should be of no concern. One way
|
115
|
+
# to change these is to specify `always_permitted_parameters` in your
|
116
|
+
# config. For instance:
|
117
|
+
#
|
118
|
+
# config.always_permitted_parameters = %w( controller action format )
|
119
|
+
cattr_accessor :always_permitted_parameters
|
120
|
+
self.always_permitted_parameters = %w( controller action )
|
121
|
+
|
122
|
+
def self.const_missing(const_name)
|
123
|
+
super unless const_name == :NEVER_UNPERMITTED_PARAMS
|
124
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
125
|
+
`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
|
126
|
+
Use `ActionController::Parameters.always_permitted_parameters` instead.
|
127
|
+
MSG
|
128
|
+
|
129
|
+
always_permitted_parameters
|
130
|
+
end
|
107
131
|
|
108
132
|
# Returns a new instance of <tt>ActionController::Parameters</tt>.
|
109
133
|
# Also, sets the +permitted+ attribute to the default value of
|
@@ -126,9 +150,50 @@ module ActionController
|
|
126
150
|
@permitted = self.class.permit_all_parameters
|
127
151
|
end
|
128
152
|
|
153
|
+
# Returns a safe +Hash+ representation of this parameter with all
|
154
|
+
# unpermitted keys removed.
|
155
|
+
#
|
156
|
+
# params = ActionController::Parameters.new({
|
157
|
+
# name: 'Senjougahara Hitagi',
|
158
|
+
# oddity: 'Heavy stone crab'
|
159
|
+
# })
|
160
|
+
# params.to_h # => {}
|
161
|
+
#
|
162
|
+
# safe_params = params.permit(:name)
|
163
|
+
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
|
164
|
+
def to_h
|
165
|
+
if permitted?
|
166
|
+
to_hash
|
167
|
+
else
|
168
|
+
slice(*self.class.always_permitted_parameters).permit!.to_h
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
|
173
|
+
def to_unsafe_h
|
174
|
+
to_hash
|
175
|
+
end
|
176
|
+
alias_method :to_unsafe_hash, :to_unsafe_h
|
177
|
+
|
178
|
+
# Convert all hashes in values into parameters, then yield each pair like
|
179
|
+
# the same way as <tt>Hash#each_pair</tt>
|
180
|
+
def each_pair(&block)
|
181
|
+
super do |key, value|
|
182
|
+
convert_hashes_to_parameters(key, value)
|
183
|
+
end
|
184
|
+
|
185
|
+
super
|
186
|
+
end
|
187
|
+
|
188
|
+
alias_method :each, :each_pair
|
189
|
+
|
129
190
|
# Attribute that keeps track of converted arrays, if any, to avoid double
|
130
191
|
# looping in the common use case permit + mass-assignment. Defined in a
|
131
192
|
# method to instantiate it only if needed.
|
193
|
+
#
|
194
|
+
# Testing membership still loops, but it's going to be faster than our own
|
195
|
+
# loop that converts values. Also, we are not going to build a new array
|
196
|
+
# object per fetch.
|
132
197
|
def converted_arrays
|
133
198
|
@converted_arrays ||= Set.new
|
134
199
|
end
|
@@ -157,9 +222,8 @@ module ActionController
|
|
157
222
|
# Person.new(params) # => #<Person id: nil, name: "Francesco">
|
158
223
|
def permit!
|
159
224
|
each_pair do |key, value|
|
160
|
-
value
|
161
|
-
|
162
|
-
_.permit! if _.respond_to? :permit!
|
225
|
+
Array.wrap(value).each do |v|
|
226
|
+
v.permit! if v.respond_to? :permit!
|
163
227
|
end
|
164
228
|
end
|
165
229
|
|
@@ -312,11 +376,56 @@ module ActionController
|
|
312
376
|
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
|
313
377
|
# params.slice(:d) # => {}
|
314
378
|
def slice(*keys)
|
315
|
-
|
316
|
-
|
379
|
+
new_instance_with_inherited_permitted_status(super)
|
380
|
+
end
|
381
|
+
|
382
|
+
# Removes and returns the key/value pairs matching the given keys.
|
383
|
+
#
|
384
|
+
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
385
|
+
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
|
386
|
+
# params # => {"c"=>3}
|
387
|
+
def extract!(*keys)
|
388
|
+
new_instance_with_inherited_permitted_status(super)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns a new <tt>ActionController::Parameters</tt> with the results of
|
392
|
+
# running +block+ once for every value. The keys are unchanged.
|
393
|
+
#
|
394
|
+
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
395
|
+
# params.transform_values { |x| x * 2 }
|
396
|
+
# # => {"a"=>2, "b"=>4, "c"=>6}
|
397
|
+
def transform_values
|
398
|
+
if block_given?
|
399
|
+
new_instance_with_inherited_permitted_status(super)
|
400
|
+
else
|
401
|
+
super
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# This method is here only to make sure that the returned object has the
|
406
|
+
# correct +permitted+ status. It should not matter since the parent of
|
407
|
+
# this object is +HashWithIndifferentAccess+
|
408
|
+
def transform_keys # :nodoc:
|
409
|
+
if block_given?
|
410
|
+
new_instance_with_inherited_permitted_status(super)
|
411
|
+
else
|
412
|
+
super
|
317
413
|
end
|
318
414
|
end
|
319
415
|
|
416
|
+
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
|
417
|
+
# to key. If the key is not found, returns the default value. If the
|
418
|
+
# optional code block is given and the key is not found, pass in the key
|
419
|
+
# and return the result of block.
|
420
|
+
def delete(key, &block)
|
421
|
+
convert_hashes_to_parameters(key, super, false)
|
422
|
+
end
|
423
|
+
|
424
|
+
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
|
425
|
+
def select!(&block)
|
426
|
+
convert_value_to_parameters(super)
|
427
|
+
end
|
428
|
+
|
320
429
|
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
|
321
430
|
# instance. +permitted+ state is kept on the duped object.
|
322
431
|
#
|
@@ -337,6 +446,12 @@ module ActionController
|
|
337
446
|
end
|
338
447
|
|
339
448
|
private
|
449
|
+
def new_instance_with_inherited_permitted_status(hash)
|
450
|
+
self.class.new(hash).tap do |new_instance|
|
451
|
+
new_instance.permitted = @permitted
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
340
455
|
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
|
341
456
|
converted = convert_value_to_parameters(value)
|
342
457
|
self[key] = converted if assign_if_converted && !converted.equal?(value)
|
@@ -385,7 +500,7 @@ module ActionController
|
|
385
500
|
end
|
386
501
|
|
387
502
|
def unpermitted_keys(params)
|
388
|
-
self.keys - params.keys -
|
503
|
+
self.keys - params.keys - self.always_permitted_parameters
|
389
504
|
end
|
390
505
|
|
391
506
|
#
|