Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for access control headers to ActionDispatch::Static #19135

Merged
merged 1 commit into from Oct 13, 2015

Conversation

@yuki24
Copy link
Contributor

yuki24 commented Feb 28, 2015

Now ActionDispatch::Static can accept access control headers such as Access-Control-Allow-Origin. These headers will be set as well as Cache-Control header when a response is delivered. Access control headers can be configured with #config in config/application.rb or config/environments/***.rb:

config.action_dispatch.access_control_headers = {
  "Access-Control-Allow-Origin"      => 'http://rubyonrails.org',
  "Access-Control-Expose-Headers"    => 'X-Other-Custom-Header',
  "Access-Control-Allow-Headers"     => 'X-Another-Custom-Header',
  "Access-Control-Max-Age"           => '42',
  "Access-Control-Allow-Credentials" => 'true',
  "Access-Control-Allow-Methods"     => 'GET'
}

cc/ @schneems

@@ -13,10 +16,10 @@ module ActionDispatch
# located at `public/assets/application.js` if the file exists. If the file
# does not exist a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, cache_control)
def initialize(root, cache_control, access_control_headers)

This comment has been minimized.

Copy link
@yuki24

yuki24 Feb 28, 2015

Author Contributor

Although this implementation works, I'm not sure about this interface since Cache-Control is also part of the HTTP headers and the third argument seems redundant. I guess this interface could be justdefault_headers.

@@ -17,6 +17,7 @@ class Railtie < Rails::Railtie # :nodoc:
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.access_control_headers = { }

This comment has been minimized.

Copy link
@yuki24

yuki24 Feb 28, 2015

Author Contributor

access_control_headers will directly be passed into Rack::File, so the name doesn't necessarily have to be prefixed by access_control_. But if we change this name to just asset_headers or static_file_headers, I think we also should deprecate static_cache_control config in favor of it.

@robin850 robin850 added the actionpack label Mar 2, 2015
@schneems
Copy link
Member

schneems commented Mar 2, 2015

Ultimately I think it looks good. Let's change some naming so that it's a little more generic. Instead of access_control_headers let's just use headers which is technically what it is (although you can use them as access control headers).

You can reduce the initialization logic to something like this:

headers ||= {}
headers['Cache-Control'] = cache_control

I would also change config.action_dispatch.access_control_headers to config.action_dispatch.asset_headers or something more accurate. We can then explicitly add a section in the guides that shows how to set access control headers http://guides.rubyonrails.org/asset_pipeline.html

I agree we should deprecate some interfaces here and clean some things up. Unfortunately as I understand, the deadline for getting deprecations in to Rails 5 was Rails 4.2.0

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from ab1bc3f to 68e35a0 Mar 3, 2015
@yuki24
Copy link
Contributor Author

yuki24 commented Mar 3, 2015

Updated the commit as suggested. Thanks @schneems for reviewing!

@yuki24
Copy link
Contributor Author

yuki24 commented Mar 3, 2015

forgot to add a CHANGELOG entry btw. I'll add it tonight.

@file_server = ::Rack::File.new(@root, headers)

headers ||= {}
@file_server = ::Rack::File.new(@root, headers.reverse_merge('Cache-Control' => cache_control))

This comment has been minimized.

Copy link
@schneems

schneems Mar 3, 2015

Member

merge and reverse_merge are really expensive. Even though this only gets called once on initialization the code in this file is very performance sensitive and I don't want people copying existing style. Can you also factor out the merge?

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

Thanks @schneems, removed the merge call,

@@ -94,9 +98,9 @@ def gzip_file_path(path)
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
# requests will result in a file being returned.
class Static
def initialize(app, path, cache_control=nil)
def initialize(app, path, cache_control = nil, headers = {})

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

Use keyword args for newly added arguments: headers: nil

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

👍 replaced with headers: {} kwarg.

