Managing access to a file with a direct link - ruby-on-rails

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.

Related

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.

How to put a windows installable program on Heroku for user download in a Rails web application?

I have a rails web application that contains a Windows executable file the user can download and install on his PC. This program will communicate with certain peripherals on the user's PC using web services to the web application.
Where should I put such a program in my Rails tree, and how should I make this "available" for users to download?
Assets
Alternatively to putting them in the public dir, you may wish to create a downlods folder in the asset pipeline: /app/assets/downloads
This will give you the ability to call downloads_path, and precompile each file.
In order to get this to work, you'll be best creating a helper method like this:
#app/helpers/application_helper.rb
def downloads_path path
asset_path("/downloads/#{path}")
end
#app/views/application/index.html.erb
<%= downloads_path("download.exe") %>
This has the added benefit of allowing you to precompile the files, and having them accessible from the likes of CDN's, as Michael Szyndel recommended

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 serving large files

I'm developing an application serving large videos only to logged users.
To keep these videos private i put them in a private folder inside Rails project and let Rails serve them, instead of using the public folder and excluding requests from apache (to avoid direct linking to them).
My action in the controller looks like this:
def video
respond_to do |format|
format.mp4{
send_file File.join([Rails.root, "private/videos", #lesson.link_video1 + ".mp4"]),
:disposition => :inline, :stream => true
}
end
end
Everything works perfectly, but just with small files, as soon as i try with real files i receive the error:
NoMemoryError (failed to allocate memory)
I read somewhere that is not a good practice to use send_file for large files, but using the other approach, to let apache serve the files, i had an issue serving files to mobile apple devices, as they're not sending the HTTP_REFERER.
Do you have any idea on how small is this memory limit?
My videos are from 400MB to 2GB (trying to reduce them).
The only question i found here is without an answer serving large media files from the assets folder in rails
I managed to activate X-Sendfile on Apache instead of letting Rails to serve large files. Working with Capistrano i found a good solution. Here is explained how Capistrano & X-Sendfile

Restrict access to images in Rails app

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.

Resources