actionpack 4.2.11.3 → 5.0.7.2

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +890 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +54 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +14 -11
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +14 -34
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +11 -11
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +71 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +9 -9
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +83 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +527 -134
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/railtie.rb +11 -7
  48. data/lib/action_controller/renderer.rb +113 -0
  49. data/lib/action_controller/template_assertions.rb +9 -0
  50. data/lib/action_controller/test_case.rb +311 -374
  51. data/lib/action_controller.rb +12 -9
  52. data/lib/action_dispatch/http/cache.rb +73 -34
  53. data/lib/action_dispatch/http/filter_parameters.rb +16 -12
  54. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  55. data/lib/action_dispatch/http/headers.rb +45 -14
  56. data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
  57. data/lib/action_dispatch/http/mime_type.rb +126 -90
  58. data/lib/action_dispatch/http/mime_types.rb +3 -4
  59. data/lib/action_dispatch/http/parameter_filter.rb +19 -9
  60. data/lib/action_dispatch/http/parameters.rb +70 -40
  61. data/lib/action_dispatch/http/request.rb +144 -89
  62. data/lib/action_dispatch/http/response.rb +215 -102
  63. data/lib/action_dispatch/http/upload.rb +6 -2
  64. data/lib/action_dispatch/http/url.rb +117 -8
  65. data/lib/action_dispatch/journey/formatter.rb +47 -30
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  67. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  68. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  69. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  70. data/lib/action_dispatch/journey/parser.rb +2 -0
  71. data/lib/action_dispatch/journey/parser_extras.rb +8 -2
  72. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  73. data/lib/action_dispatch/journey/route.rb +88 -26
  74. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  75. data/lib/action_dispatch/journey/router.rb +8 -10
  76. data/lib/action_dispatch/journey/routes.rb +14 -15
  77. data/lib/action_dispatch/journey/visitors.rb +89 -44
  78. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  79. data/lib/action_dispatch/middleware/cookies.rb +188 -134
  80. data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
  81. data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
  82. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  83. data/lib/action_dispatch/middleware/executor.rb +19 -0
  84. data/lib/action_dispatch/middleware/flash.rb +66 -45
  85. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  86. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  87. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  88. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  89. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  90. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  91. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  92. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  93. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  94. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  95. data/lib/action_dispatch/middleware/ssl.rb +124 -36
  96. data/lib/action_dispatch/middleware/stack.rb +44 -40
  97. data/lib/action_dispatch/middleware/static.rb +51 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  99. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  101. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  103. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  104. data/lib/action_dispatch/railtie.rb +2 -2
  105. data/lib/action_dispatch/request/session.rb +69 -33
  106. data/lib/action_dispatch/request/utils.rb +51 -19
  107. data/lib/action_dispatch/routing/inspector.rb +32 -43
  108. data/lib/action_dispatch/routing/mapper.rb +515 -348
  109. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  110. data/lib/action_dispatch/routing/redirection.rb +5 -4
  111. data/lib/action_dispatch/routing/route_set.rb +148 -240
  112. data/lib/action_dispatch/routing/url_for.rb +27 -10
  113. data/lib/action_dispatch/routing.rb +17 -13
  114. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  115. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  116. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  117. data/lib/action_dispatch/testing/assertions.rb +1 -1
  118. data/lib/action_dispatch/testing/integration.rb +377 -149
  119. data/lib/action_dispatch/testing/request_encoder.rb +53 -0
  120. data/lib/action_dispatch/testing/test_process.rb +24 -20
  121. data/lib/action_dispatch/testing/test_request.rb +22 -31
  122. data/lib/action_dispatch/testing/test_response.rb +12 -4
  123. data/lib/action_dispatch.rb +4 -1
  124. data/lib/action_pack/gem_version.rb +4 -4
  125. data/lib/action_pack.rb +1 -1
  126. metadata +32 -34
  127. data/lib/action_controller/metal/hide_actions.rb +0 -40
  128. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  129. data/lib/action_controller/middleware.rb +0 -39
  130. data/lib/action_controller/model_naming.rb +0 -12
  131. data/lib/action_dispatch/journey/backwards.rb +0 -5
  132. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  133. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  134. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  135. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  136. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -11,6 +11,7 @@ module ActionController
11
11
  Renderers.remove(key)
12
12
  end
13
13
 
14
+ # See <tt>Responder#api_behavior</tt>
14
15
  class MissingRenderer < LoadError
