How To: Secure Upload

Ryan Leto edited this page Jan 29, 2016 · 9 revisions
Clone this wiki locally

Uploading files to their default directory in the Public folder can be dangerous if you're looking to restrict who can download the file. You will need to avoid uploading files to this Public directory at all costs. Instead, you can upload the file to the root of your folder where it will be inaccessible by default. We will be creating a download in the file controller. This way, you can use authorization (like cancan) to permit access to certain files using download.

There are two ways of getting to this result, choose which you find better, don't apply both:

Option 1. Change your document_uploader.rb (uploader file)

def store_dir
  "/PATH/RAILSAPPLICATION/uploads/#{model.id}"
end

def cache_dir
  "/PATH/RAILSAPPLICATION/tmp/uploads/cache/#{model.id}"
end

Option 2. Leave your document_uploader.rb unchanged and change the root directory of CarrierWave in the carrier wave initializer config/initializers/carrierwave.rb:

CarrierWave.configure do |config|
  # These permissions will make dir and files available only to the user running
  # the servers
  config.permissions = 0600
  config.directory_permissions = 0700
  config.storage = :file
  # This avoids uploaded files from saving to public/ and so
  # they will not be available for public (non-authenticated) downloading
  config.root = Rails.root
end

Having chosen one of the two methods above, continue the procedure below, which serves both:

Make sure that you have write access to those locations. You can then upload your files like normal. However, when you go to recall the file, you will notice that the URL is the full path of that computer where the file is located. This just won't work! In this example, I am uploading a file to

/uploads/fileid/filename.extension

In my routes.rb, I will need to change the path to my file.

  match "/uploads/:id/:basename.:extension", :controller => "redocuments", :action => "download", :conditions => { :method => :get }

In my controller, I will need to create and pass some variables to dynamically change the link.

def download
  path = "/#{redocument.redocument}"
  send_file path, :x_sendfile=>true
end

In my view, I can create my URL link to the file

<%= link_to File.basename(f.redocument.url), "/uploads/#{f.id}/#{File.basename(f.redocument.url)}" %>