Restrict access to images in Rails app - ruby-on-rails

I have rails project. In my project I load images on server (rails 3 + paperclip + devise + cancan). I want to limit access to files (for example, the original image can be viewed only by the administrator). How should I do it?

If you are limiting by an attribute in your database then one way would be to serve the image via a controller. It's not the most performant but it is secure.
I haven't tried this out, but if you were serving the image from a URL like
/images/picture_of_mickey_mouse.png
then you could create a route in your app that responds to /images with a filename attribute and serve all those images through a controller.
e.g.
class ImagesController < ApplicationController
before_filter :authenticate_admin!, :only => [:show]
def show
send_file "/images/#{params[:filename]}", :disposition => 'inline'
end
end
You would want to make sure you sanitize the params[:filename] however, otherwise the user would be able to download any file on your server!
The docs on send_file are here: http://apidock.com/rails/ActionController/DataStreaming/send_file

Files are handled with ActionDispatch::Static. IMO the best solution is provide similar middleware (basing on Rails source code) but with some authentication and insert it before ActionDispatch::Static. This would work with Warden-based solutions (like Devise) since they do authentication in middleware; just ensure your middleware is put after Warden and old plain ActionDispatch::Static is run just after them.
EDIT: One more thing worth noting. It's quite common in production that Nginx (I'm not sure about Apache and others) is configured to serve all the static files by itself, without passing those requests to rack stack. You may need to disable this Nginx' feature.

Related

Managing access to a file with a direct link

I have a rails app where I upload files. I'm using gem cancan for managing access to files. The files are stored on the disk. Is it possible to manage access to a file or restrict/allow it even when a user has a direct link to it? Note I'm not using nginx or apache, it's a local application, therefore at best it's unicorn or simply the standard Rails web server.
Yes, it's possible. Move your files out of public folder, which is served by by your web server, to some other folder (private for example) and use send_file in controller to transmit data. Some pseudo-code for controller:
def get_file
absolute_path = Rails.root + 'private' + params[:filepath]
if current_user.allowed_to_download?(params[:filepath]) && File.exists?(absolute_path)
send_file absolute_path, status: 200 # ok
else
render status: 403 # forbidden
end
end
You will have to setup route for this action, but basicly that's all.

Prohibit Direct URL for public, Rails

I'm making a Rails application.
When user post content, it makes a folder in public folder. Only admin can put images in it. And user can watch it in folder he made.
But if other users put direct url like this http://railsapp.com/folder/test/test.jpg/, it shows.
I want to prohibit to show when somebody access directly. How should I do?
Do I make the folder in Rails.root? But I don't know how to show it. Please give me a advise.
The solution here is pretty obvious - don't upload to /public if you don't want it to be public. And no you don't want to change the access rules for your public directory as this will mess up all your assets and the rails fallback error pages.
The common choice is to use /storage but then you need to use Rails to serve the assets.
That would involve creating a model and a controller for the uploaded files and serving them with send_file.
class AttachmentController
def show
#attachment = Attachment.find(params[:id])
# #todo authorize access.
send_file #attachment.path, type: #attachment.mime, disposition: 'inline'
end
end
A workaround for cases when you don't want to tie up rails threads just with serving images is to use a non guessable hash in the path name to your "private" assets. Note that they are not really private - just harder to find.

Rails + Paperclips + Rackspace CloudFiles with the Private CDN

