actionpack 5.2.3

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 (170) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +429 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +265 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +166 -0
  10. data/lib/abstract_controller/callbacks.rb +212 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +31 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +66 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +276 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +78 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +274 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +152 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  34. data/lib/action_controller/metal/exceptions.rb +53 -0
  35. data/lib/action_controller/metal/flash.rb +61 -0
  36. data/lib/action_controller/metal/force_ssl.rb +99 -0
  37. data/lib/action_controller/metal/head.rb +60 -0
  38. data/lib/action_controller/metal/helpers.rb +123 -0
  39. data/lib/action_controller/metal/http_authentication.rb +519 -0
  40. data/lib/action_controller/metal/implicit_render.rb +73 -0
  41. data/lib/action_controller/metal/instrumentation.rb +107 -0
  42. data/lib/action_controller/metal/live.rb +312 -0
  43. data/lib/action_controller/metal/mime_responds.rb +313 -0
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +293 -0
  46. data/lib/action_controller/metal/redirecting.rb +133 -0
  47. data/lib/action_controller/metal/renderers.rb +181 -0
  48. data/lib/action_controller/metal/rendering.rb +122 -0
  49. data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
  50. data/lib/action_controller/metal/rescue.rb +28 -0
  51. data/lib/action_controller/metal/streaming.rb +223 -0
  52. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  53. data/lib/action_controller/metal/testing.rb +16 -0
  54. data/lib/action_controller/metal/url_for.rb +58 -0
  55. data/lib/action_controller/railtie.rb +89 -0
  56. data/lib/action_controller/railties/helpers.rb +24 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +629 -0
  60. data/lib/action_dispatch.rb +112 -0
  61. data/lib/action_dispatch/http/cache.rb +222 -0
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +84 -0
  64. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  65. data/lib/action_dispatch/http/headers.rb +132 -0
  66. data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
  67. data/lib/action_dispatch/http/mime_type.rb +342 -0
  68. data/lib/action_dispatch/http/mime_types.rb +50 -0
  69. data/lib/action_dispatch/http/parameter_filter.rb +86 -0
  70. data/lib/action_dispatch/http/parameters.rb +126 -0
  71. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  72. data/lib/action_dispatch/http/request.rb +430 -0
  73. data/lib/action_dispatch/http/response.rb +519 -0
  74. data/lib/action_dispatch/http/upload.rb +84 -0
  75. data/lib/action_dispatch/http/url.rb +350 -0
  76. data/lib/action_dispatch/journey.rb +7 -0
  77. data/lib/action_dispatch/journey/formatter.rb +189 -0
  78. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  81. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  82. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  85. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  86. data/lib/action_dispatch/journey/parser.rb +199 -0
  87. data/lib/action_dispatch/journey/parser.y +50 -0
  88. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  89. data/lib/action_dispatch/journey/path/pattern.rb +198 -0
  90. data/lib/action_dispatch/journey/route.rb +203 -0
  91. data/lib/action_dispatch/journey/router.rb +156 -0
  92. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  93. data/lib/action_dispatch/journey/routes.rb +82 -0
  94. data/lib/action_dispatch/journey/scanner.rb +64 -0
  95. data/lib/action_dispatch/journey/visitors.rb +268 -0
  96. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  97. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  98. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  99. data/lib/action_dispatch/middleware/callbacks.rb +36 -0
  100. data/lib/action_dispatch/middleware/cookies.rb +685 -0
  101. data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
  102. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +300 -0
  106. data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
  107. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  108. data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
  109. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  110. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  111. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  112. data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
  113. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  114. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  115. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  116. data/lib/action_dispatch/middleware/stack.rb +116 -0
  117. data/lib/action_dispatch/middleware/static.rb +130 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  139. data/lib/action_dispatch/railtie.rb +55 -0
  140. data/lib/action_dispatch/request/session.rb +234 -0
  141. data/lib/action_dispatch/request/utils.rb +78 -0
  142. data/lib/action_dispatch/routing.rb +260 -0
  143. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  144. data/lib/action_dispatch/routing/inspector.rb +225 -0
  145. data/lib/action_dispatch/routing/mapper.rb +2267 -0
  146. data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
  147. data/lib/action_dispatch/routing/redirection.rb +201 -0
  148. data/lib/action_dispatch/routing/route_set.rb +890 -0
  149. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  150. data/lib/action_dispatch/routing/url_for.rb +236 -0
  151. data/lib/action_dispatch/system_test_case.rb +147 -0
  152. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  153. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  154. data/lib/action_dispatch/system_testing/server.rb +31 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  158. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  159. data/lib/action_dispatch/testing/assertions.rb +24 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +107 -0
  161. data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
  162. data/lib/action_dispatch/testing/integration.rb +652 -0
  163. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  164. data/lib/action_dispatch/testing/test_process.rb +50 -0
  165. data/lib/action_dispatch/testing/test_request.rb +71 -0
  166. data/lib/action_dispatch/testing/test_response.rb +53 -0
  167. data/lib/action_pack.rb +26 -0
  168. data/lib/action_pack/gem_version.rb +17 -0
  169. data/lib/action_pack/version.rb +10 -0
  170. metadata +318 -0
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Redirecting
5
+ extend ActiveSupport::Concern
6
+
7
+ include AbstractController::Logger
8
+ include ActionController::UrlFor
9
+
10
+ # Redirects the browser to the target specified in +options+. This parameter can be any one of:
11
+ #
12
+ # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
13
+ # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
14
+ # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
15
+ # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
16
+ # * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
17
+ #
18
+ # === Examples:
19
+ #
20
+ # redirect_to action: "show", id: 5
21
+ # redirect_to @post
22
+ # redirect_to "http://www.rubyonrails.org"
23
+ # redirect_to "/images/screenshot.jpg"
24
+ # redirect_to posts_url
25
+ # redirect_to proc { edit_post_url(@post) }
26
+ #
27
+ # The redirection happens as a <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> option:
28
+ #
29
+ # redirect_to post_url(@post), status: :found
30
+ # redirect_to action: 'atom', status: :moved_permanently
31
+ # redirect_to post_url(@post), status: 301
32
+ # redirect_to action: 'atom', status: 302
33
+ #
34
+ # The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an
35
+ # integer, or a symbol representing the downcased, underscored and symbolized description.
36
+ # Note that the status code must be a 3xx HTTP code, or redirection will not occur.
37
+ #
38
+ # If you are using XHR requests other than GET or POST and redirecting after the
39
+ # request then some browsers will follow the redirect using the original request
40
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
41
+ # around this you can return a <tt>303 See Other</tt> status code which will be
42
+ # followed using a GET request.
43
+ #
44
+ # redirect_to posts_url, status: :see_other
45
+ # redirect_to action: 'index', status: 303
46
+ #
47
+ # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
48
+ # +alert+ and +notice+ as well as a general purpose +flash+ bucket.
49
+ #
50
+ # redirect_to post_url(@post), alert: "Watch it, mister!"
51
+ # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
52
+ # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
53
+ # redirect_to({ action: 'atom' }, alert: "Something serious happened")
54
+ #
55
+ # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
56
+ # To terminate the execution of the function immediately after the +redirect_to+, use return.
57
+ # redirect_to post_url(@post) and return
58
+ def redirect_to(options = {}, response_status = {})
59
+ raise ActionControllerError.new("Cannot redirect to nil!") unless options
60
+ raise AbstractController::DoubleRenderError if response_body
61
+
62
+ self.status = _extract_redirect_to_status(options, response_status)
63
+ self.location = _compute_redirect_to_location(request, options)
64
+ self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
65
+ end
66
+
67
+ # Redirects the browser to the page that issued the request (the referrer)
68
+ # if possible, otherwise redirects to the provided default fallback
69
+ # location.
70
+ #
71
+ # The referrer information is pulled from the HTTP +Referer+ (sic) header on
72
+ # the request. This is an optional header and its presence on the request is
73
+ # subject to browser security settings and user preferences. If the request
74
+ # is missing this header, the <tt>fallback_location</tt> will be used.
75
+ #
76
+ # redirect_back fallback_location: { action: "show", id: 5 }
77
+ # redirect_back fallback_location: @post
78
+ # redirect_back fallback_location: "http://www.rubyonrails.org"
79
+ # redirect_back fallback_location: "/images/screenshot.jpg"
80
+ # redirect_back fallback_location: posts_url
81
+ # redirect_back fallback_location: proc { edit_post_url(@post) }
82
+ # redirect_back fallback_location: '/', allow_other_host: false
83
+ #
84
+ # ==== Options
85
+ # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
86
+ # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
87
+ #
88
+ # All other options that can be passed to <tt>redirect_to</tt> are accepted as
89
+ # options and the behavior is identical.
90
+ def redirect_back(fallback_location:, allow_other_host: true, **args)
91
+ referer = request.headers["Referer"]
92
+ redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer))
93
+ redirect_to redirect_to_referer ? referer : fallback_location, **args
94
+ end
95
+
96
+ def _compute_redirect_to_location(request, options) #:nodoc:
97
+ case options
98
+ # The scheme name consist of a letter followed by any combination of
99
+ # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
100
+ # characters; and is terminated by a colon (":").
101
+ # See https://tools.ietf.org/html/rfc3986#section-3.1
102
+ # The protocol relative scheme starts with a double slash "//".
103
+ when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
104
+ options
105
+ when String
106
+ request.protocol + request.host_with_port + options
107
+ when Proc
108
+ _compute_redirect_to_location request, instance_eval(&options)
109
+ else
110
+ url_for(options)
111
+ end.delete("\0\r\n")
112
+ end
113
+ module_function :_compute_redirect_to_location
114
+ public :_compute_redirect_to_location
115
+
116
+ private
117
+ def _extract_redirect_to_status(options, response_status)
118
+ if options.is_a?(Hash) && options.key?(:status)
119
+ Rack::Utils.status_code(options.delete(:status))
120
+ elsif response_status.key?(:status)
121
+ Rack::Utils.status_code(response_status[:status])
122
+ else
123
+ 302
124
+ end
125
+ end
126
+
127
+ def _url_host_allowed?(url)
128
+ URI(url.to_s).host == request.host
129
+ rescue ArgumentError, URI::Error
130
+ false
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActionController
6
+ # See <tt>Renderers.add</tt>
7
+ def self.add_renderer(key, &block)
8
+ Renderers.add(key, &block)
9
+ end
10
+
11
+ # See <tt>Renderers.remove</tt>
12
+ def self.remove_renderer(key)
13
+ Renderers.remove(key)
14
+ end
15
+
16
+ # See <tt>Responder#api_behavior</tt>
17
+ class MissingRenderer < LoadError
18
+ def initialize(format)
19
+ super "No renderer defined for format: #{format}"
20
+ end
21
+ end
22
+
23
+ module Renderers
24
+ extend ActiveSupport::Concern
25
+
26
+ # A Set containing renderer names that correspond to available renderer procs.
27
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
28
+ RENDERERS = Set.new
29
+
30
+ included do
31
+ class_attribute :_renderers, default: Set.new.freeze
32
+ end
33
+
34
+ # Used in <tt>ActionController::Base</tt>
35
+ # and <tt>ActionController::API</tt> to include all
36
+ # renderers by default.
37
+ module All
38
+ extend ActiveSupport::Concern
39
+ include Renderers
40
+
41
+ included do
42
+ self._renderers = RENDERERS
43
+ end
44
+ end
45
+
46
+ # Adds a new renderer to call within controller actions.
47
+ # A renderer is invoked by passing its name as an option to
48
+ # <tt>AbstractController::Rendering#render</tt>. To create a renderer
49
+ # pass it a name and a block. The block takes two arguments, the first
50
+ # is the value paired with its key and the second is the remaining
51
+ # hash of options passed to +render+.
52
+ #
53
+ # Create a csv renderer:
54
+ #
55
+ # ActionController::Renderers.add :csv do |obj, options|
56
+ # filename = options[:filename] || 'data'
57
+ # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
58
+ # send_data str, type: Mime[:csv],
59
+ # disposition: "attachment; filename=#{filename}.csv"
60
+ # end
61
+ #
62
+ # Note that we used Mime[:csv] for the csv mime type as it comes with Rails.
63
+ # For a custom renderer, you'll need to register a mime type with
64
+ # <tt>Mime::Type.register</tt>.
65
+ #
66
+ # To use the csv renderer in a controller action:
67
+ #
68
+ # def show
69
+ # @csvable = Csvable.find(params[:id])
70
+ # respond_to do |format|
71
+ # format.html
72
+ # format.csv { render csv: @csvable, filename: @csvable.name }
73
+ # end
74
+ # end
75
+ def self.add(key, &block)
76
+ define_method(_render_with_renderer_method_name(key), &block)
77
+ RENDERERS << key.to_sym
78
+ end
79
+
80
+ # This method is the opposite of add method.
81
+ #
82
+ # To remove a csv renderer:
83
+ #
84
+ # ActionController::Renderers.remove(:csv)
85
+ def self.remove(key)
86
+ RENDERERS.delete(key.to_sym)
87
+ method_name = _render_with_renderer_method_name(key)
88
+ remove_possible_method(method_name)
89
+ end
90
+
91
+ def self._render_with_renderer_method_name(key)
92
+ "_render_with_renderer_#{key}"
93
+ end
94
+
95
+ module ClassMethods
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 least 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
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
154
+ end
155
+
156
+ add :json do |json, options|
157
+ json = json.to_json(options) unless json.kind_of?(String)
158
+
159
+ if options[:callback].present?
160
+ if content_type.nil? || content_type == Mime[:json]
161
+ self.content_type = Mime[:js]
162
+ end
163
+
164
+ "/**/#{options[:callback]}(#{json})"
165
+ else
166
+ self.content_type ||= Mime[:json]
167
+ json
168
+ end
169
+ end
170
+
171
+ add :js do |js, options|
172
+ self.content_type ||= Mime[:js]
173
+ js.respond_to?(:to_js) ? js.to_js(options) : js
174
+ end
175
+
176
+ add :xml do |xml, options|
177
+ self.content_type ||= Mime[:xml]
178
+ xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Rendering
5
+ extend ActiveSupport::Concern
6
+
7
+ RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html]
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
+
27
+ # Before processing, set the request formats in current controller formats.
28
+ def process_action(*) #:nodoc:
29
+ self.formats = request.formats.map(&:ref).compact
30
+ super
31
+ end
32
+
33
+ # Check for double render errors and set the content_type after rendering.
34
+ def render(*args) #:nodoc:
35
+ raise ::AbstractController::DoubleRenderError if response_body
36
+ super
37
+ end
38
+
39
+ # Overwrite render_to_string because body can now be set to a Rack body.
40
+ def render_to_string(*)
41
+ result = super
42
+ if result.respond_to?(:each)
43
+ string = "".dup
44
+ result.each { |r| string << r }
45
+ string
46
+ else
47
+ result
48
+ end
49
+ end
50
+
51
+ def render_to_body(options = {})
52
+ super || _render_in_priorities(options) || " "
53
+ end
54
+
55
+ private
56
+
57
+ def _process_variant(options)
58
+ if defined?(request) && !request.nil? && request.variant.present?
59
+ options[:variant] = request.variant
60
+ end
61
+ end
62
+
63
+ def _render_in_priorities(options)
64
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
65
+ return options[format] if options.key?(format)
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ def _set_html_content_type
72
+ self.content_type = Mime[:html].to_s
73
+ end
74
+
75
+ def _set_rendered_content_type(format)
76
+ if format && !response.content_type
77
+ self.content_type = format.to_s
78
+ end
79
+ end
80
+
81
+ # Normalize arguments by catching blocks and setting them on :update.
82
+ def _normalize_args(action = nil, options = {}, &blk)
83
+ options = super
84
+ options[:update] = blk if block_given?
85
+ options
86
+ end
87
+
88
+ # Normalize both text and status options.
89
+ def _normalize_options(options)
90
+ _normalize_text(options)
91
+
92
+ if options[:html]
93
+ options[:html] = ERB::Util.html_escape(options[:html])
94
+ end
95
+
96
+ if options[:status]
97
+ options[:status] = Rack::Utils.status_code(options[:status])
98
+ end
99
+
100
+ super
101
+ end
102
+
103
+ def _normalize_text(options)
104
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
105
+ if options.key?(format) && options[format].respond_to?(:to_text)
106
+ options[format] = options[format].to_text
107
+ end
108
+ end
109
+ end
110
+
111
+ # Process controller specific options, as status, content-type and location.
112
+ def _process_options(options)
113
+ status, content_type, location = options.values_at(:status, :content_type, :location)
114
+
115
+ self.status = status if status
116
+ self.content_type = content_type if content_type
117
+ headers["Location"] = url_for(location) if location
118
+
119
+ super
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,445 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/session/abstract/id"
4
+ require "action_controller/metal/exceptions"
5
+ require "active_support/security_utils"
6
+ require "active_support/core_ext/string/strip"
7
+
8
+ module ActionController #:nodoc:
9
+ class InvalidAuthenticityToken < ActionControllerError #:nodoc:
10
+ end
11
+
12
+ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
13
+ end
14
+
15
+ # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
16
+ # by including a token in the rendered HTML for your application. This token is
17
+ # stored as a random string in the session, to which an attacker does not have
18
+ # access. When a request reaches your application, \Rails verifies the received
19
+ # token with the token in the session. All requests are checked except GET requests
20
+ # as these should be idempotent. Keep in mind that all session-oriented requests
21
+ # should be CSRF protected, including JavaScript and HTML requests.
22
+ #
23
+ # Since HTML and JavaScript requests are typically made from the browser, we
24
+ # need to ensure to verify request authenticity for the web browser. We can
25
+ # use session-oriented authentication for these types of requests, by using
26
+ # the <tt>protect_from_forgery</tt> method in our controllers.
27
+ #
28
+ # GET requests are not protected since they don't have side effects like writing
29
+ # to the database and don't leak sensitive information. JavaScript requests are
30
+ # an exception: a third-party site can use a <script> tag to reference a JavaScript
31
+ # URL on your site. When your JavaScript response loads on their site, it executes.
32
+ # With carefully crafted JavaScript on their end, sensitive data in your JavaScript
33
+ # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
34
+ # Ajax) requests are allowed to make GET requests for JavaScript responses.
35
+ #
36
+ # It's important to remember that XML or JSON requests are also affected and if
37
+ # you're building an API you should change forgery protection method in
38
+ # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
39
+ #
40
+ # class ApplicationController < ActionController::Base
41
+ # protect_from_forgery unless: -> { request.format.json? }
42
+ # end
43
+ #
44
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
45
+ # By default <tt>protect_from_forgery</tt> protects your session with
46
+ # <tt>:null_session</tt> method, which provides an empty session
47
+ # during request.
48
+ #
49
+ # We may want to disable CSRF protection for APIs since they are typically
50
+ # designed to be state-less. That is, the request API client will handle
51
+ # the session for you instead of Rails.
52
+ #
53
+ # The token parameter is named <tt>authenticity_token</tt> by default. The name and
54
+ # value of this token must be added to every layout that renders forms by including
55
+ # <tt>csrf_meta_tags</tt> in the HTML +head+.
56
+ #
57
+ # Learn more about CSRF attacks and securing your application in the
58
+ # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
59
+ module RequestForgeryProtection
60
+ extend ActiveSupport::Concern
61
+
62
+ include AbstractController::Helpers
63
+ include AbstractController::Callbacks
64
+
65
+ included do
66
+ # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
67
+ # sets it to <tt>:authenticity_token</tt> by default.
68
+ config_accessor :request_forgery_protection_token
69
+ self.request_forgery_protection_token ||= :authenticity_token
70
+
71
+ # Holds the class which implements the request forgery protection.
72
+ config_accessor :forgery_protection_strategy
73
+ self.forgery_protection_strategy = nil
74
+
75
+ # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
76
+ config_accessor :allow_forgery_protection
77
+ self.allow_forgery_protection = true if allow_forgery_protection.nil?
78
+
79
+ # Controls whether a CSRF failure logs a warning. On by default.
80
+ config_accessor :log_warning_on_csrf_failure
81
+ self.log_warning_on_csrf_failure = true
82
+
83
+ # Controls whether the Origin header is checked in addition to the CSRF token.
84
+ config_accessor :forgery_protection_origin_check
85
+ self.forgery_protection_origin_check = false
86
+
87
+ # Controls whether form-action/method specific CSRF tokens are used.
88
+ config_accessor :per_form_csrf_tokens
89
+ self.per_form_csrf_tokens = false
90
+
91
+ # Controls whether forgery protection is enabled by default.
92
+ config_accessor :default_protect_from_forgery
93
+ self.default_protect_from_forgery = false
94
+
95
+ helper_method :form_authenticity_token
96
+ helper_method :protect_against_forgery?
97
+ end
98
+
99
+ module ClassMethods
100
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
101
+ #
102
+ # class ApplicationController < ActionController::Base
103
+ # protect_from_forgery
104
+ # end
105
+ #
106
+ # class FooController < ApplicationController
107
+ # protect_from_forgery except: :index
108
+ # end
109
+ #
110
+ # You can disable forgery protection on controller by skipping the verification before_action:
111
+ #
112
+ # skip_before_action :verify_authenticity_token
113
+ #
114
+ # Valid Options:
115
+ #
116
+ # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
117
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
118
+ # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
119
+ # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
120
+ # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
121
+ #
122
+ # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
123
+ # * <tt>:with</tt> - Set the method to handle unverified request.
124
+ #
125
+ # Valid unverified request handling methods are:
126
+ # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
127
+ # * <tt>:reset_session</tt> - Resets the session.
128
+ # * <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.
129
+ def protect_from_forgery(options = {})
130
+ options = options.reverse_merge(prepend: false)
131
+
132
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
133
+ self.request_forgery_protection_token ||= :authenticity_token
134
+ before_action :verify_authenticity_token, options
135
+ append_after_action :verify_same_origin_request
136
+ end
137
+
138
+ # Turn off request forgery protection. This is a wrapper for:
139
+ #
140
+ # skip_before_action :verify_authenticity_token
141
+ #
142
+ # See +skip_before_action+ for allowed options.
143
+ def skip_forgery_protection(options = {})
144
+ skip_before_action :verify_authenticity_token, options
145
+ end
146
+
147
+ private
148
+
149
+ def protection_method_class(name)
150
+ ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
151
+ rescue NameError
152
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session"
153
+ end
154
+ end
155
+
156
+ module ProtectionMethods
157
+ class NullSession
158
+ def initialize(controller)
159
+ @controller = controller
160
+ end
161
+
162
+ # This is the method that defines the application behavior when a request is found to be unverified.
163
+ def handle_unverified_request
164
+ request = @controller.request
165
+ request.session = NullSessionHash.new(request)
166
+ request.flash = nil
167
+ request.session_options = { skip: true }
168
+ request.cookie_jar = NullCookieJar.build(request, {})
169
+ end
170
+
171
+ private
172
+
173
+ class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
174
+ def initialize(req)
175
+ super(nil, req)
176
+ @data = {}
177
+ @loaded = true
178
+ end
179
+
180
+ # no-op
181
+ def destroy; end
182
+
183
+ def exists?
184
+ true
185
+ end
186
+ end
187
+
188
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
189
+ def write(*)
190
+ # nothing
191
+ end
192
+ end
193
+ end
194
+
195
+ class ResetSession
196
+ def initialize(controller)
197
+ @controller = controller
198
+ end
199
+
200
+ def handle_unverified_request
201
+ @controller.reset_session
202
+ end
203
+ end
204
+
205
+ class Exception
206
+ def initialize(controller)
207
+ @controller = controller
208
+ end
209
+
210
+ def handle_unverified_request
211
+ raise ActionController::InvalidAuthenticityToken
212
+ end
213
+ end
214
+ end
215
+
216
+ private
217
+ # The actual before_action that is used to verify the CSRF token.
218
+ # Don't override this directly. Provide your own forgery protection
219
+ # strategy instead. If you override, you'll disable same-origin
220
+ # <tt><script></tt> verification.
221
+ #
222
+ # Lean on the protect_from_forgery declaration to mark which actions are
223
+ # due for same-origin request verification. If protect_from_forgery is
224
+ # enabled on an action, this before_action flags its after_action to
225
+ # verify that JavaScript responses are for XHR requests, ensuring they
226
+ # follow the browser's same-origin policy.
227
+ def verify_authenticity_token # :doc:
228
+ mark_for_same_origin_verification!
229
+
230
+ if !verified_request?
231
+ if logger && log_warning_on_csrf_failure
232
+ if valid_request_origin?
233
+ logger.warn "Can't verify CSRF token authenticity."
234
+ else
235
+ logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
236
+ end
237
+ end
238
+ handle_unverified_request
239
+ end
240
+ end
241
+
242
+ def handle_unverified_request # :doc:
243
+ forgery_protection_strategy.new(self).handle_unverified_request
244
+ end
245
+
246
+ #:nodoc:
247
+ CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
248
+ "<script> tag on another site requested protected JavaScript. " \
249
+ "If you know what you're doing, go ahead and disable forgery " \
250
+ "protection on this action to permit cross-origin JavaScript embedding."
251
+ private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
252
+ # :startdoc:
253
+
254
+ # If +verify_authenticity_token+ was run (indicating that we have
255
+ # forgery protection enabled for this request) then also verify that
256
+ # we aren't serving an unauthorized cross-origin response.
257
+ def verify_same_origin_request # :doc:
258
+ if marked_for_same_origin_verification? && non_xhr_javascript_response?
259
+ if logger && log_warning_on_csrf_failure
260
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
261
+ end
262
+ raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
263
+ end
264
+ end
265
+
266
+ # GET requests are checked for cross-origin JavaScript after rendering.
267
+ def mark_for_same_origin_verification! # :doc:
268
+ @marked_for_same_origin_verification = request.get?
269
+ end
270
+
271
+ # If the +verify_authenticity_token+ before_action ran, verify that
272
+ # JavaScript responses are only served to same-origin GET requests.
273
+ def marked_for_same_origin_verification? # :doc:
274
+ @marked_for_same_origin_verification ||= false
275
+ end
276
+
277
+ # Check for cross-origin JavaScript responses.
278
+ def non_xhr_javascript_response? # :doc:
279
+ content_type =~ %r(\Atext/javascript) && !request.xhr?
280
+ end
281
+
282
+ AUTHENTICITY_TOKEN_LENGTH = 32
283
+
284
+ # Returns true or false if a request is verified. Checks:
285
+ #
286
+ # * Is it a GET or HEAD request? GETs should be safe and idempotent
287
+ # * Does the form_authenticity_token match the given token value from the params?
288
+ # * Does the X-CSRF-Token header match the form_authenticity_token?
289
+ def verified_request? # :doc:
290
+ !protect_against_forgery? || request.get? || request.head? ||
291
+ (valid_request_origin? && any_authenticity_token_valid?)
292
+ end
293
+
294
+ # Checks if any of the authenticity tokens from the request are valid.
295
+ def any_authenticity_token_valid? # :doc:
296
+ request_authenticity_tokens.any? do |token|
297
+ valid_authenticity_token?(session, token)
298
+ end
299
+ end
300
+
301
+ # Possible authenticity tokens sent in the request.
302
+ def request_authenticity_tokens # :doc:
303
+ [form_authenticity_param, request.x_csrf_token]
304
+ end
305
+
306
+ # Sets the token value for the current session.
307
+ def form_authenticity_token(form_options: {})
308
+ masked_authenticity_token(session, form_options: form_options)
309
+ end
310
+
311
+ # Creates a masked version of the authenticity token that varies
312
+ # on each request. The masking is used to mitigate SSL attacks
313
+ # like BREACH.
314
+ def masked_authenticity_token(session, form_options: {}) # :doc:
315
+ action, method = form_options.values_at(:action, :method)
316
+
317
+ raw_token = if per_form_csrf_tokens && action && method
318
+ action_path = normalize_action_path(action)
319
+ per_form_csrf_token(session, action_path, method)
320
+ else
321
+ real_csrf_token(session)
322
+ end
323
+
324
+ one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
325
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
326
+ masked_token = one_time_pad + encrypted_csrf_token
327
+ Base64.strict_encode64(masked_token)
328
+ end
329
+
330
+ # Checks the client's masked token to see if it matches the
331
+ # session token. Essentially the inverse of
332
+ # +masked_authenticity_token+.
333
+ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
334
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
335
+ return false
336
+ end
337
+
338
+ begin
339
+ masked_token = Base64.strict_decode64(encoded_masked_token)
340
+ rescue ArgumentError # encoded_masked_token is invalid Base64
341
+ return false
342
+ end
343
+
344
+ # See if it's actually a masked token or not. In order to
345
+ # deploy this code, we should be able to handle any unmasked
346
+ # tokens that we've issued without error.
347
+
348
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
349
+ # This is actually an unmasked token. This is expected if
350
+ # you have just upgraded to masked tokens, but should stop
351
+ # happening shortly after installing this gem.
352
+ compare_with_real_token masked_token, session
353
+
354
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
355
+ csrf_token = unmask_token(masked_token)
356
+
357
+ compare_with_real_token(csrf_token, session) ||
358
+ valid_per_form_csrf_token?(csrf_token, session)
359
+ else
360
+ false # Token is malformed.
361
+ end
362
+ end
363
+
364
+ def unmask_token(masked_token) # :doc:
365
+ # Split the token into the one-time pad and the encrypted
366
+ # value and decrypt it.
367
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
368
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
369
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
370
+ end
371
+
372
+ def compare_with_real_token(token, session) # :doc:
373
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
374
+ end
375
+
376
+ def valid_per_form_csrf_token?(token, session) # :doc:
377
+ if per_form_csrf_tokens
378
+ correct_token = per_form_csrf_token(
379
+ session,
380
+ normalize_action_path(request.fullpath),
381
+ request.request_method
382
+ )
383
+
384
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
385
+ else
386
+ false
387
+ end
388
+ end
389
+
390
+ def real_csrf_token(session) # :doc:
391
+ session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
392
+ Base64.strict_decode64(session[:_csrf_token])
393
+ end
394
+
395
+ def per_form_csrf_token(session, action_path, method) # :doc:
396
+ OpenSSL::HMAC.digest(
397
+ OpenSSL::Digest::SHA256.new,
398
+ real_csrf_token(session),
399
+ [action_path, method.downcase].join("#")
400
+ )
401
+ end
402
+
403
+ def xor_byte_strings(s1, s2) # :doc:
404
+ s2_bytes = s2.bytes
405
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
406
+ s2_bytes.pack("C*")
407
+ end
408
+
409
+ # The form's authenticity parameter. Override to provide your own.
410
+ def form_authenticity_param # :doc:
411
+ params[request_forgery_protection_token]
412
+ end
413
+
414
+ # Checks if the controller allows forgery protection.
415
+ def protect_against_forgery? # :doc:
416
+ allow_forgery_protection
417
+ end
418
+
419
+ NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc
420
+ The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
421
+ means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
422
+ refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
423
+ best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
424
+ If you cannot change the referrer policy, you can disable origin checking with the
425
+ Rails.application.config.action_controller.forgery_protection_origin_check setting.
426
+ MSG
427
+
428
+ # Checks if the request originated from the same origin by looking at the
429
+ # Origin header.
430
+ def valid_request_origin? # :doc:
431
+ if forgery_protection_origin_check
432
+ # We accept blank origin headers because some user agents don't send it.
433
+ raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
434
+ request.origin.nil? || request.origin == request.base_url
435
+ else
436
+ true
437
+ end
438
+ end
439
+
440
+ def normalize_action_path(action_path) # :doc:
441
+ uri = URI.parse(action_path)
442
+ uri.path.chomp("/")
443
+ end
444
+ end
445
+ end