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.

Files changed (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +404 -451
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +11 -4
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +7 -1
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching/fragments.rb +7 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/log_subscriber.rb +26 -26
  14. data/lib/action_controller/metal/conditional_get.rb +37 -12
  15. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  16. data/lib/action_controller/metal/exceptions.rb +1 -1
  17. data/lib/action_controller/metal/force_ssl.rb +1 -1
  18. data/lib/action_controller/metal/head.rb +7 -3
  19. data/lib/action_controller/metal/http_authentication.rb +20 -10
  20. data/lib/action_controller/metal/instrumentation.rb +8 -5
  21. data/lib/action_controller/metal/live.rb +57 -6
  22. data/lib/action_controller/metal/mime_responds.rb +25 -246
  23. data/lib/action_controller/metal/params_wrapper.rb +5 -5
  24. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  25. data/lib/action_controller/metal/redirecting.rb +14 -8
  26. data/lib/action_controller/metal/renderers.rb +29 -11
  27. data/lib/action_controller/metal/rendering.rb +2 -6
  28. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  29. data/lib/action_controller/metal/streaming.rb +1 -1
  30. data/lib/action_controller/metal/strong_parameters.rb +129 -14
  31. data/lib/action_controller/metal/url_for.rb +11 -12
  32. data/lib/action_controller/metal.rb +12 -11
  33. data/lib/action_controller/model_naming.rb +1 -1
  34. data/lib/action_controller/railtie.rb +4 -0
  35. data/lib/action_controller/test_case.rb +119 -75
  36. data/lib/action_controller.rb +1 -1
  37. data/lib/action_dispatch/http/cache.rb +5 -4
  38. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  39. data/lib/action_dispatch/http/headers.rb +43 -9
  40. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  41. data/lib/action_dispatch/http/mime_type.rb +18 -4
  42. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  43. data/lib/action_dispatch/http/parameters.rb +11 -26
  44. data/lib/action_dispatch/http/request.rb +37 -11
  45. data/lib/action_dispatch/http/response.rb +74 -23
  46. data/lib/action_dispatch/http/upload.rb +9 -8
  47. data/lib/action_dispatch/http/url.rb +89 -70
  48. data/lib/action_dispatch/journey/formatter.rb +34 -18
  49. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  50. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  51. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  52. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  53. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  54. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  55. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  56. data/lib/action_dispatch/journey/parser.rb +52 -60
  57. data/lib/action_dispatch/journey/parser.y +11 -10
  58. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  59. data/lib/action_dispatch/journey/route.rb +4 -19
  60. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  61. data/lib/action_dispatch/journey/router/utils.rb +1 -1
  62. data/lib/action_dispatch/journey/router.rb +53 -77
  63. data/lib/action_dispatch/journey/routes.rb +4 -0
  64. data/lib/action_dispatch/journey/scanner.rb +5 -5
  65. data/lib/action_dispatch/journey/visitors.rb +81 -92
  66. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  67. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  68. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +34 -34
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  72. data/lib/action_dispatch/middleware/flash.rb +13 -7
  73. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  74. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  75. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  76. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +1 -1
  80. data/lib/action_dispatch/middleware/static.rb +75 -39
  81. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  82. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  83. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  84. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  90. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  92. data/lib/action_dispatch/railtie.rb +2 -0
  93. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  94. data/lib/action_dispatch/routing/inspector.rb +5 -12
  95. data/lib/action_dispatch/routing/mapper.rb +414 -283
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  97. data/lib/action_dispatch/routing/redirection.rb +10 -12
  98. data/lib/action_dispatch/routing/route_set.rb +300 -173
  99. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  100. data/lib/action_dispatch/routing/url_for.rb +17 -5
  101. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  103. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  104. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  105. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  106. data/lib/action_dispatch/testing/assertions.rb +11 -7
  107. data/lib/action_dispatch/testing/integration.rb +28 -20
  108. data/lib/action_dispatch/testing/test_request.rb +1 -1
  109. data/lib/action_dispatch/testing/test_response.rb +1 -5
  110. data/lib/action_pack/gem_version.rb +3 -3
  111. metadata +55 -13
  112. 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 submit
9
- # POST requests without having to specify any root elements.
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 make the wrapped hash displayed in the log file
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 use to stored wrapped parameters.
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, :_status_code, :to => "@_response"
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 take one of three forms:
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, ActionController::RedirectBackError will be raised. You may specify some fallback
64
- # behavior for this case by rescuing ActionController::RedirectBackError.
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.h(location)}\">redirected</a>.</body></html>"
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
- _handle_render_options(options) || super
37
+ _render_to_body_with_renderer(options) || super
33
38
  end
34
39
 
35
- def _handle_render_options(options)
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
- return send("_render_option_#{name}", options.delete(name), options)
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
- # Hash of available renderers, mapping a renderer name to its proc.
46
- # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
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("_render_option_#{key}", &block)
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 self.content_type.nil? || self.content_type == Mime::JSON
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) || _any_render_format_is_nil?(options)
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 html for your application. This token is
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 html +head+.
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 only non-GET, HTML/JavaScript requests are checked.
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.warn "Can't verify CSRF token authenticity" if 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
- form_authenticity_token == params[request_forgery_protection_token] ||
245
- form_authenticity_token == request.headers['X-CSRF-Token']
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[:_csrf_token] ||= SecureRandom.base64(32)
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 Nginx, you may need to tweak Nginx.
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 unexpected keys: a, b
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 parameters: #{params.join(", ")}")
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 shouldnt be exposed.
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
- # <tt>ActionController::Parameters</tt> is inherited from
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
- # Never raise an UnpermittedParameters exception because of these params
105
- # are present. They are added by Rails and it's of no concern.
106
- NEVER_UNPERMITTED_PARAMS = %w( controller action )
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 = convert_hashes_to_parameters(key, value)
161
- Array.wrap(value).each do |_|
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
- self.class.new(super).tap do |new_instance|
316
- new_instance.permitted = @permitted
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 - NEVER_UNPERMITTED_PARAMS
503
+ self.keys - params.keys - self.always_permitted_parameters
389
504
  end
390
505
 
391
506
  #