I have a Rails application that uses Paperclip to handle uploaded files and we are currently hosted by Rackspace.
The application is currently hosted on a single server and I am building out a more scalable solution with load balancers, application servers, and a separate database server. The last thing I need to do is a solution for the uploaded assets. I have tried to use Rackspace's CloudFiles, but it seems the only way to use paperclip and CloudFiles is to have them on the public CDN, which I can't use, a user needs to be authenticate to access the files. Before I turn to Amazon S3, since they have the option for temporary URLs, does know how to use CloudFiles with Paperclip and require authentication to access the files?
Any help, tips, google searches, links, or solutions would be greatly appreciated.
As it happens, Cloud Files also supports the generation of temporary URLs, and it appears that Paperclip does allow you to make use of it. Just generate the URL from your Attachment with #expiring_url instead of #url in your views:
= image_tag #organization.logo.expiring_url(Time.now.to_i + 100, :original).gsub(/^http:/, "https")
Paperclip will only generate http urls, but since Rackspace's temporary URLs don't use the scheme in their checksums, you can use a gsub call to turn it into an https URL. Also, notice that the first argument to #expiring_url is an absolute timestamp (in seconds-since-the-epoch).
Expiring URLs for Rackspace only made it into fog somewhat recently -- v1.18.0 -- so if you're using an older version, you may need to upgrade fog to take advantage of them:
bundle upgrade fog
Paperclip also supports generating obfuscated URLs, which looks interesting, but would be less secure, since the server wouldn't expire it.
You can add the key like this:
class Rackspace
def self.add_temp_url_key
require 'fog'
puts "Creating Storage Service"
begin
service = Fog::Storage.new(
:provider => 'rackspace',
:rackspace_username => ENV['FOG_USERNAME'],
:rackspace_api_key => ENV['FOG_API_KEY'],
:rackspace_region => ENV['RACKSPACE_REGION'].to_sym
)
service.post_set_meta_temp_url_key(ENV['RACKSPACE_TEMP_URL_KEY'])
puts "X-Account-Meta-Temp-Url-Key successfully set to #{ENV['RACKSPACE_TEMP_URL_KEY']}"
rescue => e
puts "Unable to set X-Account-Meta-Temp-Url-Key - #{e.inspect}"
puts e.backtrace
end
end
end

Rails/Gollum: Gollum does not use path prefix to load assets

I mount gollum inside my Rails app like provided this answer.
It works, however the CSS and Javascript assets do not load properly.
routes.rb:
authenticate :user do
mount Precious::App, at: 'wiki'
end
So I can access the wiki at /wiki. This works, but gollum tries to load the css files from eg. http://localhost:3000/css/gollum.css which does not work, instead of http://localhost:3000/wiki/css/gollum.css. How do I tell gollum to use the correct prefix?
This is almost certainly a bug in either Rails or Gollum. I know that Gollum does have the ability to map its assets to a subpath, because I do it in my apps (via Rack, though, not Rails routes), like this:
map "/wiki" do
run Precious::App
end
Reporting a bug would probably be your best bet.

How do I add asset search paths to Sprockets based on a wildcard subdomain in rails 3.1?

The Rails Asset Pipeline guide instructs you to use config.assets.paths in config/application.rb but I don't have access to the request's subdomain at this point.
I'd like to be able to prepend an extra path (for the current request only) based on the request's subdomain.
My application specific details
It's a basic CMS app. The root domain.com host handles the administrative part with standard controller/view rendering and default asset paths.
Requests to subdomain.domain.com renders the site based on subdomain. It calls prepend_view_path in a before_filter and adds Rails.root.join('vendor/sites/[subdomain]/templates') for the current request only.
I'd like to be able to prepend Rails.root.join('vendor/sites/[subdomain]/assets') to the Sprockets search paths when the request host is [subdomain].domain.com.
EDIT
I ended up just dropping in a mixin for Sprockets::Environment that overwrites the call method:
module SiteAssetsResolver
def call(env)
begin
# prepend path based on subdomain (from env)
super # Sprockets::Server#call
ensure
# remove path based on subdomain
end
end
end
MyApp::Application.assets.extend(SiteAssetsResolver)
Just as you did for your view path, add a before filter and append the new path to Rails.application.config.assets.paths
I got this idea while watching Railscasts #279 Understanding the Asset Pipeline
I agree with commenter on your question that said "The asset pipeline isn't really meant to be compiling your assets each request in production." -- making it not really possible to do exactly what you ask.
So how about an alternative to accomplish what you're really trying to accomplish here, which is different asset resolution for different subdomains. Put your sub-domain specific assets in sub-directories of your asset folders.
Now, in the view/helpers, when you call asset_path or any other helpers that take a relative asset path, ask it for "#{subdomain}/name_of_asset" instead of just "name_of_asset".
Now, because of the way the asset compiler works, it's possible this subdirectory method won't work, you may have to put the subdomain at the beginning of the actual filename instead. "#{subdomain}_name_of_asset". Not sure.
And this still wouldn't give you a sort of 'default fall through' where some assets in some subdomains don't have subdomain-specific assets, they just 'fall through' to the default. Which would be nice. It's possible a way can be figured out to do that too, not sure.
But at any rate, following this approach of asking for a different asset at display-time using logic in view/helper.... is going to get you further than your original suggested approach, which probably isn't possible.

Resources