rails/railties/test/application/feature_policy_test.rb
Jacob Bednarz bf19b8774e Adds support for configuring HTTP Feature Policy (#33439)
A HTTP feature policy is Yet Another HTTP header for instructing the
browser about which features the application intends to make use of and
to lock down access to others. This is a new security mechanism that
ensures that should an application become compromised or a third party
attempts an unexpected action, the browser will override it and maintain
the intended UX.

WICG specification: https://wicg.github.io/feature-policy/

The end result is a HTTP header that looks like the following:

```
Feature-Policy: geolocation 'none'; autoplay https://example.com
```

This will prevent the browser from using geolocation and only allow
autoplay on `https://example.com`. Full feature list can be found over
in the WICG repository[1].

As of today Chrome and Safari have public support[2] for this
functionality with Firefox working on support[3] and Edge still pending
acceptance of the suggestion[4].

#### Examples

Using an initializer

```rb
# config/initializers/feature_policy.rb
Rails.application.config.feature_policy do |f|
  f.geolocation :none
  f.camera      :none
  f.payment     "https://secure.example.com"
  f.fullscreen  :self
end
```

In a controller

```rb
class SampleController < ApplicationController
  def index
    feature_policy do |f|
      f.geolocation "https://example.com"
    end
  end
end
```

Some of you might realise that the HTTP feature policy looks pretty
close to that of a Content Security Policy; and you're right. So much so
that I used the Content Security Policy DSL from #31162 as the starting
point for this change.

This change *doesn't* introduce support for defining a feature policy on
an iframe and this has been intentionally done to split the HTTP header
and the HTML element (`iframe`) support. If this is successful, I'll
look to add that on it's own.

Full documentation on HTTP feature policies can be found at
https://wicg.github.io/feature-policy/. Google have also published[5] a
great in-depth write up of this functionality.

[1]: https://github.com/WICG/feature-policy/blob/master/features.md
[2]: https://www.chromestatus.com/feature/5694225681219584
[3]: https://bugzilla.mozilla.org/show_bug.cgi?id=1390801
[4]: https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/33507907-support-feature-policy
[5]: https://developers.google.com/web/updates/2018/06/feature-policy
2019-07-10 15:33:15 -07:00

192 lines
4.6 KiB
Ruby

# 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