15
16
  def initialize(format)
16
17
  super "No renderer defined for format: #{format}"
@@ -20,40 +21,25 @@ module ActionController
20
21
  module Renderers
21
22
  extend ActiveSupport::Concern
22
23
 
24
+ # A Set containing renderer names that correspond to available renderer procs.
25
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
26
+ RENDERERS = Set.new
27
+
23
28
  included do
24
29
  class_attribute :_renderers
25
30
  self._renderers = Set.new.freeze
26
31
  end
27
32
 
28
- module ClassMethods
29
- def use_renderers(*args)
30
- renderers = _renderers + args
31
- self._renderers = renderers.freeze
32
- end
33
- alias use_renderer use_renderers
34
- end
35
-
36
- def render_to_body(options)
37
- _render_to_body_with_renderer(options) || super
38
- end
33
+ # Used in <tt>ActionController::Base</tt>
34
+ # and <tt>ActionController::API</tt> to include all
35
+ # renderers by default.
36
+ module All
37
+ extend ActiveSupport::Concern
38
+ include Renderers
39
39
 
40
- def _render_to_body_with_renderer(options)
41
- _renderers.each do |name|
42
- if options.key?(name)
43
- _process_options(options)
44
- method_name = Renderers._render_with_renderer_method_name(name)
45
- return send(method_name, options.delete(name), options)
46
- end
40
+ included do
41
+ self._renderers = RENDERERS
47
42
  end
48
- nil
49
- end
50
-
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>.
53
- RENDERERS = Set.new
54
-
55
- def self._render_with_renderer_method_name(key)
56
- "_render_with_renderer_#{key}"
57
43
  end
58
44
 
59
45
  # Adds a new renderer to call within controller actions.
@@ -68,11 +54,11 @@ module ActionController
68
54
  # ActionController::Renderers.add :csv do |obj, options|
69
55
  # filename = options[:filename] || 'data'
70
56
  # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
71
- # send_data str, type: Mime::CSV,
57
+ # send_data str, type: Mime[:csv],
72
58
  # disposition: "attachment; filename=#{filename}.csv"
73
59
  # end
74
60
  #
75
- # Note that we used Mime::CSV for the csv mime type as it comes with Rails.
61
+ # Note that we used Mime[:csv] for the csv mime type as it comes with Rails.
76
62
  # For a custom renderer, you'll need to register a mime type with
77
63
  # <tt>Mime::Type.register</tt>.
78
64
  #
@@ -92,7 +78,7 @@ module ActionController
92
78
 
93
79
  # This method is the opposite of add method.
94
80
  #
95
- # Usage:
81
+ # To remove a csv renderer:
96
82
  #
97
83
  # ActionController::Renderers.remove(:csv)
98
84
  def self.remove(key)
@@ -101,37 +87,94 @@ module ActionController
101
87
  remove_method(method_name) if method_defined?(method_name)
102
88
  end
103
89
 
104
- module All
105
- extend ActiveSupport::Concern
106
- include Renderers
90
+ def self._render_with_renderer_method_name(key)
91
+ "_render_with_renderer_#{key}"
92
+ end
107
93
 
108
- included do
109
- self._renderers = RENDERERS
94
+ module ClassMethods
95
+
96
+ # Adds, by name, a renderer or renderers to the +_renderers+ available
97
+ # to call within controller actions.
98
+ #
99
+ # It is useful when rendering from an <tt>ActionController::Metal</tt> controller or
100
+ # otherwise to add an available renderer proc to a specific controller.
101
+ #
102
+ # Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
103
+ # include <tt>ActionController::Renderers::All</tt>, making all renderers
104
+ # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
105
+ #
106
+ # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
107
+ # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
108
+ # and <tt>ActionController::Renderers</tt>, and have at lest one renderer.
109
+ #
110
+ # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,
111
+ # you may specify which renderers to include by passing the renderer name or names to
112
+ # +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
113
+ # (+_render_with_renderer_json+) might look like:
114
+ #
115
+ # class MetalRenderingController < ActionController::Metal
116
+ # include AbstractController::Rendering
117
+ # include ActionController::Rendering
118
+ # include ActionController::Renderers
119
+ #
120
+ # use_renderers :json
121
+ #
122
+ # def show
123
+ # render json: record
124
+ # end
125
+ # end
126
+ #
127
+ # You must specify a +use_renderer+, else the +controller.renderer+ and
128
+ # +controller._renderers+ will be <tt>nil</tt>, and the action will fail.
129
+ def use_renderers(*args)
130
+ renderers = _renderers + args
131
+ self._renderers = renderers.freeze
110
132
  end
