aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
Diffstat (limited to 'railties')
-rw-r--r--railties/lib/rails.rb1
-rw-r--r--railties/lib/rails/application.rb3
-rw-r--r--railties/lib/rails/application/configuration.rb9
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb1
-rw-r--r--railties/lib/rails/command.rb1
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/feature_policy.rb.tt11
-rw-r--r--railties/lib/rails/tasks/zeitwerk.rake95
-rw-r--r--railties/test/application/feature_policy_test.rb191
-rw-r--r--railties/test/application/middleware_test.rb1
10 files changed, 239 insertions, 76 deletions
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 1f533a8c04..440d2953c3 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -5,7 +5,6 @@ require "rails/ruby_version_check"
require "pathname"
require "active_support"
-require "active_support/dependencies/autoload"
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/array/extract_options"
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 225152c50b..cbaab6cc33 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -271,7 +271,8 @@ module Rails
"action_dispatch.content_security_policy" => config.content_security_policy,
"action_dispatch.content_security_policy_report_only" => config.content_security_policy_report_only,
"action_dispatch.content_security_policy_nonce_generator" => config.content_security_policy_nonce_generator,
- "action_dispatch.content_security_policy_nonce_directives" => config.content_security_policy_nonce_directives
+ "action_dispatch.content_security_policy_nonce_directives" => config.content_security_policy_nonce_directives,
+ "action_dispatch.feature_policy" => config.feature_policy,
)
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 5b3e96cbd4..50d43ff69e 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -69,6 +69,7 @@ module Rails
@autoloader = :classic
@disable_sandbox = false
@add_autoload_paths_to_load_path = true
+ @feature_policy = nil
end
def load_defaults(target_version)
@@ -301,6 +302,14 @@ module Rails
end
end
+ def feature_policy(&block)
+ if block_given?
+ @feature_policy = ActionDispatch::FeaturePolicy.new(&block)
+ else
+ @feature_policy
+ end
+ end
+
def autoloader=(autoloader)
case autoloader
when :classic
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 3659c0ac3a..572f51fca2 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -68,6 +68,7 @@ module Rails
unless config.api_only
middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware
+ middleware.use ::ActionDispatch::FeaturePolicy::Middleware
end
middleware.use ::Rack::Head
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
index f09aa3ae0d..7e6e968c92 100644
--- a/railties/lib/rails/command.rb
+++ b/railties/lib/rails/command.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support"
-require "active_support/dependencies/autoload"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/object/blank"
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 709863275d..436315ce1e 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -6,8 +6,6 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.inc
require "thor/group"
require "rails/command"
-require "active_support"
-require "active_support/core_ext/object/blank"
require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/deep_merge"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/feature_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/feature_policy.rb.tt
new file mode 100644
index 0000000000..a1c46695d2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/feature_policy.rb.tt
@@ -0,0 +1,11 @@
+# Define an application-wide HTTP feature policy. For further
+# information see https://developers.google.com/web/updates/2018/06/feature-policy
+#
+# Rails.application.config.feature_policy do |f|
+# f.camera :none
+# f.gyroscope :none
+# f.microphone :none
+# f.usb :none
+# f.fullscreen :self
+# f.payment :self, "https://secure.example.com"
+# end
diff --git a/railties/lib/rails/tasks/zeitwerk.rake b/railties/lib/rails/tasks/zeitwerk.rake
index e748a479a7..5421af6e8b 100644
--- a/railties/lib/rails/tasks/zeitwerk.rake
+++ b/railties/lib/rails/tasks/zeitwerk.rake
@@ -1,62 +1,14 @@
# frozen_string_literal: true
-indent = " " * 2
-
-ensure_classic_mode = ->() do
- if Rails.autoloaders.zeitwerk_enabled?
- abort <<~EOS
- Please, enable temporarily :classic mode:
-
- # config/application.rb
- config.autoloader = :classic
-
- and try again. When all is good, you can delete that line.
- EOS
+ensure_zeitwerk_mode = ->() do
+ unless Rails.autoloaders.zeitwerk_enabled?
+ abort "Please, enable :zeitwerk mode in config/application.rb and try again."
end
end
eager_load = ->() do
- Rails.configuration.eager_load_namespaces.each(&:eager_load!)
-end
-
-check_directory = ->(directory, parent, mismatches) do
- # test/mailers/previews might not exist.
- return unless File.exist?(directory)
-
- Dir.foreach(directory) do |entry|
- next if entry.start_with?(".")
- next if parent == Object && entry == "concerns"
-
- abspath = File.join(directory, entry)
-
- if File.directory?(abspath) || abspath.end_with?(".rb")
- print "."
- cname = File.basename(abspath, ".rb").camelize.to_sym
- if parent.const_defined?(cname, false)
- if File.directory?(abspath)
- check_directory[abspath, parent.const_get(cname), mismatches]
- end
- else
- mismatches << [abspath, parent, cname]
- end
- end
- end
-end
-
-report_mismatches = ->(mismatches) do
- puts
- rails_root_prefix_re = %r{\A#{Regexp.escape(Rails.root.to_path)}/}
- mismatches.each do |abspath, parent, cname|
- relpath = abspath.sub(rails_root_prefix_re, "")
- cpath = parent == Object ? cname : "#{parent.name}::#{cname}"
- puts indent + "Mismatch: Expected #{relpath} to define #{cpath}"
- end
- puts
-
- puts <<~EOS
- Please revise the reported mismatches. You can normally fix them by adding
- acronyms to config/initializers/inflections.rb or renaming the constants.
- EOS
+ puts "Hold on, I am eager loading the application."
+ Zeitwerk::Loader.eager_load_all
end
report_not_checked = ->(not_checked) do
@@ -67,47 +19,48 @@ report_not_checked = ->(not_checked) do
EOS
puts
- not_checked.each { |dir| puts indent + dir }
+ not_checked.each { |dir| puts " #{dir}" }
puts
puts <<~EOS
You may verify them manually, or add them to config.eager_load_paths
in config/application.rb and run zeitwerk:check again.
EOS
+ puts
end
-report = ->(mismatches, not_checked) do
- puts
- if mismatches.empty? && not_checked.empty?
- puts "All is good!"
- puts "Please, remember to delete `config.autoloader = :classic` from config/application.rb."
+report = ->(not_checked) do
+ if not_checked.any?
+ report_not_checked[not_checked]
+ puts "Otherwise, all is good!"
else
- report_mismatches[mismatches] if mismatches.any?
- report_not_checked[not_checked] if not_checked.any?
+ puts "All is good!"
end
end
namespace :zeitwerk do
desc "Checks project structure for Zeitwerk compatibility"
task check: :environment do
- ensure_classic_mode[]
- eager_load[]
+ ensure_zeitwerk_mode[]
+
+ begin
+ eager_load[]
+ rescue NameError => e
+ if e.message =~ /expected file .*? to define constant \S+/
+ abort $&.sub(/#{Regexp.escape(Rails.root.to_s)}./, "")
+ else
+ raise
+ end
+ end
eager_load_paths = Rails.configuration.eager_load_namespaces.map do |eln|
eln.config.eager_load_paths if eln.respond_to?(:config)
end.compact.flatten
- mismatches = []
-
- $stdout.sync = true
- eager_load_paths.each do |eager_load_path|
- check_directory[eager_load_path, Object, mismatches]
- end
-
not_checked = ActiveSupport::Dependencies.autoload_paths - eager_load_paths
not_checked.select! { |dir| Dir.exist?(dir) }
not_checked.reject! { |dir| Dir.empty?(dir) }
- report[mismatches, not_checked]
+ report[not_checked]
end
end
diff --git a/railties/test/application/feature_policy_test.rb b/railties/test/application/feature_policy_test.rb
new file mode 100644
index 0000000000..e751d782ee
--- /dev/null
+++ b/railties/test/application/feature_policy_test.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "rack/test"
+
+module ApplicationTests
+ class FeaturePolicyTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "feature policy is not enabled by default" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_nil last_response.headers["Feature-Policy"]
+ end
+
+ test "global feature policy in an initializer" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/feature_policy.rb", <<-RUBY
+ Rails.application.config.feature_policy do |p|
+ p.geolocation :none
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "geolocation 'none'"
+ end
+
+ test "override feature policy using same directive in a controller" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ feature_policy do |p|
+ p.geolocation "https://example.com"
+ end
+
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/feature_policy.rb", <<-RUBY
+ Rails.application.config.feature_policy do |p|
+ p.geolocation :none
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "geolocation https://example.com"
+ end
+
+ test "override feature policy by unsetting a directive in a controller" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ feature_policy do |p|
+ p.geolocation nil
+ end
+
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/feature_policy.rb", <<-RUBY
+ Rails.application.config.feature_policy do |p|
+ p.geolocation :none
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_equal 200, last_response.status
+ assert_nil last_response.headers["Feature-Policy"]
+ end
+
+ test "override feature policy using different directives in a controller" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ feature_policy do |p|
+ p.geolocation nil
+ p.payment "https://secure.example.com"
+ p.autoplay :none
+ end
+
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/feature_policy.rb", <<-RUBY
+ Rails.application.config.feature_policy do |p|
+ p.geolocation :none
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "payment https://secure.example.com; autoplay 'none'"
+ end
+
+ test "global feature policy added to rack app" do
+ app_file "config/initializers/feature_policy.rb", <<-RUBY
+ Rails.application.config.feature_policy do |p|
+ p.payment :none
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ app = ->(env) {
+ [200, { "Content-Type" => "text/html" }, ["<p>Hello, World!</p>"]]
+ }
+ root to: app
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "payment 'none'"
+ end
+
+ private
+ def assert_policy(expected)
+ assert_equal 200, last_response.status
+ assert_equal expected, last_response.headers["Feature-Policy"]
+ end
+ end
+end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 54c84e2e7c..e93f2f5aa4 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -46,6 +46,7 @@ module ApplicationTests
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
"ActionDispatch::ContentSecurityPolicy::Middleware",
+ "ActionDispatch::FeaturePolicy::Middleware",
"Rack::Head",
"Rack::ConditionalGet",
"Rack::ETag",