@app = app
@file_handler = FileHandler.new(path, cache_control)
@file_handler = FileHandler.new(path, cache_control, headers)

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

Since we're passing response headers to FileHandler now, we can deprecate the cache_control argument and handle it here rather than in FileHandler#initialize:

# Static
def initialize(app, path, deprecated_cache_control = :not_set, headers: nil)
  if deprecated_cache_control != :not_set
    ActiveSupport::Deprecation.warn "The `cache_control = '…'` argument is deprecated, replaced by `headers: { 'Cache-Control' => '…' }`, and will be removed in Rails 5.1."
    headers = (headers || {}).merge('Cache-Control'.freeze => deprecated_cache_control)
  end

  @app = app
  @file_handler = FileHandler.new(path, headers: headers)
end

# FileHandler
def initialize(root, headers:)
  
  @file_server = ::Rack::File.new(@root, headers)
end

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

(That'd mean deprecating config.static_cache_control as well, transitioning to a headers config setting.)

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

changed it as suggested.

@@ -17,6 +17,7 @@ class Railtie < Rails::Railtie # :nodoc:
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.asset_headers = { }

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

This sounds like headers on the asset pipeline rather than headers on static file response.

The other config scattered among config.static_cache_control and config.serve_static_files. Let's unify these as "public file server" config, reflecting that we're just serving files from the highly visible public/ dir:

config.public_file_server.enabled = true  # replace config.serve_static_files
config.public_file_server.headers = { 'Cache-Control' => '…' }  # replace cache.static_cache_control

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

added config.public_file_server namespace, but will send a separate pull request for adding config.public_file_server.enabled.

assert_equal 'true', response.headers["Access-Control-Allow-Credentials"]
assert_equal 'GET', response.headers["Access-Control-Allow-Methods"]
end

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

Calling out Access-Control headers suggests that we built a feature to add these headers in particular. Using arbitrary/meaningless headers would make it clearer that we support any response headers.

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

added Cache-Control and a custom header 👍

@@ -199,7 +219,7 @@ class StaticTest < ActiveSupport::TestCase

def setup
@root = "#{FIXTURE_LOAD_PATH}/public"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60", ACCESS_CONTROL_HEADERS)

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

These headers are not needed for all tests. Would instantiate @app = … in the specific test case instead.

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

done 👍

@@ -228,7 +248,7 @@ def test_custom_handler_called_when_file_is_outside_root
class StaticEncodingTest < StaticTest
def setup
@root = "#{FIXTURE_LOAD_PATH}/公共"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60", ACCESS_CONTROL_HEADERS)

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

Ditto.

@@ -18,7 +18,7 @@ def build_stack
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header

if config.serve_static_files
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control, config.action_dispatch.asset_headers
end

This comment has been minimized.

Copy link
@jeremy

jeremy Mar 3, 2015

Member

Will need to use config.public_file_server.* config here, and introduce deprecations either on Rails::Application::Configuration#serve_static_files= and #static_cache_control= (they're attr accessors now), or handle deprecations here, where the config is read. Probably better to get a deprecation warning when they're set rather than when they're read.

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

Added a deprecation in the setter, I'm not sure if it looks good enough, though. I'll send a separate pull request for replacing config.serve_static_files with config.public_file_server.enabled.

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from 68e35a0 to ef5c266 Mar 4, 2015
@@ -24,6 +24,9 @@ class Railtie < Rails::Railtie # :nodoc:
'X-Content-Type-Options' => 'nosniff'
}

config.public_file_server = ActiveSupport::OrderedOptions.new
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

I'm not sure if this is the right place to define public_file_server. I'm open to suggestions.

"Please use `config.public_file_server.headers['Cache-Control'] = #{value} instead.")

@static_cache_control = value
public_file_server.headers['Cache-Control'.freeze] = value

This comment has been minimized.

Copy link
@yuki24

yuki24 Mar 4, 2015

Author Contributor