133
+ alias use_renderer use_renderers
134
+ end
135
+
136
+ # Called by +render+ in <tt>AbstractController::Rendering</tt>
137
+ # which sets the return value as the +response_body+.
138
+ #
139
+ # If no renderer is found, +super+ returns control to
140
+ # <tt>ActionView::Rendering.render_to_body</tt>, if present.
141
+ def render_to_body(options)
142
+ _render_to_body_with_renderer(options) || super
143
+ end
144
+
145
+ def _render_to_body_with_renderer(options)
146
+ _renderers.each do |name|
147
+ if options.key?(name)
148
+ _process_options(options)
149
+ method_name = Renderers._render_with_renderer_method_name(name)
150
+ return send(method_name, options.delete(name), options)
151
+ end
152
+ end
153
+ nil
111
154
  end
112
155
 
113
156
  add :json do |json, options|
114
157
  json = json.to_json(options) unless json.kind_of?(String)
115
158
 
116
159
  if options[:callback].present?
117
- if content_type.nil? || content_type == Mime::JSON
118
- self.content_type = Mime::JS
160
+ if content_type.nil? || content_type == Mime[:json]
161
+ self.content_type = Mime[:js]
119
162
  end
120
163
 
121
164
  "/**/#{options[:callback]}(#{json})"
122
165
  else
123
- self.content_type ||= Mime::JSON
166
+ self.content_type ||= Mime[:json]
124
167
  json
125
168
  end
126
169
  end
127
170
 
128
171
  add :js do |js, options|
129
- self.content_type ||= Mime::JS
172
+ self.content_type ||= Mime[:js]
130
173
  js.respond_to?(:to_js) ? js.to_js(options) : js
131
174
  end
132
175
 
133
176
  add :xml do |xml, options|
134
- self.content_type ||= Mime::XML
177
+ self.content_type ||= Mime[:xml]
135
178
  xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
136
179
  end
137
180
  end
@@ -1,9 +1,29 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
1
3
  module ActionController
2
4
  module Rendering
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
6
8
 
9
+ module ClassMethods
10
+ # Documentation at ActionController::Renderer#render
11
+ delegate :render, to: :renderer
12
+
13
+ # Returns a renderer instance (inherited from ActionController::Renderer)
14
+ # for the controller.
15
+ attr_reader :renderer
16
+
17
+ def setup_renderer! # :nodoc:
18
+ @renderer = Renderer.for(self)
19
+ end
20
+
21
+ def inherited(klass)
22
+ klass.setup_renderer!
23
+ super
24
+ end
25
+ end
26
+
7
27
  # Before processing, set the request formats in current controller formats.
8
28
  def process_action(*) #:nodoc:
9
29
  self.formats = request.formats.map(&:ref).compact
@@ -42,13 +62,13 @@ module ActionController
42
62
  nil
43
63
  end
44
64
 
45
- def _process_format(format, options = {})
46
- super
65
+ def _set_html_content_type
66
+ self.content_type = Mime[:html].to_s
67
+ end
47
68
 
48
- if options[:plain]
49
- self.content_type = Mime::TEXT
50
- else
51
- self.content_type ||= format.to_s
69
+ def _set_rendered_content_type(format)
70
+ if format && !response.content_type
71
+ self.content_type = format.to_s
52
72
  end
53
73
  end
54
74
 
@@ -63,11 +83,23 @@ module ActionController
63
83
  def _normalize_options(options) #:nodoc:
64
84
  _normalize_text(options)
65
85
 
86
+ if options[:text]
87
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
88
+ `render :text` is deprecated because it does not actually render a
89
+ `text/plain` response. Switch to `render plain: 'plain text'` to
90
+ render as `text/plain`, `render html: '<strong>HTML</strong>'` to
91
+ render as `text/html`, or `render body: 'raw'` to match the deprecated
92
+ behavior and render with the default Content-Type, which is
93
+ `text/html`.
94
+ WARNING
95
+ end
96
+
66
97
  if options[:html]
67
98
  options[:html] = ERB::Util.html_escape(options[:html])
68
99
  end
69
100
 
70
101
  if options.delete(:nothing)
102
+ ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.")
71
103
  options[:body] = nil
72
104
  end
73
105
 
