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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +890 -384
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/base.rb +28 -38
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
- data/lib/abstract_controller/caching.rb +62 -0
- data/lib/abstract_controller/callbacks.rb +54 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/error.rb +4 -0
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +28 -18
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/abstract_controller.rb +6 -2
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/api.rb +147 -0
- data/lib/action_controller/base.rb +14 -11
- data/lib/action_controller/caching.rb +13 -58
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +3 -10
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +106 -34
- data/lib/action_controller/metal/cookies.rb +1 -3
- data/lib/action_controller/metal/data_streaming.rb +14 -34
- data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +11 -11
- data/lib/action_controller/metal/head.rb +14 -8
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +44 -35
- data/lib/action_controller/metal/implicit_render.rb +61 -6
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/live.rb +71 -88
- data/lib/action_controller/metal/mime_responds.rb +27 -42
- data/lib/action_controller/metal/params_wrapper.rb +9 -9
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +83 -40
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
- data/lib/action_controller/metal/rescue.rb +3 -12
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +527 -134
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/metal.rb +88 -63
- data/lib/action_controller/railtie.rb +11 -7
- data/lib/action_controller/renderer.rb +113 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +311 -374
- data/lib/action_controller.rb +12 -9
- data/lib/action_dispatch/http/cache.rb +73 -34
- data/lib/action_dispatch/http/filter_parameters.rb +16 -12
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +45 -14
- data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
- data/lib/action_dispatch/http/mime_type.rb +126 -90
- data/lib/action_dispatch/http/mime_types.rb +3 -4
- data/lib/action_dispatch/http/parameter_filter.rb +19 -9
- data/lib/action_dispatch/http/parameters.rb +70 -40
- data/lib/action_dispatch/http/request.rb +144 -89
- data/lib/action_dispatch/http/response.rb +215 -102
- data/lib/action_dispatch/http/upload.rb +6 -2
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +47 -30
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser.rb +2 -0
- data/lib/action_dispatch/journey/parser_extras.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +38 -42
- data/lib/action_dispatch/journey/route.rb +88 -26
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/router.rb +8 -10
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +89 -44
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/cookies.rb +188 -134
- data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
- data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
- data/lib/action_dispatch/middleware/executor.rb +19 -0
- data/lib/action_dispatch/middleware/flash.rb +66 -45
- data/lib/action_dispatch/middleware/params_parser.rb +32 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +14 -58
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +124 -36
- data/lib/action_dispatch/middleware/stack.rb +44 -40
- data/lib/action_dispatch/middleware/static.rb +51 -35
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +2 -2
- data/lib/action_dispatch/request/session.rb +69 -33
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing/inspector.rb +32 -43
- data/lib/action_dispatch/routing/mapper.rb +515 -348
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +5 -4
- data/lib/action_dispatch/routing/route_set.rb +148 -240
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/routing.rb +17 -13
- data/lib/action_dispatch/testing/assertion_response.rb +45 -0
- data/lib/action_dispatch/testing/assertions/response.rb +38 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +377 -149
- data/lib/action_dispatch/testing/request_encoder.rb +53 -0
- data/lib/action_dispatch/testing/test_process.rb +24 -20
- data/lib/action_dispatch/testing/test_request.rb +22 -31
- data/lib/action_dispatch/testing/test_response.rb +12 -4
- data/lib/action_dispatch.rb +4 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +32 -34
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
- /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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
41
|
-
|
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
|
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
|
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
|
-
#
|
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
|
-
|
105
|
-
|
106
|
-
|
90
|
+
def self._render_with_renderer_method_name(key)
|
91
|
+
"_render_with_renderer_#{key}"
|
92
|
+
end
|
107
93
|
|
108
|
-
|
109
|
-
|
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
|
118
|
-
self.content_type = Mime
|
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
|
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
|
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
|
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
|
46
|
-
|
65
|
+
def _set_html_content_type
|
66
|
+
self.content_type = Mime[:html].to_s
|
67
|
+
end
|
47
68
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
self.content_type ||= format.to_s
|
69
|
+
def _set_rendered_content_type(format)
|
70
|
+
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.
|
17
|
-
#
|
18
|
-
#
|
16
|
+
# token with the token in the session. All requests are checked except GET requests
|
17
|
+
# as these should be idempotent. Keep in mind that all session-oriented requests
|
18
|
+
# should be CSRF protected, including JavaScript and HTML requests.
|
19
|
+
#
|
20
|
+
# Since HTML and JavaScript requests are typically made from the browser, we
|
21
|
+
# need to ensure to verify request authenticity for the web browser. We can
|
22
|
+
# use session-oriented authentication for these types of requests, by using
|
23
|
+
# the `protect_from_forgery` method in our controllers.
|
19
24
|
#
|
20
25
|
# GET requests are not protected since they don't have side effects like writing
|
21
26
|
# to the database and don't leak sensitive information. JavaScript requests are
|
@@ -26,22 +31,21 @@ module ActionController #:nodoc:
|
|
26
31
|
# Ajax) requests are allowed to make GET requests for JavaScript responses.
|
27
32
|
#
|
28
33
|
# It's important to remember that XML or JSON requests are also affected and if
|
29
|
-
# you're building an API you
|
34
|
+
# you're building an API you should change forgery protection method in
|
35
|
+
# <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
|
30
36
|
#
|
31
37
|
# class ApplicationController < ActionController::Base
|
32
|
-
# protect_from_forgery
|
33
|
-
# skip_before_action :verify_authenticity_token, if: :json_request?
|
34
|
-
#
|
35
|
-
# protected
|
36
|
-
#
|
37
|
-
# def json_request?
|
38
|
-
# request.format.json?
|
39
|
-
# end
|
38
|
+
# protect_from_forgery unless: -> { request.format.json? }
|
40
39
|
# end
|
41
40
|
#
|
42
|
-
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method
|
43
|
-
#
|
44
|
-
#
|
41
|
+
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
|
42
|
+
# By default <tt>protect_from_forgery</tt> protects your session with
|
43
|
+
# <tt>:null_session</tt> method, which provides an empty session
|
44
|
+
# during request.
|
45
|
+
#
|
46
|
+
# We may want to disable CSRF protection for APIs since they are typically
|
47
|
+
# designed to be state-less. That is, the request API client will handle
|
48
|
+
# the session for you instead of Rails.
|
45
49
|
#
|
46
50
|
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
47
51
|
# value of this token must be added to every layout that renders forms by including
|
@@ -73,6 +77,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> -
|
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
|
-
|
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
|
128
|
-
request.
|
129
|
-
request.
|
130
|
-
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(
|
137
|
-
super(nil,
|
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
|
-
# *
|
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
|
-
|
255
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
3
|
-
# to controllers and
|
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
|
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
|
114
|
-
#
|
115
|
-
#
|
113
|
+
# assets, which kills the purpose of streaming. For this purpose, you can use
|
114
|
+
# a helper called +provide+ that does the same as +content_for+ but tells the
|
115
|
+
# layout to stop searching for other entries and continue rendering.
|
116
116
|
#
|
117
117
|
# For instance, the template above using +provide+ would be:
|
118
118
|
#
|
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
|
|
199
199
|
def _process_options(options) #:nodoc:
|
200
200
|
super
|
201
201
|
if options[:stream]
|
202
|
-
if
|
202
|
+
if request.version == "HTTP/1.0"
|
203
203
|
options.delete(:stream)
|
204
204
|
else
|
205
205
|
headers["Cache-Control"] ||= "no-cache"
|