This is a bit smelly, but somewhere we have to this to avoid duplicate warnings from ActionDispatch::Static.

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from ef5c266 to 57e68ad May 2, 2015
@yuki24 yuki24 mentioned this pull request May 15, 2015
@schneems
Copy link
Member

schneems commented May 21, 2015

What are we waiting on for this to go in? Anything a blocker here?

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from 57e68ad to 582cca3 Jun 10, 2015
headers = config.public_file_server.headers
headers['Cache-Control'] = config.static_cache_control if config.static_cache_control.present?

middleware.use ::ActionDispatch::Static, paths["public"].first, :not_set, config.static_index, headers: headers

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 10, 2015

Author Contributor

I needed to use :not_set explicitly due to this change: #20017. I think we should make the index arg a keyword argument so we don't have to care about the order of the args here. Alternatively, we can just merge the static_index config into the public_file_server namespace, which I actually prefer, but I'm open to suggestions.

cc/ @eliotsykes @jonatack

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from 582cca3 Jun 10, 2015
@vipulnsward
vipulnsward reviewed Jun 10, 2015
View changes
actionpack/lib/action_dispatch/middleware/static.rb Outdated
@@ -13,15 +13,13 @@ module ActionDispatch
# located at `public/assets/application.js` if the file exists. If the file
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, cache_control, index = 'index')
def initialize(root, index = 'index', headers: )

This comment has been minimized.

Copy link
@vipulnsward

vipulnsward Jun 10, 2015

Member

index can be kwarg too.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 10, 2015

Author Contributor

Absolutely. See my comment above about it: #19135 (comment)

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 11, 2015

Author Contributor

I just sent a separate pull request for this: #20520. Now we need to wait on it to be merged before merging this PR.

This comment has been minimized.

Copy link
@jonatack

jonatack Jun 11, 2015

Contributor

The comment states optional HTTP headers but the headers keyword is required. Perhaps headers: needs a default value?

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 12, 2015

Author Contributor

Headers are totally optional. I changed it to use a {} by default.

This comment has been minimized.

Copy link
@jonatack

jonatack Jun 12, 2015

Contributor

Great.

This PR will break the interface for other services/gems that hook into it, for instance the Heroku-deflator gem here but seems unavoidable for adding this feature.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 12, 2015

Author Contributor

oh, I didn't know that ActionDispatch::FileHandler is a public class. I don't think it's ok to break it.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 13, 2015

Author Contributor

I did a little investigation but I'm not sure if we should treat ActionDispatch::FileHandler as a public class. It's basically an inner class. Does it still have to be public?

@yuki24
Copy link
Contributor Author

yuki24 commented Jun 10, 2015

@schneems updated the commit. I'm a bit concerned about the arguments of the ActionDispatch::Static initializer, but everything else should be good now.

@vipulnsward
vipulnsward reviewed Jun 10, 2015
View changes
railties/lib/rails/application/configuration.rb Outdated
@@ -52,6 +52,13 @@ def initialize(*)
@x = Custom.new
end

def static_cache_control=(value)
ActiveSupport::Deprecation.warn("static_cache_control is deprecated and will be removed in Rails 5.1. " \
"Please use `config.public_file_server.headers['Cache-Control'] = #{value} instead.")

This comment has been minimized.

Copy link
@vipulnsward

vipulnsward Jun 10, 2015

Member

Would be good to have this as-