@@ -13,9 +13,14 @@ module ActionController #:nodoc:
13
13
  # by including a token in the rendered HTML for your application. This token is
14
14
  # stored as a random string in the session, to which an attacker does not have
15
15
  # access. When a request reaches your application, \Rails verifies the received
16
- # token with the token in the session. Only HTML and JavaScript requests are checked,
17
- # so this will not protect your XML API (presumably you'll have a different
18
- # authentication scheme there anyway).
16
+ # token with the token in the session. All requests are checked except GET requests
17
+ # as these should be idempotent. Keep in mind that all session-oriented requests
18
+ # should be CSRF protected, including JavaScript and HTML requests.
19
+ #
20
+ # Since HTML and JavaScript requests are typically made from the browser, we
21
+ # need to ensure to verify request authenticity for the web browser. We can
22
+ # use session-oriented authentication for these types of requests, by using
23
+ # the `protect_from_forgery` method in our controllers.
19
24
  #
20
25
  # GET requests are not protected since they don't have side effects like writing
21
26
  # to the database and don't leak sensitive information. JavaScript requests are
@@ -26,22 +31,21 @@ module ActionController #:nodoc:
26
31
  # Ajax) requests are allowed to make GET requests for JavaScript responses.
27
32
  #
28
33
  # It's important to remember that XML or JSON requests are also affected and if
29
- # you're building an API you'll need something like:
34
+ # you're building an API you should change forgery protection method in
35
+ # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
30
36
  #
31
37
  # class ApplicationController < ActionController::Base
32
- # protect_from_forgery
33
- # skip_before_action :verify_authenticity_token, if: :json_request?
34
- #
35
- # protected
36
- #
37
- # def json_request?
38
- # request.format.json?
39
- # end
38
+ # protect_from_forgery unless: -> { request.format.json? }
40
39
  # end
41
40
  #
42
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
43
- # which checks the token and resets the session if it doesn't match what was expected.
44
- # A call to this method is generated for new \Rails applications by default.
41
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
42
+ # By default <tt>protect_from_forgery</tt> protects your session with
43
+ # <tt>:null_session</tt> method, which provides an empty session
44
+ # during request.
45
+ #
46
+ # We may want to disable CSRF protection for APIs since they are typically
47
+ # designed to be state-less. That is, the request API client will handle
48
+ # the session for you instead of Rails.
45
49
  #
46
50
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
47
51
  # value of this token must be added to every layout that renders forms by including
@@ -73,6 +77,14 @@ module ActionController #:nodoc:
73
77
  config_accessor :log_warning_on_csrf_failure
74
78
  self.log_warning_on_csrf_failure = true
75
79
 
80
+ # Controls whether the Origin header is checked in addition to the CSRF token.
81
+ config_accessor :forgery_protection_origin_check
82
+ self.forgery_protection_origin_check = false
83
+
84
+ # Controls whether form-action/method specific CSRF tokens are used.
85
+ config_accessor :per_form_csrf_tokens
86
+ self.per_form_csrf_tokens = false
87
+
76
88
  helper_method :form_authenticity_token
77
89
  helper_method :protect_against_forgery?
78
90
  end
@@ -86,13 +98,21 @@ module ActionController #:nodoc:
86
98
  #
87
99
  # class FooController < ApplicationController
88
100
  # protect_from_forgery except: :index
101
+ # end
102
+ #
103
+ # You can disable forgery protection on controller by skipping the verification before_action:
89
104
  #
90
- # You can disable CSRF protection on controller by skipping the verification before_action:
91
105
  # skip_before_action :verify_authenticity_token
92
106
  #
93
107
  # Valid Options:
94
108
  #
95
- # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
109
+ # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
110
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
111
+ # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
112
+ # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
113
+ # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
114
+ #
115
+ # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
96
116
  # * <tt>:with</tt> - Set the method to handle unverified request.
97
117
  #
98
118
  # Valid unverified request handling methods are:
@@ -100,9 +120,11 @@ module ActionController #:nodoc:
100
120
  # * <tt>:reset_session</tt> - Resets the session.
101
121
  # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
102
122
  def protect_from_forgery(options = {})
123
+ options = options.reverse_merge(prepend: false)
124
+
103
125
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
104
126
  self.request_forgery_protection_token ||= :authenticity_token
105
- prepend_before_action :verify_authenticity_token, options
127
+ before_action :verify_authenticity_token, options
106
128
  append_after_action :verify_same_origin_request
107
129
  end
