diff options
Diffstat (limited to 'actionpack')
56 files changed, 518 insertions, 254 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 44d87878a4..a370c3fc99 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,21 @@ -## Rails 6.0.0.alpha (Unreleased) ## +* Output only one Content-Security-Policy nonce header value per request. + + Fixes #32597. + + *Andrey Novikov*, *Andrew White* + +* Move default headers configuration into their own module that can be included in controllers. + + *Kevin Deisz* + +* Add method `dig` to `session`. + + *claudiob*, *Takumi Shotoku* + +* Controller level `force_ssl` has been deprecated in favor of + `config.force_ssl`. + + *Derek Prior* * Rails 6 requires Ruby 2.4.1 or newer. diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 4dd7c59ce8..e99eb1723a 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -28,7 +28,7 @@ namespace :test do end task :lines do - load File.expand_path("..", __dir__) + "/tools/line_statistics" + load File.expand_path("../tools/line_statistics", __dir__) files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 146d17cf40..42bab411d2 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -103,6 +103,10 @@ module AbstractController # :call-seq: before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. + # + # If the callback renders or redirects, the action will not run. If there + # are additional callbacks scheduled to run after that callback, they are + # also cancelled. ## # :method: prepend_before_action @@ -110,6 +114,10 @@ module AbstractController # :call-seq: prepend_before_action(names, block) # # Prepend a callback before actions. See _insert_callbacks for parameter details. + # + # If the callback renders or redirects, the action will not run. If there + # are additional callbacks scheduled to run after that callback, they are + # also cancelled. ## # :method: skip_before_action @@ -124,6 +132,10 @@ module AbstractController # :call-seq: append_before_action(names, block) # # Append a callback before actions. See _insert_callbacks for parameter details. + # + # If the callback renders or redirects, the action will not run. If there + # are additional callbacks scheduled to run after that callback, they are + # also cancelled. ## # :method: after_action diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index f43784f9f2..29d61c3ceb 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -25,6 +25,7 @@ module ActionController autoload :ContentSecurityPolicy autoload :Cookies autoload :DataStreaming + autoload :DefaultHeaders autoload :EtagWithTemplateDigest autoload :EtagWithFlash autoload :Flash diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index b192e496de..93ffff1bd6 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -122,6 +122,7 @@ module ActionController ForceSSL, DataStreaming, + DefaultHeaders, # Before callbacks should also be executed as early as possible, so # also include them at the bottom. diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 204a3d400c..3378d6db0f 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -232,6 +232,7 @@ module ActionController HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, + DefaultHeaders, # Before callbacks should also be executed as early as possible, so # also include them at the bottom. @@ -264,12 +265,6 @@ module ActionController PROTECTED_IVARS end - def self.make_response!(request) - ActionDispatch::Response.create.tap do |res| - res.request = request - end - end - ActiveSupport.run_load_hooks(:action_controller_base, self) ActiveSupport.run_load_hooks(:action_controller, self) end diff --git a/actionpack/lib/action_controller/metal/content_security_policy.rb b/actionpack/lib/action_controller/metal/content_security_policy.rb index 95f2f3242d..b8fab4ebe3 100644 --- a/actionpack/lib/action_controller/metal/content_security_policy.rb +++ b/actionpack/lib/action_controller/metal/content_security_policy.rb @@ -14,13 +14,17 @@ module ActionController #:nodoc: end module ClassMethods - def content_security_policy(**options, &block) + def content_security_policy(enabled = true, **options, &block) before_action(options) do if block_given? - policy = request.content_security_policy.clone + policy = current_content_security_policy yield policy request.content_security_policy = policy end + + unless enabled + request.content_security_policy = nil + end end end @@ -40,5 +44,9 @@ module ActionController #:nodoc: def content_security_policy_nonce request.content_security_policy_nonce end + + def current_content_security_policy + request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new + end end end diff --git a/actionpack/lib/action_controller/metal/default_headers.rb b/actionpack/lib/action_controller/metal/default_headers.rb new file mode 100644 index 0000000000..eef0602fcd --- /dev/null +++ b/actionpack/lib/action_controller/metal/default_headers.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionController + # Allows configuring default headers that will be automatically merged into + # each response. + module DefaultHeaders + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + ActionDispatch::Response.create.tap do |res| + res.request = request + end + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index a65857d6ef..58db4a1458 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -22,7 +22,7 @@ module ActionController end end - class ActionController::UrlGenerationError < ActionControllerError #:nodoc: + class UrlGenerationError < ActionControllerError #:nodoc: end class MethodNotAllowed < ActionControllerError #:nodoc: diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index 7de500d119..8d53a30e93 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -4,18 +4,10 @@ require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/slice" module ActionController - # This module provides a method which will redirect the browser to use the secured HTTPS - # protocol. This will ensure that users' sensitive information will be - # transferred safely over the internet. You _should_ always force the browser - # to use HTTPS when you're transferring sensitive information such as - # user authentication, account information, or credit card information. - # - # Note that if you are really concerned about your application security, - # you might consider using +config.force_ssl+ in your config file instead. - # That will ensure all the data is transferred via HTTPS, and will - # prevent the user from getting their session hijacked when accessing the - # site over unsecured HTTP protocol. - module ForceSSL + # This module is deprecated in favor of +config.force_ssl+ in your environment + # config file. This will ensure all communication to non-whitelisted endpoints + # served by your application occurs over HTTPS. + module ForceSSL # :nodoc: extend ActiveSupport::Concern include AbstractController::Callbacks @@ -23,45 +15,17 @@ module ActionController URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] - module ClassMethods - # Force the request to this particular controller or specified actions to be - # through the HTTPS protocol. - # - # If you need to disable this for any reason (e.g. development) then you can use - # an +:if+ or +:unless+ condition. - # - # class AccountsController < ApplicationController - # force_ssl if: :ssl_configured? - # - # def ssl_configured? - # !Rails.env.development? - # end - # end - # - # ==== URL Options - # You can pass any of the following options to affect the redirect URL - # * <tt>host</tt> - Redirect to a different host name - # * <tt>subdomain</tt> - Redirect to a different subdomain - # * <tt>domain</tt> - Redirect to a different domain - # * <tt>port</tt> - Redirect to a non-standard port - # * <tt>path</tt> - Redirect to a different path - # - # ==== Redirect Options - # You can pass any of the following options to affect the redirect status and response - # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) - # * <tt>flash</tt> - Set a flash message when redirecting - # * <tt>alert</tt> - Set an alert message when redirecting - # * <tt>notice</tt> - Set a notice message when redirecting - # - # ==== Action Options - # You can pass any of the following options to affect the before_action callback - # * <tt>only</tt> - The callback should be run only for this action - # * <tt>except</tt> - The callback should be run for all actions except this action - # * <tt>if</tt> - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a true value. - # * <tt>unless</tt> - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a false value. + module ClassMethods # :nodoc: def force_ssl(options = {}) + ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) + Controller-level `force_ssl` is deprecated and will be removed from + Rails 6.1. Please enable `config.force_ssl` in your environment + configuration to enable the ActionDispatch::SSL middleware to more + fully enforce that your application communicate over HTTPS. If needed, + you can use `config.ssl_options` to exempt matching endpoints from + being redirected to HTTPS. + MESSAGE + action_options = options.slice(*ACTION_OPTIONS) redirect_options = options.except(*ACTION_OPTIONS) before_action(action_options) do @@ -70,11 +34,6 @@ module ActionController end end - # Redirect the existing request to use the HTTPS protocol. - # - # ==== Parameters - # * <tt>host_or_options</tt> - Either a host name or any of the URL and - # redirect options available to the <tt>force_ssl</tt> method. def force_ssl_redirect(host_or_options = nil) unless request.ssl? options = { diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 94092de96c..fc9cf8aaff 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -417,7 +417,7 @@ module ActionController #:nodoc: NULL_ORIGIN_MESSAGE = <<~MSG The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually - means you have the 'no-referrer' Referrer-Policy header enabled, or that you the request came from a site that + means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin. If you cannot change the referrer policy, you can disable origin checking with the diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 615c90c496..5a06bf86e3 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -374,7 +374,7 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - Array.wrap(value).each do |v| + Array.wrap(value).flatten.each do |v| v.permit! if v.respond_to? :permit! end end @@ -580,19 +580,18 @@ module ActionController ) end - if Hash.method_defined?(:dig) - # Extracts the nested parameter from the given +keys+ by calling +dig+ - # at each step. Returns +nil+ if any intermediate step is +nil+. - # - # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) - # params.dig(:foo, :bar, :baz) # => 1 - # params.dig(:foo, :zot, :xyz) # => nil - # - # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) - # params2.dig(:foo, 1) # => 11 - def dig(*keys) - convert_value_to_parameters(@parameters.dig(*keys)) - end + # Extracts the nested parameter from the given +keys+ by calling +dig+ + # at each step. Returns +nil+ if any intermediate step is +nil+. + # + # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) + # params.dig(:foo, :bar, :baz) # => 1 + # params.dig(:foo, :zot, :xyz) # => nil + # + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 + def dig(*keys) + convert_hashes_to_parameters(keys.first, @parameters[keys.first]) + @parameters.dig(*keys) end # Returns a new <tt>ActionController::Parameters</tt> instance that diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index a3407c9698..17e72b46ff 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -21,13 +21,8 @@ module ActionDispatch #:nodoc: return response if policy_present?(headers) if policy = request.content_security_policy - if policy.directives["script-src"] - if nonce = request.content_security_policy_nonce - policy.directives["script-src"] << "'nonce-#{nonce}'" - end - end - - headers[header_name(request)] = policy.build(request.controller_instance) + nonce = request.content_security_policy_nonce + headers[header_name(request)] = policy.build(request.controller_instance, nonce) end response @@ -113,7 +108,9 @@ module ActionDispatch #:nodoc: blob: "blob:", filesystem: "filesystem:", report_sample: "'report-sample'", - strict_dynamic: "'strict-dynamic'" + strict_dynamic: "'strict-dynamic'", + ws: "ws:", + wss: "wss:" }.freeze DIRECTIVES = { @@ -134,7 +131,9 @@ module ActionDispatch #:nodoc: worker_src: "worker-src" }.freeze - private_constant :MAPPINGS, :DIRECTIVES + NONCE_DIRECTIVES = %w[script-src].freeze + + private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES attr_reader :directives @@ -203,8 +202,8 @@ module ActionDispatch #:nodoc: end end - def build(context = nil) - build_directives(context).compact.join("; ") + def build(context = nil, nonce = nil) + build_directives(context, nonce).compact.join("; ") end private @@ -227,10 +226,14 @@ module ActionDispatch #:nodoc: end end - def build_directives(context) + def build_directives(context, nonce) @directives.map do |directive, sources| if sources.is_a?(Array) - "#{directive} #{build_directive(sources, context).join(' ')}" + if nonce && nonce_directive?(directive) + "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'" + else + "#{directive} #{build_directive(sources, context).join(' ')}" + end elsif sources directive else @@ -259,5 +262,9 @@ module ActionDispatch #:nodoc: raise RuntimeError, "Unexpected content security policy source: #{source.inspect}" end end + + def nonce_directive?(directive) + NONCE_DIRECTIVES.include?(directive) + end end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 3e11846778..fd05eec172 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -73,7 +73,7 @@ module ActionDispatch end end - def reset_session # :nodoc + def reset_session # :nodoc: super self.flash = nil end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 6d9f36ad75..240269d1c7 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -15,6 +15,8 @@ module ActionDispatch # # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } # + # Cookies will not be flagged as secure for excluded requests. + # # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they # must not be sent along with +http://+ requests. Enabled by default. Set # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature. @@ -71,7 +73,7 @@ module ActionDispatch if request.ssl? @app.call(env).tap do |status, headers, body| set_hsts_header! headers - flag_cookies_as_secure! headers if @secure_cookies + flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request) end else return redirect_to_https request unless @exclude.call(request) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 23492e14eb..acd999444a 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -16,7 +16,7 @@ module ActionDispatch # does not exist, a 404 "File not Found" response will be returned. class FileHandler def initialize(root, index: "index", headers: {}) - @root = root.chomp("/") + @root = root.chomp("/").b @file_server = ::Rack::File.new(@root, headers) @index = index end @@ -35,7 +35,7 @@ module ActionDispatch paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"] if match = paths.detect { |p| - path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8)) + path = File.join(@root, p.b) begin File.file?(path) && File.readable?(path) rescue SystemCallError @@ -43,7 +43,7 @@ module ActionDispatch end } - return ::Rack::Utils.escape_path(match) + return ::Rack::Utils.escape_path(match).b end end diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 000847e193..bc5e0670e0 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -93,6 +93,14 @@ module ActionDispatch @delegate[key.to_s] end + # Returns the nested value specified by the sequence of keys, returning + # +nil+ if any intermediate step is +nil+. + def dig(*keys) + load_for_read! + keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key } + @delegate.dig(*keys) + end + # Returns true if the session has the given key or false. def has_key?(key) load_for_read! diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 776058d98e..5cde677051 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -243,9 +243,9 @@ module ActionDispatch # # rails routes # - # Target specific controllers by prefixing the command with <tt>-c</tt> option. Use - # <tt>--expanded</tt> to turn on the expanded table formatting mode. - # + # Target a specific controller with <tt>-c</tt>, or grep routes + # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt> + # which displays routes vertically. module Routing extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb index 24dced1efd..28bb20d688 100644 --- a/actionpack/lib/action_dispatch/routing/endpoint.rb +++ b/actionpack/lib/action_dispatch/routing/endpoint.rb @@ -3,12 +3,15 @@ module ActionDispatch module Routing class Endpoint # :nodoc: - def dispatcher?; false; end - def redirect?; false; end - def engine?; rack_app.respond_to?(:routes); end - def matches?(req); true; end - def app; self; end - def rack_app; app; end + def dispatcher?; false; end + def redirect?; false; end + def matches?(req); true; end + def app; self; end + def rack_app; app; end + + def engine? + rack_app.is_a?(Class) && rack_app < Rails::Engine + end end end end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index de200fada0..bae50f6a43 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "delegate" +require "io/console/size" module ActionDispatch module Routing @@ -60,11 +61,11 @@ module ActionDispatch @routes = routes end - def format(formatter, filter = nil) + def format(formatter, filter = {}) routes_to_display = filter_routes(normalize_filter(filter)) routes = collect_routes(routes_to_display) if routes.none? - formatter.no_routes(collect_routes(@routes)) + formatter.no_routes(collect_routes(@routes), filter) return formatter.result end @@ -80,12 +81,12 @@ module ActionDispatch end private - def normalize_filter(filter) - if filter.is_a?(Hash) && filter[:controller] + if filter[:controller] { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } - elsif filter - { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + elsif filter[:grep] + { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/, + verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ } end end @@ -126,7 +127,7 @@ module ActionDispatch end module ConsoleFormatter - class Sheet + class Base def initialize @buffer = [] end @@ -136,30 +137,44 @@ module ActionDispatch end def section_title(title) - @buffer << "\n#{title}:" end def section(routes) - @buffer << draw_section(routes) end def header(routes) - @buffer << draw_header(routes) end - def no_routes(routes) + def no_routes(routes, filter) @buffer << - if routes.none? - <<~MESSAGE - You don't have any routes defined! + if routes.none? + <<~MESSAGE + You don't have any routes defined! + + Please add some routes in config/routes.rb. + MESSAGE + elsif filter.key?(:controller) + "No routes were found for this controller." + elsif filter.key?(:grep) + "No routes were found for this grep pattern." + end - Please add some routes in config/routes.rb. - MESSAGE - else - "No routes were found for this controller" - end @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." end + end + + class Sheet < Base + def section_title(title) + @buffer << "\n#{title}:" + end + + def section(routes) + @buffer << draw_section(routes) + end + + def header(routes) + @buffer << draw_header(routes) + end private @@ -185,54 +200,36 @@ module ActionDispatch end end - class Expanded - def initialize - @buffer = [] - end - - def result - @buffer.join("") - end - + class Expanded < Base def section_title(title) - @buffer << "\n#{"[ #{title} ]"}\n" + @buffer << "\n#{"[ #{title} ]"}" end def section(routes) @buffer << draw_expanded_section(routes) end - def header(routes) - @buffer - end - - def no_routes(routes) - @buffer << - if routes.none? - <<~MESSAGE - You don't have any routes defined! - - Please add some routes in config/routes.rb.\n - MESSAGE - else - "No routes were found for this controller\n" - end - @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." - end - private def draw_expanded_section(routes) routes.map.each_with_index do |r, i| - <<~MESSAGE - --[ Route #{i + 1} ]#{'-' * 60} - Prefix | #{r[:name]} - Verb | #{r[:verb]} - URI | #{r[:path]} - Controller#Action | #{r[:reqs]} + <<~MESSAGE.chomp + #{route_header(index: i + 1)} + Prefix | #{r[:name]} + Verb | #{r[:verb]} + URI | #{r[:path]} + Controller#Action | #{r[:reqs]} MESSAGE end end + + def route_header(index:) + console_width = IO.console_size.second + header_prefix = "--[ Route #{index} ]" + dash_remainder = [console_width - header_prefix.size, 0].max + + "#{header_prefix}#{'-' * dash_remainder}" + end end end @@ -264,7 +261,7 @@ module ActionDispatch <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>. </li> </ul> - MESSAGE + MESSAGE end def result diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f3970d5445..d9dd24935b 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -664,6 +664,7 @@ module ActionDispatch def define_generate_prefix(app, name) _route = @set.named_routes.get name _routes = @set + _url_helpers = @set.url_helpers script_namer = ->(options) do prefix_options = options.slice(*_route.segment_keys) @@ -675,7 +676,7 @@ module ActionDispatch # We must actually delete prefix segment keys to avoid passing them to next url_for. _route.segment_keys.each { |k| options.delete(k) } - _routes.url_helpers.send("#{name}_path", prefix_options) + _url_helpers.send("#{name}_path", prefix_options) end app.routes.define_mounted_helper(name, script_namer) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 6da869c0c2..e17ccaf986 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -120,8 +120,7 @@ module ActionDispatch opts end - # Returns the path component of a URL for the given record. It uses - # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>. + # Returns the path component of a URL for the given record. def polymorphic_path(record_or_hash_or_array, options = {}) if Hash === record_or_hash_or_array options = record_or_hash_or_array.merge(options) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index fa345dccdf..1a31c7dbb8 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -191,7 +191,25 @@ module ActionDispatch end end - def route_for(name, *args) # :nodoc: + # Allows calling direct or regular named route. + # + # resources :buckets + # + # direct :recordable do |recording| + # route_for(:bucket, recording.bucket) + # end + # + # direct :threadable do |threadable| + # route_for(:recordable, threadable.parent) + # end + # + # This maintains the context of the original caller on + # whether to return a path or full URL, e.g: + # + # threadable_path(threadable) # => "/buckets/1" + # threadable_url(threadable) # => "http://example.com/buckets/1" + # + def route_for(name, *args) public_send(:"#{name}_url", *args) end diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index f85f816bb9..c74c0ccced 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -gem "capybara", "~> 2.15" +gem "capybara", ">= 2.15" require "capybara/dsl" require "capybara/minitest" diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb index 10e6888ab3..1b0bce6b9e 100644 --- a/actionpack/lib/action_dispatch/system_testing/browser.rb +++ b/actionpack/lib/action_dispatch/system_testing/browser.rb @@ -33,7 +33,7 @@ module ActionDispatch def headless_chrome_browser_options options = Selenium::WebDriver::Chrome::Options.new options.args << "--headless" - options.args << "--disable-gpu" + options.args << "--disable-gpu" if Gem.win_platform? options end diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index ffa85f4e14..e47d5020f4 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -19,6 +19,7 @@ module ActionDispatch def after_teardown take_failed_screenshot Capybara.reset_sessions! + ensure super end end diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index fdc09bd951..4512ea27b3 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -154,7 +154,7 @@ module AbstractController test "when :except is specified, an after action is not triggered on that action" do @controller.process(:index) - assert !@controller.instance_variable_defined?("@authenticated") + assert_not @controller.instance_variable_defined?("@authenticated") end end @@ -198,7 +198,7 @@ module AbstractController test "when :except is specified with an array, an after action is not triggered on that action" do @controller.process(:index) - assert !@controller.instance_variable_defined?("@authenticated") + assert_not @controller.instance_variable_defined?("@authenticated") end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 504c77b8ef..552b6cbf47 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -290,13 +290,13 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_template_objects_exist process :assign_this - assert !@controller.instance_variable_defined?(:"@hi") + assert_not @controller.instance_variable_defined?(:"@hi") assert @controller.instance_variable_get(:"@howdy") end def test_template_objects_missing process :nothing - assert !@controller.instance_variable_defined?(:@howdy) + assert_not @controller.instance_variable_defined?(:@howdy) end def test_empty_flash @@ -366,7 +366,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase process :redirect_external assert_predicate @response, :redirect? assert_match(/rubyonrails/, @response.redirect_url) - assert !/perloffrails/.match(@response.redirect_url) + assert_not /perloffrails/.match(@response.redirect_url) end def test_redirection diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb index 07459c3753..8191578eb0 100644 --- a/actionpack/test/controller/api/force_ssl_test.rb +++ b/actionpack/test/controller/api/force_ssl_test.rb @@ -3,7 +3,9 @@ require "abstract_unit" class ForceSSLApiController < ActionController::API - force_ssl + ActiveSupport::Deprecation.silence do + force_ssl + end def one; end def two diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 8b596083d5..6fe036dd15 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -94,14 +94,14 @@ class FragmentCachingTest < ActionController::TestCase def test_fragment_exist_with_caching_enabled @store.write("views/name", "value") assert @controller.fragment_exist?("name") - assert !@controller.fragment_exist?("other_name") + assert_not @controller.fragment_exist?("other_name") end def test_fragment_exist_with_caching_disabled @controller.perform_caching = false @store.write("views/name", "value") - assert !@controller.fragment_exist?("name") - assert !@controller.fragment_exist?("other_name") + assert_not @controller.fragment_exist?("name") + assert_not @controller.fragment_exist?("other_name") end def test_write_fragment_with_caching_enabled @@ -144,7 +144,7 @@ class FragmentCachingTest < ActionController::TestCase buffer = "generated till now -> ".html_safe buffer << view_context.send(:fragment_for, "expensive") { fragment_computed = true } - assert !fragment_computed + assert_not fragment_computed assert_equal "generated till now -> fragment content", buffer end @@ -173,6 +173,9 @@ class FunctionalCachingController < CachingController end end + def xml_fragment_cached_with_html_partial + end + def formatted_fragment_cached respond_to do |format| format.html @@ -308,6 +311,11 @@ CACHED @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment") end + def test_fragment_caching_with_html_partials_in_xml + get :xml_fragment_cached_with_html_partial, format: "*/*" + assert_response :success + end + private def template_digest(name) ActionView::Digestor.digest(name: name, finder: @controller.lookup_context) diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 2b16a555bb..425a6e25cc 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -787,7 +787,7 @@ class FilterTest < ActionController::TestCase assert_equal %w( ensure_login find_user ), @controller.instance_variable_get(:@ran_filter) test_process(ConditionalSkippingController, "login") - assert !@controller.instance_variable_defined?("@ran_after_action") + assert_not @controller.instance_variable_defined?("@ran_after_action") test_process(ConditionalSkippingController, "change_password") assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_action") end diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb index 6c3ac26de1..e3ec5bb7fc 100644 --- a/actionpack/test/controller/flash_hash_test.rb +++ b/actionpack/test/controller/flash_hash_test.rb @@ -44,7 +44,7 @@ module ActionDispatch @hash["foo"] = "bar" @hash.delete "foo" - assert !@hash.key?("foo") + assert_not @hash.key?("foo") assert_nil @hash["foo"] end @@ -53,7 +53,7 @@ module ActionDispatch assert_equal({ "foo" => "bar" }, @hash.to_hash) @hash.to_hash["zomg"] = "aaron" - assert !@hash.key?("zomg") + assert_not @hash.key?("zomg") assert_equal({ "foo" => "bar" }, @hash.to_hash) end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 84ac1fda3c..7f59f6acaf 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -13,19 +13,23 @@ class ForceSSLController < ActionController::Base end class ForceSSLControllerLevel < ForceSSLController - force_ssl + ActiveSupport::Deprecation.silence do + force_ssl + end end class ForceSSLCustomOptions < ForceSSLController - force_ssl host: "secure.example.com", only: :redirect_host - force_ssl port: 8443, only: :redirect_port - force_ssl subdomain: "secure", only: :redirect_subdomain - force_ssl domain: "secure.com", only: :redirect_domain - force_ssl path: "/foo", only: :redirect_path - force_ssl status: :found, only: :redirect_status - force_ssl flash: { message: "Foo, Bar!" }, only: :redirect_flash - force_ssl alert: "Foo, Bar!", only: :redirect_alert - force_ssl notice: "Foo, Bar!", only: :redirect_notice + ActiveSupport::Deprecation.silence do + force_ssl host: "secure.example.com", only: :redirect_host + force_ssl port: 8443, only: :redirect_port + force_ssl subdomain: "secure", only: :redirect_subdomain + force_ssl domain: "secure.com", only: :redirect_domain + force_ssl path: "/foo", only: :redirect_path + force_ssl status: :found, only: :redirect_status + force_ssl flash: { message: "Foo, Bar!" }, only: :redirect_flash + force_ssl alert: "Foo, Bar!", only: :redirect_alert + force_ssl notice: "Foo, Bar!", only: :redirect_notice + end def force_ssl_action render plain: action_name @@ -55,15 +59,21 @@ class ForceSSLCustomOptions < ForceSSLController end class ForceSSLOnlyAction < ForceSSLController - force_ssl only: :cheeseburger + ActiveSupport::Deprecation.silence do + force_ssl only: :cheeseburger + end end class ForceSSLExceptAction < ForceSSLController - force_ssl except: :banana + ActiveSupport::Deprecation.silence do + force_ssl except: :banana + end end class ForceSSLIfCondition < ForceSSLController - force_ssl if: :use_force_ssl? + ActiveSupport::Deprecation.silence do + force_ssl if: :use_force_ssl? + end def use_force_ssl? action_name == "cheeseburger" @@ -71,7 +81,9 @@ class ForceSSLIfCondition < ForceSSLController end class ForceSSLFlash < ForceSSLController - force_ssl except: [:banana, :set_flash, :use_flash] + ActiveSupport::Deprecation.silence do + force_ssl except: [:banana, :set_flash, :use_flash] + end def set_flash flash["that"] = "hello" diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 560157dc61..3f211cd60d 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -202,7 +202,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "validate_digest_response should fail with nil returning password_procedure" do @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: nil, password: nil) - assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret") { nil } + assert_not ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret") { nil } end test "authentication request with request-uri ending in '/'" do diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index a685f5868e..9cdf04b886 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -135,7 +135,7 @@ class IntegrationTestTest < ActiveSupport::TestCase session1 = @test.open_session { |sess| } session2 = @test.open_session # implicit session - assert !session1.equal?(session2) + assert_not session1.equal?(session2) end # RSpec mixes Matchers (which has a #method_missing) into @@ -345,7 +345,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest follow_redirect! assert_response :ok - refute_same previous_html_document, html_document + assert_not_same previous_html_document, html_document end end @@ -375,7 +375,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest a = open_session b = open_session - refute_same(a.integration_session, b.integration_session) + assert_not_same(a.integration_session, b.integration_session) end def test_get_with_query_string diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 07a897a103..674b2c6266 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -284,4 +284,12 @@ class ParametersAccessorsTest < ActiveSupport::TestCase value.is_a?(ActionController::Parameters) end end + + test "mutating #dig return value mutates underlying parameters" do + @params.dig(:person, :name)[:first] = "Bill" + assert_equal "Bill", @params.dig(:person, :name, :first) + + @params.dig(:person, :addresses)[0] = { city: "Boston", state: "Massachusetts" } + assert_equal "Boston", @params.dig(:person, :addresses, 0, :city) + end end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 295f3a03ef..34b9ac0ab8 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -136,7 +136,7 @@ class ParametersPermitTest < ActiveSupport::TestCase test "key: it is not assigned if not present in params" do params = ActionController::Parameters.new(name: "Joe") permitted = params.permit(:id) - assert !permitted.has_key?(:id) + assert_not permitted.has_key?(:id) end test "key to empty array: empty arrays pass" do @@ -309,7 +309,7 @@ class ParametersPermitTest < ActiveSupport::TestCase merged_params = @params.reverse_merge(default_params) assert_equal "1234", merged_params[:id] - refute_predicate merged_params[:person], :empty? + assert_not_predicate merged_params[:person], :empty? end test "#with_defaults is an alias of reverse_merge" do @@ -317,11 +317,11 @@ class ParametersPermitTest < ActiveSupport::TestCase merged_params = @params.with_defaults(default_params) assert_equal "1234", merged_params[:id] - refute_predicate merged_params[:person], :empty? + assert_not_predicate merged_params[:person], :empty? end test "not permitted is sticky beyond reverse_merge" do - refute_predicate @params.reverse_merge(a: "b"), :permitted? + assert_not_predicate @params.reverse_merge(a: "b"), :permitted? end test "permitted is sticky beyond reverse_merge" do @@ -334,7 +334,7 @@ class ParametersPermitTest < ActiveSupport::TestCase @params.reverse_merge!(default_params) assert_equal "1234", @params[:id] - refute_predicate @params[:person], :empty? + assert_not_predicate @params[:person], :empty? end test "#with_defaults! is an alias of reverse_merge!" do @@ -342,7 +342,7 @@ class ParametersPermitTest < ActiveSupport::TestCase @params.with_defaults!(default_params) assert_equal "1234", @params[:id] - refute_predicate @params[:person], :empty? + assert_not_predicate @params[:person], :empty? end test "modifying the parameters" do @@ -353,12 +353,15 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "Jonas", @params[:person][:family][:brother] end - test "permit is recursive" do + test "permit! is recursive" do + @params[:nested_array] = [[{ x: 2, y: 3 }, { x: 21, y: 42 }]] @params.permit! assert_predicate @params, :permitted? assert_predicate @params[:person], :permitted? assert_predicate @params[:person][:name], :permitted? assert_predicate @params[:person][:addresses][0], :permitted? + assert_predicate @params[:nested_array][0][0], :permitted? + assert_predicate @params[:nested_array][0][1], :permitted? end test "permitted takes a default value when Parameters.permit_all_parameters is set" do diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 7d4850294d..d1122abba6 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -740,6 +740,14 @@ XML assert_equal "application/json", @response.body end + def test_request_format_kwarg_doesnt_mutate_params + params = { foo: "bar" }.freeze + + assert_nothing_raised do + get :test_format, format: "json", params: params + end + end + def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set cookies["foo"] = "bar" get :no_op diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb index b88f90190a..c4c7f53903 100644 --- a/actionpack/test/dispatch/content_security_policy_test.rb +++ b/actionpack/test/dispatch/content_security_policy_test.rb @@ -51,6 +51,12 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase @policy.script_src :strict_dynamic assert_equal "script-src 'strict-dynamic'", @policy.build + @policy.script_src :ws + assert_equal "script-src ws:", @policy.build + + @policy.script_src :wss + assert_equal "script-src wss:", @policy.build + @policy.script_src :none, :report_sample assert_equal "script-src 'none' 'report-sample'", @policy.build end @@ -194,7 +200,7 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase end def test_dynamic_directives - request = Struct.new(:host).new("www.example.com") + request = ActionDispatch::Request.new("HTTP_HOST" => "www.example.com") controller = Struct.new(:request).new(request) @policy.script_src -> { request.host } @@ -203,7 +209,9 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase def test_mixed_static_and_dynamic_directives @policy.script_src :self, -> { "foo.com" }, "bar.com" - assert_equal "script-src 'self' foo.com bar.com", @policy.build(Object.new) + request = ActionDispatch::Request.new({}) + controller = Struct.new(:request).new(request) + assert_equal "script-src 'self' foo.com bar.com", @policy.build(controller) end def test_invalid_directive_source @@ -235,6 +243,73 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase end end +class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest + class PolicyController < ActionController::Base + def index + head :ok + end + end + + ROUTES = ActionDispatch::Routing::RouteSet.new + ROUTES.draw do + scope module: "default_content_security_policy_integration_test" do + get "/", to: "policy#index" + end + end + + POLICY = ActionDispatch::ContentSecurityPolicy.new do |p| + p.default_src :self + p.script_src :https + end + + class PolicyConfigMiddleware + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.content_security_policy"] = POLICY + env["action_dispatch.content_security_policy_nonce_generator"] = proc { "iyhD0Yc0W+c=" } + env["action_dispatch.content_security_policy_report_only"] = false + env["action_dispatch.show_exceptions"] = false + + @app.call(env) + end + end + + APP = build_app(ROUTES) do |middleware| + middleware.use PolicyConfigMiddleware + middleware.use ActionDispatch::ContentSecurityPolicy::Middleware + end + + def app + APP + end + + def test_adds_nonce_to_script_src_content_security_policy_only_once + get "/" + get "/" + assert_policy "default-src 'self'; script-src https: 'nonce-iyhD0Yc0W+c='" + end + + private + + def assert_policy(expected, report_only: false) + assert_response :success + + if report_only + expected_header = "Content-Security-Policy-Report-Only" + unexpected_header = "Content-Security-Policy" + else + expected_header = "Content-Security-Policy" + unexpected_header = "Content-Security-Policy-Report-Only" + end + + assert_nil response.headers[unexpected_header] + assert_equal expected, response.headers[expected_header] + end +end + class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest class PolicyController < ActionController::Base content_security_policy only: :inline do |p| @@ -258,6 +333,8 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest p.script_src :self end + content_security_policy(false, only: :no_policy) + content_security_policy_report_only only: :report_only def index @@ -280,6 +357,10 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest head :ok end + def no_policy + head :ok + end + private def condition? params[:condition] == "true" @@ -294,6 +375,7 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest get "/conditional", to: "policy#conditional" get "/report-only", to: "policy#report_only" get "/script-src", to: "policy#script_src" + get "/no-policy", to: "policy#no_policy" end end @@ -353,19 +435,14 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest assert_policy "script-src 'self' 'nonce-iyhD0Yc0W+c='" end - private - - def env_config - Rails.application.env_config - end + def test_generates_no_content_security_policy + get "/no-policy" - def content_security_policy - env_config["action_dispatch.content_security_policy"] - end + assert_nil response.headers["Content-Security-Policy"] + assert_nil response.headers["Content-Security-Policy-Report-Only"] + end - def content_security_policy=(policy) - env_config["action_dispatch.content_security_policy"] = policy - end + private def assert_policy(expected, report_only: false) assert_response :success @@ -382,3 +459,61 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest assert_equal expected, response.headers[expected_header] end end + +class DisabledContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest + class PolicyController < ActionController::Base + content_security_policy only: :inline do |p| + p.default_src "https://example.com" + end + + def index + head :ok + end + + def inline + head :ok + end + end + + ROUTES = ActionDispatch::Routing::RouteSet.new + ROUTES.draw do + scope module: "disabled_content_security_policy_integration_test" do + get "/", to: "policy#index" + get "/inline", to: "policy#inline" + end + end + + class PolicyConfigMiddleware + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.content_security_policy"] = nil + env["action_dispatch.content_security_policy_nonce_generator"] = nil + env["action_dispatch.content_security_policy_report_only"] = false + env["action_dispatch.show_exceptions"] = false + + @app.call(env) + end + end + + APP = build_app(ROUTES) do |middleware| + middleware.use PolicyConfigMiddleware + middleware.use ActionDispatch::ContentSecurityPolicy::Middleware + end + + def app + APP + end + + def test_generates_no_content_security_policy_by_default + get "/" + assert_nil response.headers["Content-Security-Policy"] + end + + def test_generates_content_security_policy_header_when_globally_disabled + get "/inline" + assert_equal "default-src https://example.com", response.headers["Content-Security-Policy"] + end +end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 94cff10fe4..aba778fad6 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -65,8 +65,8 @@ class CookieJarTest < ActiveSupport::TestCase end def test_key_methods - assert !request.cookie_jar.key?(:foo) - assert !request.cookie_jar.has_key?("foo") + assert_not request.cookie_jar.key?(:foo) + assert_not request.cookie_jar.has_key?("foo") request.cookie_jar[:foo] = :bar assert request.cookie_jar.key?(:foo) diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb index 8eb6450385..5b8be39b6d 100644 --- a/actionpack/test/dispatch/executor_test.rb +++ b/actionpack/test/dispatch/executor_test.rb @@ -81,7 +81,7 @@ class ExecutorTest < ActiveSupport::TestCase running = false body.close - assert !running + assert_not running end def test_complete_callbacks_are_called_on_close @@ -89,7 +89,7 @@ class ExecutorTest < ActiveSupport::TestCase executor.to_complete { completed = true } body = call_and_return_body - assert !completed + assert_not completed body.close assert completed @@ -116,7 +116,7 @@ class ExecutorTest < ActiveSupport::TestCase call_and_return_body.close assert result - assert !defined?(@in_shared_context) # it's not in the test itself + assert_not defined?(@in_shared_context) # it's not in the test itself end private diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 6167ea46df..fa264417e1 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -180,8 +180,8 @@ class MimeTypeTest < ActiveSupport::TestCase assert Mime[:js] =~ "text/javascript" assert Mime[:js] =~ "application/javascript" assert Mime[:js] !~ "text/html" - assert !(Mime[:js] !~ "text/javascript") - assert !(Mime[:js] !~ "application/javascript") + assert_not (Mime[:js] !~ "text/javascript") + assert_not (Mime[:js] !~ "application/javascript") assert Mime[:html] =~ "application/xhtml+xml" end end diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index e529229fae..edc4cd62a3 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -115,7 +115,7 @@ class ReloaderTest < ActiveSupport::TestCase reloader.to_complete { completed = true } body = call_and_return_body - assert !completed + assert_not completed body.close assert completed @@ -129,7 +129,7 @@ class ReloaderTest < ActiveSupport::TestCase prepared = false body.close - assert !prepared + assert_not prepared end def test_complete_callbacks_are_called_on_exceptions diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index bf5a74e694..74da2fe7d3 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -118,6 +118,18 @@ module ActionDispatch end end + def test_dig + session = Session.create(store, req, {}) + session["one"] = { "two" => "3" } + + assert_equal "3", session.dig("one", "two") + assert_equal "3", session.dig(:one, "two") + + assert_nil session.dig("three", "two") + assert_nil session.dig("one", "three") + assert_nil session.dig("one", :two) + end + private def store Class.new { diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 4c8d528507..6d87314e97 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -191,7 +191,7 @@ class ResponseTest < ActiveSupport::TestCase test "does not include Status header" do @response.status = "200 OK" _, headers, _ = @response.to_a - assert !headers.has_key?("Status") + assert_not headers.has_key?("Status") end test "response code" do diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 127212b228..9150d5010b 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -3,6 +3,7 @@ require "abstract_unit" require "rails/engine" require "action_dispatch/routing/inspector" +require "io/console/size" class MountedRackApp def self.call(env) @@ -15,16 +16,10 @@ end module ActionDispatch module Routing class RoutesInspectorTest < ActiveSupport::TestCase - def setup + setup do @set = ActionDispatch::Routing::RouteSet.new end - def draw(options = nil, formater = ActionDispatch::Routing::ConsoleFormatter::Sheet.new, &block) - @set.draw(&block) - inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) - inspector.format(formater, options).split("\n") - end - def test_displaying_routes_for_engines engine = Class.new(Rails::Engine) do def self.inspect @@ -305,7 +300,7 @@ module ActionDispatch end def test_routes_can_be_filtered - output = draw("posts") do + output = draw(grep: "posts") do resources :articles resources :posts end @@ -322,6 +317,9 @@ module ActionDispatch end def test_routes_when_expanded + previous_console_winsize = IO.console.winsize + IO.console.winsize = [0, 23] + engine = Class.new(Rails::Engine) do def self.inspect "Blog::Engine" @@ -331,50 +329,51 @@ module ActionDispatch get "/cart", to: "cart#show" end - output = draw(nil, ActionDispatch::Routing::ConsoleFormatter::Expanded.new) do + output = draw(formatter: ActionDispatch::Routing::ConsoleFormatter::Expanded.new) do get "/custom/assets", to: "custom_assets#show" get "/custom/furnitures", to: "custom_furnitures#show" mount engine => "/blog", :as => "blog" end - assert_equal ["--[ Route 1 ]------------------------------------------------------------", + assert_equal ["--[ Route 1 ]----------", "Prefix | custom_assets", "Verb | GET", "URI | /custom/assets(.:format)", "Controller#Action | custom_assets#show", - "--[ Route 2 ]------------------------------------------------------------", + "--[ Route 2 ]----------", "Prefix | custom_furnitures", "Verb | GET", "URI | /custom/furnitures(.:format)", "Controller#Action | custom_furnitures#show", - "--[ Route 3 ]------------------------------------------------------------", + "--[ Route 3 ]----------", "Prefix | blog", "Verb | ", "URI | /blog", "Controller#Action | Blog::Engine", "", "[ Routes for Blog::Engine ]", - "--[ Route 1 ]------------------------------------------------------------", + "--[ Route 1 ]----------", "Prefix | cart", "Verb | GET", "URI | /cart(.:format)", "Controller#Action | cart#show"], output + ensure + IO.console.winsize = previous_console_winsize end - def test_no_routes_matched_filter_when_expanded - output = draw("rails/dummy", ActionDispatch::Routing::ConsoleFormatter::Expanded.new) do + output = draw(grep: "rails/dummy", formatter: ActionDispatch::Routing::ConsoleFormatter::Expanded.new) do get "photos/:id" => "photos#show", :id => /[A-Z]\d{5}/ end assert_equal [ - "No routes were found for this controller", + "No routes were found for this grep pattern.", "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." ], output end def test_not_routes_when_expanded - output = draw("rails/dummy", ActionDispatch::Routing::ConsoleFormatter::Expanded.new) {} + output = draw(grep: "rails/dummy", formatter: ActionDispatch::Routing::ConsoleFormatter::Expanded.new) {} assert_equal [ "You don't have any routes defined!", @@ -386,7 +385,7 @@ module ActionDispatch end def test_routes_can_be_filtered_with_namespaced_controllers - output = draw("admin/posts") do + output = draw(grep: "admin/posts") do resources :articles namespace :admin do resources :posts @@ -434,24 +433,24 @@ module ActionDispatch end assert_equal [ - "No routes were found for this controller", + "No routes were found for this controller.", "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." ], output end def test_no_routes_matched_filter - output = draw("rails/dummy") do + output = draw(grep: "rails/dummy") do get "photos/:id" => "photos#show", :id => /[A-Z]\d{5}/ end assert_equal [ - "No routes were found for this controller", + "No routes were found for this grep pattern.", "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." ], output end def test_no_routes_were_defined - output = draw("Rails::DummyController") {} + output = draw(grep: "Rails::DummyController") {} assert_equal [ "You don't have any routes defined!", @@ -484,6 +483,13 @@ module ActionDispatch "custom_assets GET /custom/assets(.:format) custom_assets#show", ], output end + + private + def draw(formatter: ActionDispatch::Routing::ConsoleFormatter::Sheet.new, **options, &block) + @set.draw(&block) + inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) + inspector.format(formatter, options).split("\n") + end end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index fe314e26b1..dd6adcbfd1 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3166,7 +3166,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - assert !respond_to?(:routes_no_collision_path) + assert_not respond_to?(:routes_no_collision_path) end def test_controller_name_with_leading_slash_raise_error diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 90f2ee46ea..baf46e7c7e 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -208,6 +208,14 @@ class SecureCookiesTest < SSLTest assert_cookies(*DEFAULT.split("\n")) end + def test_cookies_as_not_secure_with_exclude + excluding = { exclude: -> request { request.domain =~ /example/ } } + get headers: { "Set-Cookie" => DEFAULT }, ssl_options: { redirect: excluding } + + assert_cookies(*DEFAULT.split("\n")) + assert_response :ok + end + def test_no_cookies get assert_nil response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 0bdff68692..6b69cd9999 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -71,7 +71,16 @@ module StaticTests end def test_served_static_file_with_non_english_filename - assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}") + assert_html "means hello in Japanese\n", get("/foo/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF.html") + end + + def test_served_gzipped_static_file_with_non_english_filename + response = get("/foo/%E3%81%95%E3%82%88%E3%81%86%E3%81%AA%E3%82%89.html", "HTTP_ACCEPT_ENCODING" => "gzip") + + assert_gzip "/foo/さようなら.html", response + assert_equal "text/html", response.headers["Content-Type"] + assert_equal "Accept-Encoding", response.headers["Vary"] + assert_equal "gzip", response.headers["Content-Encoding"] end def test_serves_static_file_with_exclamation_mark_in_filename diff --git a/actionpack/test/dispatch/system_testing/server_test.rb b/actionpack/test/dispatch/system_testing/server_test.rb index 95e411faf4..740e90a4da 100644 --- a/actionpack/test/dispatch/system_testing/server_test.rb +++ b/actionpack/test/dispatch/system_testing/server_test.rb @@ -17,7 +17,7 @@ class ServerTest < ActiveSupport::TestCase test "server is changed from `default` to `puma`" do Capybara.server = :default ActionDispatch::SystemTesting::Server.new.run - refute_equal Capybara.server, Capybara.servers[:default] + assert_not_equal Capybara.server, Capybara.servers[:default] end test "server is not changed to `puma` when is different than default" do diff --git a/actionpack/test/fixtures/functional_caching/_formatted_partial.html.erb b/actionpack/test/fixtures/functional_caching/_formatted_partial.html.erb new file mode 100644 index 0000000000..aad73c0d6b --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/_formatted_partial.html.erb @@ -0,0 +1 @@ +<p>Hello!</p> diff --git a/actionpack/test/fixtures/functional_caching/xml_fragment_cached_with_html_partial.xml.builder b/actionpack/test/fixtures/functional_caching/xml_fragment_cached_with_html_partial.xml.builder new file mode 100644 index 0000000000..2bdda3af18 --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/xml_fragment_cached_with_html_partial.xml.builder @@ -0,0 +1,5 @@ +cache do + xml.title "Hello!" +end + +xml.body cdata_section(render("formatted_partial")) diff --git a/actionpack/test/fixtures/public/foo/さようなら.html b/actionpack/test/fixtures/public/foo/さようなら.html new file mode 100644 index 0000000000..627bb2469f --- /dev/null +++ b/actionpack/test/fixtures/public/foo/さようなら.html @@ -0,0 +1 @@ +means goodbye in Japanese diff --git a/actionpack/test/fixtures/public/foo/さようなら.html.gz b/actionpack/test/fixtures/public/foo/さようなら.html.gz Binary files differnew file mode 100644 index 0000000000..4f484cfe86 --- /dev/null +++ b/actionpack/test/fixtures/public/foo/さようなら.html.gz diff --git a/actionpack/test/fixtures/公共/foo/さようなら.html b/actionpack/test/fixtures/公共/foo/さようなら.html new file mode 100644 index 0000000000..627bb2469f --- /dev/null +++ b/actionpack/test/fixtures/公共/foo/さようなら.html @@ -0,0 +1 @@ +means goodbye in Japanese diff --git a/actionpack/test/fixtures/公共/foo/さようなら.html.gz b/actionpack/test/fixtures/公共/foo/さようなら.html.gz Binary files differnew file mode 100644 index 0000000000..4f484cfe86 --- /dev/null +++ b/actionpack/test/fixtures/公共/foo/さようなら.html.gz |