I'm trying to create an "asset controller" shim which will filter static asset requests so only authorized users can get retrieve certain assets. I wanted to continue to use the asset pipeline so I setup a route like this
get 'assets/*assetfile' => 'assets#sendfile'
Then I created an AssetsController with one method "sendfile". Stripping it down to only the stuff that matters, it looks like this:
class AssetsController < ApplicationController
def sendfile
# Basically the following function forces the file
# path to be Rails.root/public/assets/basename
assetfilename=sanitize_filename(params[:assetfile] + '.' + params[:format])
send_file(assetfilename)
end
end
It looks like I have to run this in production mode as rails by-passes my route for assets in development. So I precompile my assets and I can verify in the controller that the files exist where they are expected to be.
However, now the problem is that I'm getting a "ActionController::InvalidCrossOriginRequest" when the Javascript asset is requested (just using the default application.* assets for now). I've read about this error and I understand that as of Rails 4.1 there are special cross-origin protections for Javascript assets. Sounds good to me, but I don't understand where the "cross-origin" part is coming from. Using firebug, I can see that the asset requests are being requested from the same domain as the original page.
I am certain that this is the problem because I can solve it by putting "skip_before_action :verify_authenticity_token" in the beginning of my controller. However, I really don't want to do this (I don't fully understand why this check is necessary, but I'm sure there are very good reasons).
The application.html.erb file is unchanged from the default generated file so I assume it's sending the CSRF token when the request is made, just as it would if I didn't have my own controller for assets.
So what am I missing?
Ok, I think I answered my own question (unsatisfactorily). Again, long post, so bear with me. I mistakenly forgot to add this to my original questions, but I'm using Ruby 2.2.0 and Rails 4.2.4.
From looking at the code in "actionpack-4.2.4/lib/action_controller/metal/request_forgery_protection.rb", it looks like Rails is doing two checks. The first check is the "verify_authenticity_token" method which does the expected validation of the authenticity token for POST requests. For GET requests, it ALSO sets a flag which causes a second check on the formed computed response to the request.
The check on the response simply says that if the request was NOT an XHR (AJAX) request AND the MIME Type of the response is "text/javascript", then raise an "ActionController::InvalidCrossOriginRequest", which was the error I was getting.
I verified this by setting the type to "application/javascript" for ".js" files in "send_file". Here's the code:
if request.format.js?
send_file(assetfilename, type: 'application/javascript')
else
send_file(assetfilename)
end
I can skip the response check all together by just adding the following line to the top of my controller class:
skip_after_action :verify_same_origin_request
The check on the response seems pretty weak to me and it's not clear how this really provides further protection against CSRF. But I'll post that in another question.
Related
Edit: I re-added the options method in a pull request to Rails which should now be live. The answer below should no longer be necessary. Call process(:options, path, **args) in order to preform the options request.
See commit 1f979184efc27e73f42c5d86c7f19437c6719612 for more information if required.
I've read around the other answers and none of them seemed to work in Rails 5. It's surprising that Rails doesn't just ship with an options method, but here we are. Of course if you can use xdomain, you probably should (edit: I no longer hold this view, there are advantages to CORS) because it's both faster (no preflight check doubling latency!), easier (no need for silly headers / HTTP methods!), and more supported (works basically everywhere!) but sometimes you just need to support CORS and something about the CORS gem makes it not work for you.
At the top of your config/routes.rb file place the following:
match "/example/",
controller: "example_controller",
action: "options_request",
via: [:options]
And in your controller write:
def options_request
# Your code goes here.
end
If you are interested in writing an integration test there is some misinformation around the process method, which is not actually a public method. In order to support OPTIONS requests from your integration tests create an initializer (mine is at: config/initializers/integration_test_overrides.rb because I override a number of things) and add the following code:
class ActionDispatch::Integration::Session
def http_options_request(path)
process(:options, path)
end
end
So that you can call http_options_request from your integration test.
In my Rails 4 application I have a model that needs to build links to other parts of the application. I am using Rails.application.routes.url_helpers.<path_name> to generate the URL of this link. The problem I have is that this generated path doesn't included the nested path.
Locally the app is served to localhost:3000 and all the paths work correctly, but when I deploy to a remote server it is served by Nginx/Passenger using the root http://<servername>/admin and the paths are incorrect. So as an example, what I want is payments_path to resolve to "/admin/payments", but instead I get "/payments".
The strange thing is when I use payments_path directly in my view or other places in my app, I get the path with the nested /admin path, ie "/admin/payments".
Anyone have any idea why path is giving me two different things in the view and the Rails.application.routes.url_helpers?
I think the core issue is that path generation at the view / controller level is getting some additional metadata to generate the paths correctly. I ended up finding a blog post dealing with the subject of routes inside models.
The author of the blog (Adam Hawkins) recommends the following approach for adding paths generation as a concern to the model,
module Routing
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
included do
def default_url_options
ActionMailer::Base.default_url_options
end
end
end
class UrlGenerator
include Routing
end
This concern piggybacks off the ActionMailer configuration, which in my case is,
config.action_mailer.default_url_options = { host: 'https://dev.<domain-name>.com/admin' }
However after I added this concern I still couldn't generate the full relative path, but I could get the absolute url generation working. So I ended up using payments_url instead of payments_path.
I think there is probably a way to get relative paths to work, but this is my workaround for the time being.
I am working on an application where I use paperclip for uploading images, then the image is manipulated in a flash app and returned to my application using application/octet-stream. The problem is that the parameters from flash are not available using params. I have seen examples where something like
File.open(..,..) {|f| f.write(request.body) }
but when I do this, the file is damaged some how.
How can I handle this in rails 3?
After you make sure that the request parameters have hit the Rails application, you may want to ensure that there were no parsing problems. Try to add these lines in you controller's action:
def update # (or whatever)
logger.debug "params: #{params.inspect}"
# I hope you do not test this using very large files ;)
logger.debug "request.raw_post: #{request.raw_post.inspect}"
# ...
end
Maybe the variable names got changed somehow? Maybe something escaped the parameter string one time too much?
Also, you have said that the file into which you want to save the request body is damaged. How exactly?
The request.body object does not need to be String. It may be a StringIO, for example, so you may want to type this:
File.open(..,..) {|f| f.write(request.body.read) }
I'm using FLEX 3 to make XML request to Rails 3. Since FLEX 3 only offers POST and GET, I have to use the "?_method=PUT" hack to maintain proper RESTfulness:
http://127.0.0.1:3000/locator/locator_users/1.xml?_method=PUT
On the server side its showing up as a POST and I'm getting an ActionController::RoutingError (No Route Matches).
I did a rake routes and the route is there, properly namespaced and all.
This worked fine with Rails 2, so I have reason to believe it must be Rails 3 that changed. After doing some searching, people seemed to have indicated that it should still work. But, it's not for me. Can anyone confirm or deny Rails 3 compatibility?
UPDATE
OK, after some more tinkering, I think this is actually a problem of Flash Player 10. Flash PLayer 9 seems to work fine with "_method=" hack, 10 does not. See this new post I wrote (Flash Player 9 vs Flash Player 10 with FLEX 3, ?_method=PUT/DELETE not working?).
Partly this is due to Rack::MethodOverride's behavior. It does not check the query params for _method, so a call to http://127.0.0.1:3000/locator/locator_users/1.xml?_method=PUT
will not get overridden properly due to that.
I wrote a piece of Rack middleware that replaces it to fix this particular issue.
All you have to do is
add it to the Gemfile
gem 'rack-methodoverride-with-params'
swap Rack::MethodOverride out in config/environment.rb
config.middleware.swap Rack::MethodOverride, Rack::MethodOverrideWithParams
If you can add _method=PUT in your request body, then no need to swap out the rack middleware.
If you cannot do that, then I've come across another (lower-impact) solution, which is to simply define a custom route that accomplishes what you're looking for, for example:
# config/routes.rb
post '/locator/locator_users/:id', to: 'locator_users#update', constraints: {_method: 'POST'} # allow http method override
Granted, you would have to add this route for every resource you need HTTP method override on, but that might be a good thing if you want to limit your exposure to potential weirdness since this breaks HTTP semantics.
EDIT: You can do the same thing with GET requests if you need to, just swap out post for get (this can be useful if you need to support REST over JSONP).
You might have to rewrite how you're making your request to the server. I'm using Rails 3, Flex 4, and Flash 10 together (but with an app developed in Flex 3) and use _method as a parameter in my HTTPService object (leaving the content-type as the default application/x-www-form-url).
HTTPService only supports GET and POST requests. If you use set useProxy property to true on the HTTPService object, you can use HEAD, OPTIONS, TRACE, and DELETE but only if you are also using a server-based proxy service. If this doesn't work, then you may want to try URLLoader or URLRequest or implementing your own custom solution instead.
This may be really obvious, but in rails how can I get the contents of a page without making an HTTP request?
i.e. I want to grab the contents of any page that my rails app generates, in a script on the same server. This is of course trivial with an HTTP request, but how can I get the contents of a page otherwise?
This is what I am doing now which works fine:
require 'open-uri'
contents = open("http://localhost/path_to_my_page").read # but I really want to get rid of the overhead of the HTTP request
I need to be able to do this from a script, within the rails app, not a controller (e.g. with render_to_string)
This is how you get the contents of a page in the console. It might work for you:
require 'action_controller/integration'
app = ActionController::Integration::Session.new;
app.get('/path_to_your_page')
puts app.response.body
In your controller , you can call render_to_string instead of render and the page is returned instead of being send to the browser.
The arguments of both methods are the same, so you need to specify which controller and action you require to render.
Why not use erb directly? See this question for more details