108
130
 
@@ -124,17 +146,17 @@ module ActionController #:nodoc:
124
146
  # This is the method that defines the application behavior when a request is found to be unverified.
125
147
  def handle_unverified_request
126
148
  request = @controller.request
127
- request.session = NullSessionHash.new(request.env)
128
- request.env['action_dispatch.request.flash_hash'] = nil
129
- request.env['rack.session.options'] = { skip: true }
130
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
149
+ request.session = NullSessionHash.new(request)
150
+ request.flash = nil
151
+ request.session_options = { skip: true }
152
+ request.cookie_jar = NullCookieJar.build(request, {})
131
153
  end
132
154
 
133
155
  protected
134
156
 
135
157
  class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
136
- def initialize(env)
137
- super(nil, env)
158
+ def initialize(req)
159
+ super(nil, req)
138
160
  @data = {}
139
161
  @loaded = true
140
162
  end
@@ -148,14 +170,6 @@ module ActionController #:nodoc:
148
170
  end
149
171
 
150
172
  class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
151
- def self.build(request)
152
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
153
- host = request.host
154
- secure = request.ssl?
155
-
156
- new(key_generator, host, secure, options_for_env({}))
157
- end
158
-
159
173
  def write(*)
160
174
  # nothing
161
175
  end
@@ -199,7 +213,7 @@ module ActionController #:nodoc:
199
213
 
200
214
  if !verified_request?
201
215
  if logger && log_warning_on_csrf_failure
202
- logger.warn "Can't verify CSRF token authenticity"
216
+ logger.warn "Can't verify CSRF token authenticity."
203
217
  end
204
218
  handle_unverified_request
205
219
  end
@@ -246,26 +260,46 @@ module ActionController #:nodoc:
246
260
 
247
261
  # Returns true or false if a request is verified. Checks:
248
262
  #
249
- # * is it a GET or HEAD request? Gets should be safe and idempotent
263
+ # * Is it a GET or HEAD request? Gets should be safe and idempotent
250
264
  # * Does the form_authenticity_token match the given token value from the params?
251
265
  # * Does the X-CSRF-Token header match the form_authenticity_token
252
266
  def verified_request?
253
267
  !protect_against_forgery? || request.get? || request.head? ||
254
- valid_authenticity_token?(session, form_authenticity_param) ||
255
- valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
268
+ (valid_request_origin? && any_authenticity_token_valid?)
269
+ end
270
+
271
+ # Checks if any of the authenticity tokens from the request are valid.
272
+ def any_authenticity_token_valid?
273
+ request_authenticity_tokens.any? do |token|
274
+ valid_authenticity_token?(session, token)
275
+ end
276
+ end
277
+
278
+ # Possible authenticity tokens sent in the request.
279
+ def request_authenticity_tokens
280
+ [form_authenticity_param, request.x_csrf_token]
256
281
  end
257
282
 
258
283
  # Sets the token value for the current session.
259
- def form_authenticity_token
260
- masked_authenticity_token(session)
284
+ def form_authenticity_token(form_options: {})
285
+ masked_authenticity_token(session, form_options: form_options)
261
286
  end
262
287
 
263
288
  # Creates a masked version of the authenticity token that varies
264
289
  # on each request. The masking is used to mitigate SSL attacks
265
290
  # like BREACH.
266
- def masked_authenticity_token(session)
291
+ def masked_authenticity_token(session, form_options: {})
292
+ action, method = form_options.values_at(:action, :method)
293
+
294
+ raw_token = if per_form_csrf_tokens && action && method
295
+ action_path = normalize_action_path(action)
296
+ per_form_csrf_token(session, action_path, method)
297
+ else
298
+ real_csrf_token(session)
299
+ end
300
+
267
301
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
268
- encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
302
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
269
303
  masked_token = one_time_pad + encrypted_csrf_token
270
304
  Base64.strict_encode64(masked_token)
271
305
  end
@@ -295,30 +329,58 @@ module ActionController #:nodoc:
295
329
  compare_with_real_token masked_token, session
296
330
 
297
331
  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
332
+ csrf_token = unmask_token(masked_token)
305
333
 
334
+ compare_with_real_token(csrf_token, session) ||
335
+ valid_per_form_csrf_token?(csrf_token, session)
306
336
  else
307
337
  false # Token is malformed
308
338
  end
309
339
  end
310
340
 
