actionpack 4.1.7 → 4.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +311 -527
- 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/url_for.rb +1 -1
- data/lib/action_controller/base.rb +2 -1
- 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 +14 -9
- 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 +23 -246
- data/lib/action_controller/metal/params_wrapper.rb +2 -2
- 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 +30 -10
- 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 +125 -12
- 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 +112 -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 +2 -2
- 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 +70 -18
- data/lib/action_dispatch/http/upload.rb +3 -8
- data/lib/action_dispatch/http/url.rb +88 -69
- data/lib/action_dispatch/journey/formatter.rb +33 -17
- 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 +3 -18
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router.rb +53 -77
- 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 +29 -29
- 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/static.rb +66 -37
- 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/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +5 -12
- data/lib/action_dispatch/routing/mapper.rb +410 -281
- 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 +297 -168
- data/lib/action_dispatch/routing/url_for.rb +15 -4
- 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 +24 -19
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +7 -0
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +55 -13
- data/lib/action_controller/metal/responder.rb +0 -297
|
@@ -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,26 @@ 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
88
|
# To use renderers and their mime types in more concise ways, see
|
|
79
|
-
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
|
|
80
|
-
# <tt>ActionController::MimeResponds#respond_with</tt>
|
|
89
|
+
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
|
|
81
90
|
def self.add(key, &block)
|
|
82
|
-
define_method(
|
|
91
|
+
define_method(_render_with_renderer_method_name(key), &block)
|
|
83
92
|
RENDERERS << key.to_sym
|
|
84
93
|
end
|
|
85
94
|
|
|
95
|
+
# This method is the opposite of add method.
|
|
96
|
+
#
|
|
97
|
+
# Usage:
|
|
98
|
+
#
|
|
99
|
+
# ActionController::Renderers.remove(:csv)
|
|
100
|
+
def self.remove(key)
|
|
101
|
+
RENDERERS.delete(key.to_sym)
|
|
102
|
+
method_name = _render_with_renderer_method_name(key)
|
|
103
|
+
remove_method(method_name) if method_defined?(method_name)
|
|
104
|
+
end
|
|
105
|
+
|
|
86
106
|
module All
|
|
87
107
|
extend ActiveSupport::Concern
|
|
88
108
|
include Renderers
|
|
@@ -96,7 +116,7 @@ module ActionController
|
|
|
96
116
|
json = json.to_json(options) unless json.kind_of?(String)
|
|
97
117
|
|
|
98
118
|
if options[:callback].present?
|
|
99
|
-
if
|
|
119
|
+
if content_type.nil? || content_type == Mime::JSON
|
|
100
120
|
self.content_type = Mime::JS
|
|
101
121
|
end
|
|
102
122
|
|
|
@@ -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'
|
|
@@ -32,14 +34,14 @@ module ActionController
|
|
|
32
34
|
|
|
33
35
|
def initialize(params) # :nodoc:
|
|
34
36
|
@params = params
|
|
35
|
-
super("found unpermitted
|
|
37
|
+
super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# == Action Controller \Parameters
|
|
40
42
|
#
|
|
41
43
|
# Allows to choose which attributes should be whitelisted for mass updating
|
|
42
|
-
# and thus prevent accidentally exposing that which shouldn
|
|
44
|
+
# and thus prevent accidentally exposing that which shouldn't be exposed.
|
|
43
45
|
# Provides two methods for this purpose: #require and #permit. The former is
|
|
44
46
|
# used to mark parameters as required. The latter is used to set the parameter
|
|
45
47
|
# as permitted and limit which attributes should be allowed for mass updating.
|
|
@@ -90,7 +92,11 @@ module ActionController
|
|
|
90
92
|
# params.permit(:c)
|
|
91
93
|
# # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
|
|
92
94
|
#
|
|
93
|
-
#
|
|
95
|
+
# Please note that these options *are not thread-safe*. In a multi-threaded
|
|
96
|
+
# environment they should only be set once at boot-time and never mutated at
|
|
97
|
+
# runtime.
|
|
98
|
+
#
|
|
99
|
+
# <tt>ActionController::Parameters</tt> inherits from
|
|
94
100
|
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
|
|
95
101
|
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
|
|
96
102
|
#
|
|
@@ -101,9 +107,25 @@ module ActionController
|
|
|
101
107
|
cattr_accessor :permit_all_parameters, instance_accessor: false
|
|
102
108
|
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
|
|
103
109
|
|
|
104
|
-
#
|
|
105
|
-
# are present.
|
|
106
|
-
|
|
110
|
+
# By default, never raise an UnpermittedParameters exception if these
|
|
111
|
+
# params are present. The default includes both 'controller' and 'action'
|
|
112
|
+
# because they are added by Rails and should be of no concern. One way
|
|
113
|
+
# to change these is to specify `always_permitted_parameters` in your
|
|
114
|
+
# config. For instance:
|
|
115
|
+
#
|
|
116
|
+
# config.always_permitted_parameters = %w( controller action format )
|
|
117
|
+
cattr_accessor :always_permitted_parameters
|
|
118
|
+
self.always_permitted_parameters = %w( controller action )
|
|
119
|
+
|
|
120
|
+
def self.const_missing(const_name)
|
|
121
|
+
super unless const_name == :NEVER_UNPERMITTED_PARAMS
|
|
122
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
123
|
+
`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
|
|
124
|
+
Use `ActionController::Parameters.always_permitted_parameters` instead.
|
|
125
|
+
MSG
|
|
126
|
+
|
|
127
|
+
always_permitted_parameters
|
|
128
|
+
end
|
|
107
129
|
|
|
108
130
|
# Returns a new instance of <tt>ActionController::Parameters</tt>.
|
|
109
131
|
# Also, sets the +permitted+ attribute to the default value of
|
|
@@ -126,9 +148,50 @@ module ActionController
|
|
|
126
148
|
@permitted = self.class.permit_all_parameters
|
|
127
149
|
end
|
|
128
150
|
|
|
151
|
+
# Returns a safe +Hash+ representation of this parameter with all
|
|
152
|
+
# unpermitted keys removed.
|
|
153
|
+
#
|
|
154
|
+
# params = ActionController::Parameters.new({
|
|
155
|
+
# name: 'Senjougahara Hitagi',
|
|
156
|
+
# oddity: 'Heavy stone crab'
|
|
157
|
+
# })
|
|
158
|
+
# params.to_h # => {}
|
|
159
|
+
#
|
|
160
|
+
# safe_params = params.permit(:name)
|
|
161
|
+
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
|
|
162
|
+
def to_h
|
|
163
|
+
if permitted?
|
|
164
|
+
to_hash
|
|
165
|
+
else
|
|
166
|
+
slice(*self.class.always_permitted_parameters).permit!.to_h
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
|
|
171
|
+
def to_unsafe_h
|
|
172
|
+
to_hash
|
|
173
|
+
end
|
|
174
|
+
alias_method :to_unsafe_hash, :to_unsafe_h
|
|
175
|
+
|
|
176
|
+
# Convert all hashes in values into parameters, then yield each pair like
|
|
177
|
+
# the same way as <tt>Hash#each_pair</tt>
|
|
178
|
+
def each_pair(&block)
|
|
179
|
+
super do |key, value|
|
|
180
|
+
convert_hashes_to_parameters(key, value)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
super
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
alias_method :each, :each_pair
|
|
187
|
+
|
|
129
188
|
# Attribute that keeps track of converted arrays, if any, to avoid double
|
|
130
189
|
# looping in the common use case permit + mass-assignment. Defined in a
|
|
131
190
|
# method to instantiate it only if needed.
|
|
191
|
+
#
|
|
192
|
+
# Testing membership still loops, but it's going to be faster than our own
|
|
193
|
+
# loop that converts values. Also, we are not going to build a new array
|
|
194
|
+
# object per fetch.
|
|
132
195
|
def converted_arrays
|
|
133
196
|
@converted_arrays ||= Set.new
|
|
134
197
|
end
|
|
@@ -157,9 +220,8 @@ module ActionController
|
|
|
157
220
|
# Person.new(params) # => #<Person id: nil, name: "Francesco">
|
|
158
221
|
def permit!
|
|
159
222
|
each_pair do |key, value|
|
|
160
|
-
value
|
|
161
|
-
|
|
162
|
-
_.permit! if _.respond_to? :permit!
|
|
223
|
+
Array.wrap(value).each do |v|
|
|
224
|
+
v.permit! if v.respond_to? :permit!
|
|
163
225
|
end
|
|
164
226
|
end
|
|
165
227
|
|
|
@@ -312,11 +374,56 @@ module ActionController
|
|
|
312
374
|
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
|
|
313
375
|
# params.slice(:d) # => {}
|
|
314
376
|
def slice(*keys)
|
|
315
|
-
|
|
316
|
-
|
|
377
|
+
new_instance_with_inherited_permitted_status(super)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Removes and returns the key/value pairs matching the given keys.
|
|
381
|
+
#
|
|
382
|
+
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
|
383
|
+
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
|
|
384
|
+
# params # => {"c"=>3}
|
|
385
|
+
def extract!(*keys)
|
|
386
|
+
new_instance_with_inherited_permitted_status(super)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Returns a new <tt>ActionController::Parameters</tt> with the results of
|
|
390
|
+
# running +block+ once for every value. The keys are unchanged.
|
|
391
|
+
#
|
|
392
|
+
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
|
|
393
|
+
# params.transform_values { |x| x * 2 }
|
|
394
|
+
# # => {"a"=>2, "b"=>4, "c"=>6}
|
|
395
|
+
def transform_values
|
|
396
|
+
if block_given?
|
|
397
|
+
new_instance_with_inherited_permitted_status(super)
|
|
398
|
+
else
|
|
399
|
+
super
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# This method is here only to make sure that the returned object has the
|
|
404
|
+
# correct +permitted+ status. It should not matter since the parent of
|
|
405
|
+
# this object is +HashWithIndifferentAccess+
|
|
406
|
+
def transform_keys # :nodoc:
|
|
407
|
+
if block_given?
|
|
408
|
+
new_instance_with_inherited_permitted_status(super)
|
|
409
|
+
else
|
|
410
|
+
super
|
|
317
411
|
end
|
|
318
412
|
end
|
|
319
413
|
|
|
414
|
+
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
|
|
415
|
+
# to key. If the key is not found, returns the default value. If the
|
|
416
|
+
# optional code block is given and the key is not found, pass in the key
|
|
417
|
+
# and return the result of block.
|
|
418
|
+
def delete(key, &block)
|
|
419
|
+
convert_hashes_to_parameters(key, super, false)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
|
|
423
|
+
def select!(&block)
|
|
424
|
+
convert_value_to_parameters(super)
|
|
425
|
+
end
|
|
426
|
+
|
|
320
427
|
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
|
|
321
428
|
# instance. +permitted+ state is kept on the duped object.
|
|
322
429
|
#
|
|
@@ -337,6 +444,12 @@ module ActionController
|
|
|
337
444
|
end
|
|
338
445
|
|
|
339
446
|
private
|
|
447
|
+
def new_instance_with_inherited_permitted_status(hash)
|
|
448
|
+
self.class.new(hash).tap do |new_instance|
|
|
449
|
+
new_instance.permitted = @permitted
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
340
453
|
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
|
|
341
454
|
converted = convert_value_to_parameters(value)
|
|
342
455
|
self[key] = converted if assign_if_converted && !converted.equal?(value)
|
|
@@ -385,7 +498,7 @@ module ActionController
|
|
|
385
498
|
end
|
|
386
499
|
|
|
387
500
|
def unpermitted_keys(params)
|
|
388
|
-
self.keys - params.keys -
|
|
501
|
+
self.keys - params.keys - self.always_permitted_parameters
|
|
389
502
|
end
|
|
390
503
|
|
|
391
504
|
#
|