Posted almost 8 years ago. Visible to the public.

Deliver Paperclip attachments to authorized users only

When Paperclip attachments should only be downloadable for selected users, there are two ways to go.

  1. Deliver attachments through Rails

The first way is to store Paperclip attachments not in the default public/system, but in a private path like storage inside the current release. You should prefer this method when dealing with sensitive data.

Make sure that folder is linked to the current release when deploying by adding this to your config/deploy.rb:

Copy
namespace :paperclip do desc "Create a storage folder for Paperclip attachment in shared path" task :create_storage do run "mkdir -p #{shared_path}/storage" end desc "Link the Paperclip storage folder into the current release" task :link_storage do run "ln -nfs #{shared_path}/storage #{release_path}/storage" end end before "deploy:setup", 'paperclip:create_storage' after "deploy:update_code", "paperclip:link_storage"

Also make sure that storage is ignored in your .gitignore or mayhem will ensue.

Now write a controller action that sends the attachment to authorized users. That action should be protected by aegis or a before_filter.

Copy
class NotesController permissions :notes # Authorization through Aegis def attachment note = Note.find(params[:id]) send_file note.attachment.path end end

Connect the action in your config/routes.rb:

Copy
map.resources :notes, :member => { :attachment => :get }

Your attachment can be configured like this:

Copy
class Note < ActiveRecord::Base has_attached_file :attachment, :path => ":rails_root/storage/:rails_env/attachments/:id/:style/:basename.:extension" end

To link to the protecting controller actions, e.g. in a view:

Copy
link_to 'Download attachment', [:attachment, @note]
  1. Store attachments in hashed paths

The method above is very secure, but it hits the framework stack everytime an attachment is downloaded. E.g. when you display an image gallery with 40 thumbnails, that would generate 40 expensive Rails requests.

An alternative is to do store Paperclip attachments in the default public/system, but generate the path from a hash function, e.g. attachments/519b6d87b1225ba3/9/beach.jpg. The hash function hashes the containing record id, class name and a secret that must be unique to every application you deploy. This way attachments will only be accessible to users who know the link. However, you can never take authorization away (rarely an issue with images) and if you have directory indexes activated in Apache, you screwed up big time.

Copy the code below to config/initializers/paperclip_hashed_path.rb:

Copy
Paperclip.interpolates :hashed_path do |attachment, style| secret = 'CHANGE-THIS-FOR-EVERY-APPLICATION! jivEutIgtepsIvgikUpCoshBypMowjuegbisIgJo' hash = Digest::MD5.hexdigest("--#{attachment.class.name}--#{attachment.instance.id}--#{secret}--") hash.slice(0, 16) end

Remember to change the secret in line 2 for every application you deploy! Changing a duplicated secret is very painful. We have a note on how to create strong secrets.

You can now use the hashed_path interpolation in your Paperclip attachments:

Copy
class Note < ActiveRecord::Base attachment_virtual_path = "/system/attachments/:rails_env/:hashed_path/:id/:style/:basename.:extension" attachment_real_path = ":rails_root/public" + attachment_virtual_path has_attached_file :attachment, :path => attachment_real_path, :url => attachment_virtual_path end

Paperclip will now automatically store attachments using hashed paths, and generate URLs accordingly. You don't need a protecting controller action here, because all security lies in the hashed secret.

To link to the protecting controller actions, e.g. in a view:

Copy
link_to 'Download attachment', [:attachment, @note]

After you deploy, make sure you do not get an directory index when you access http://yourpage.com/system/attachments.

  1. Further reading

    Common mistakes when storing file uploads with Rails

Does your version of Ruby on Rails still receive security updates?
Rails LTS provides security patches for old versions of Ruby on Rails (3.2 and 2.3).

Owner of this card:

Avatar
Henning Koch
Last edit:
10 months ago
by Michael Leimstädtner
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more