341
+ def unmask_token(masked_token)
342
+ # Split the token into the one-time pad and the encrypted
343
+ # value and decrypt it
344
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
345
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
346
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
347
+ end
348
+
311
349
  def compare_with_real_token(token, session)
312
350
  ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
313
351
  end
314
352
 
353
+ def valid_per_form_csrf_token?(token, session)
354
+ if per_form_csrf_tokens
355
+ correct_token = per_form_csrf_token(
356
+ session,
357
+ normalize_action_path(request.fullpath),
358
+ request.request_method
359
+ )
360
+
361
+ ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
362
+ else
363
+ false
364
+ end
365
+ end
366
+
315
367
  def real_csrf_token(session)
316
368
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
317
369
  Base64.strict_decode64(session[:_csrf_token])
318
370
  end
319
371
 
372
+ def per_form_csrf_token(session, action_path, method)
373
+ OpenSSL::HMAC.digest(
374
+ OpenSSL::Digest::SHA256.new,
375
+ real_csrf_token(session),
376
+ [action_path, method.downcase].join("#")
377
+ )
378
+ end
379
+
320
380
  def xor_byte_strings(s1, s2)
321
- s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
381
+ s2_bytes = s2.bytes
382
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
383
+ s2_bytes.pack('C*')
322
384
  end
323
385
 
324
386
  # The form's authenticity parameter. Override to provide your own.
@@ -330,5 +392,21 @@ module ActionController #:nodoc:
330
392
  def protect_against_forgery?
331
393
  allow_forgery_protection
332
394
  end
395
+
396
+ # Checks if the request originated from the same origin by looking at the
397
+ # Origin header.
398
+ def valid_request_origin?
399
+ if forgery_protection_origin_check
400
+ # We accept blank origin headers because some user agents don't send it.
401
+ request.origin.nil? || request.origin == request.base_url
402
+ else
403
+ true
404
+ end
405
+ end
406
+
407
+ def normalize_action_path(action_path)
408
+ uri = URI.parse(action_path)
409
+ uri.path.chomp('/')
410
+ end
333
411
  end
334
412
  end
@@ -1,20 +1,11 @@
1
1
  module ActionController #:nodoc:
2
- # This module is responsible to provide `rescue_from` helpers
3
- # to controllers and configure when detailed exceptions must be
2
+ # This module is responsible for providing `rescue_from` helpers
3
+ # to controllers and configuring when detailed exceptions must be
4
4
  # shown.
5
5
  module Rescue
6
6
  extend ActiveSupport::Concern
7
7
  include ActiveSupport::Rescuable
8
8
 
9
- def rescue_with_handler(exception)
10
- if (exception.respond_to?(:original_exception) &&
11
- (orig_exception = exception.original_exception) &&
12
- handler_for_rescue(orig_exception))
13
- exception = orig_exception
14
- end
15
- super(exception)
16
- end
17
-
18
9
  # Override this method if you want to customize when detailed
19
10
  # exceptions must be shown. This method is only called when
20
11
  # consider_all_requests_local is false. By default, it returns
@@ -29,7 +20,7 @@ module ActionController #:nodoc:
29
20
  super
30
21
  rescue Exception => exception
31
22
  request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
32
- rescue_with_handler(exception) || raise(exception)
23
+ rescue_with_handler(exception) || raise
33
24
  end
34
25
  end
35
26
  end
@@ -110,9 +110,9 @@ module ActionController #:nodoc:
110
110
  # This means that, if you have <code>yield :title</code> in your layout
111
111
  # and you want to use streaming, you would have to render the whole template
112
112
  # (and eventually trigger all queries) before streaming the title and all
113
- # assets, which kills the purpose of streaming. For this reason Rails 3.1
114
- # introduces a new helper called +provide+ that does the same as +content_for+
115
- # but tells the layout to stop searching for other entries and continue rendering.
113
+ # assets, which kills the purpose of streaming. For this purpose, you can use
114
+ # a helper called +provide+ that does the same as +content_for+ but tells the
115
+ # layout to stop searching for other entries and continue rendering.
116
116
  #
117
117
  # For instance, the template above using +provide+ would be:
118
118
  #
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
199
199
  def _process_options(options) #:nodoc:
200
200
  super
201
201
  if options[:stream]
202
- if env["HTTP_VERSION"] == "HTTP/1.0"
202
+ if request.version == "HTTP/1.0"
203
203
  options.delete(:stream)
204
204
  else
205
205
  headers["Cache-Control"] ||= "no-cache"