config.public_file_server.headers= {'Cache-Control' => #{value}}

This would suggest the options being passed to headers have been expanded.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 11, 2015

Author Contributor

updated.

@vipulnsward
vipulnsward reviewed Jun 10, 2015
View changes
railties/lib/rails/application/default_middleware_stack.rb Outdated
@@ -18,7 +18,10 @@ def build_stack
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header

if config.serve_static_files
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control, config.static_index
headers = config.public_file_server.headers
headers['Cache-Control'] = config.static_cache_control if config.static_cache_control.present?

This comment has been minimized.

Copy link
@vipulnsward

vipulnsward Jun 10, 2015

Member

present? is redundant here.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 10, 2015

Author Contributor

👍 I'll remove it.

This comment has been minimized.

Copy link
@yuki24

yuki24 Jun 11, 2015

Author Contributor

updated.

@vipulnsward
Copy link
Member

vipulnsward commented Jun 10, 2015

@yuki24 Thanks a ton for this, I was looking to implement something similar due to this - https://twitter.com/vipulnsward/status/603866969201373184

@yuki24 yuki24 force-pushed the yuki24:access-control-support branch 5 times, most recently from f235974 to 3223d80 Jun 11, 2015
Now ActionDispatch::Static can accept HTTP headers so that developers
will have control of returning arbitrary headers like
'Access-Control-Allow-Origin' when a response is delivered. They can
be configured through `#config.public_file_server.headers`:

  config.public_file_server.headers = {
    "Cache-Control"               => "public, max-age=60",
    "Access-Control-Allow-Origin" => "http://rubyonrails.org"
  }

Also deprecate `config.static_cache_control` in favor of
`config.public_file_server.headers`.
@yuki24 yuki24 force-pushed the yuki24:access-control-support branch from 3223d80 to 5226058 Jun 13, 2015
@yuki24
Copy link
Contributor Author

yuki24 commented Jun 13, 2015

I believe everything should be good and we are ready to merge.

@schneems
Copy link
Member

schneems commented Oct 13, 2015

I spoke to @jeremy, I think it just fell off his radar. Can you rebase, this is good to merge.

@jeremy
Copy link
Member

jeremy commented Oct 13, 2015

👍 Looks like a changelog conflict only, no need to rebase.

@jeremy jeremy merged commit 5226058 into rails:master Oct 13, 2015
jeremy added a commit that referenced this pull request Oct 13, 2015
Add basic support for access control headers to ActionDispatch::Static
@yuki24
Copy link
Contributor Author

yuki24 commented Oct 15, 2015

@schneems @jeremy Thanks! ❤️ 💟 💛 💙 ❤️ 💟 💛 💙

@yuki24 yuki24 deleted the yuki24:access-control-support branch Oct 15, 2015
@connorshea connorshea mentioned this pull request Oct 19, 2015
0 of 4 tasks complete
@kaspth
Copy link
Member

kaspth commented Nov 3, 2015

@yuki24 nice! Have you sent a pull request to enable public_file_server as mentioned in the outdated diffs yet? 😁

@yuki24
Copy link
Contributor Author

yuki24 commented Nov 3, 2015

@kaspth Not yet. I'm not sure if I can work on it anytime soon, so feel free to send a PR if you could.

@schneems
Copy link
Member

schneems commented Nov 3, 2015

What was the idea?

kaspth added a commit to kaspth/rails that referenced this pull request Nov 3, 2015
As discussed in rails#19135 (comment).

Replaces `serve_static_files` to unify the static options under the `public_file_server` wing.

Deprecates `serve_static_files` accessors, but make them use the newer config internally.
@kaspth
Copy link
Member

kaspth commented Nov 3, 2015

@yuki24 @schneems added a pull request at #22173 😁

@@ -1,3 +1,22 @@
* Deprecate `config.static_cache_control` in favor of

This comment has been minimized.

Copy link
@kaspth

kaspth Nov 3, 2015

Member

Feels like this should be in the Railties CHANGELOG file. I will change it at #22173.

kaspth added a commit that referenced this pull request Nov 5, 2015
It was deprecated in #19135.

We're now favoring `public_file_server.headers`.
shamozhixing pushed a commit to shamozhixing/gitlab-ce that referenced this pull request Aug 23, 2016
…ions.

Deprecation of static_cache_control: rails/rails#19135

Deprecation of serve_static_files: rails/rails#22173
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

7 participants
You can’t perform that action at this time.