4

How would I do authorization on files uploaded with Refile gem using Pundit? I have uploaded files which should be restricted to the user that uploaded them, but anyone with the url that Refile's attachment_url generates can access the file. Since Refile uses it's own Sinatra app, there's no rails controller for me to call Pundit's authorize method in.

Jerome
  • 43
  • 3

1 Answers1

2

In your controller, you can have a method which will download the file. For a more complicated example, let's say that you have a download action in your UsersController controller. From within here, you can use pundit as you normally would. This download action grabs the image of the user.

Disclaimer: This is a horrible example of production practice as you are going to be hammering the server if this action is called a lot. You are essentially resizing the image every single time this action is called. However, as a proof of concept to go outside of how refile file download normally works and adds in the authorization you're seeking.

We create a processor variable which initializes the ImageProcessor fill option. We then create a temporary file and set it to binary mode. We take the file from our user model and read it into the temporary file. Rewind the temporary file to the beginning of the file and read it into MiniMagick. We then call on our processor to convert the temporary file (leaving the original intact). We then send the file to the user.

  def download
    @user = User.find(params[:id])
    authorize @user
    processor = Refile.processor(:fill, Refile::ImageProcessor.new(:fill))
    temp_file = Tempfile.new('profile_image')
    temp_file.binmode
    temp_file.write @user.profile_image.read
    temp_file.rewind
    image_file = MiniMagick::Image.new(temp_file.path)
    file = processor.fill(image_file, 150, 150)
    temp_file.close
    send_file file.path
  end  

Here is the example of it rendering the file as an image_tag

enter image description here

Along with the code that calls the image to be resized and downloaded.

<%= image_tag user_download_path(@user), class: 'img-circle img-thumbnail' %>

You can set your action to accept various other processing options and dimensions. For this example, I'm showing how to fill a size of 150x150 pixels.

Edit to add more clarity:

The function of the temp_file is to leave the original image alone. If you are looking to simply provide an unprocessed download of the original file, you could do something like this below. You should also read on send_file and send_data as they provide other things like filename, disposition, content_type, etc. options for customizing the download and how it should be handled.

  def download
    @user = User.find(params[:id])
    authorize @user
    send_file @user.profile_image.download
  end

Edit: I looked further into the Refile source and found that the creation of the file links is caused by the mounting of the engine within the routes. Create an initializer file and place the code below in there. This will allow you to keep the existing functionality described above while removing the public links to the files uploaded.

Refile.configure do |config|
  # config.direct_upload = ["cache"]
  # config.allow_origin = "*"
  # config.logger = Logger.new(STDOUT)
  # config.mount_point = "attachments"
  config.automount = false
  # config.content_max_age = 60 * 60 * 24 * 365
  # config.types[:image] = Refile::Type.new(:image, content_type: %w[image/jpeg image/gif image/png])
end
kobaltz
  • 6,980
  • 1
  • 35
  • 52
  • Thanks kobaltz! This is essentially what I ended up doing at the suggestion of the Refile author minus the image processing. My files were pdfs, so my download action created a temporary link directly to S3 to download the files, bypassing Refile entirely. If it were images, you are absolutely right, this would kill my app by not utilizing caching. Thanks again! – Jerome Mar 15 '15 at 03:30
  • No problem. I showed the image processing since it is directly related to the function of Refile whereas PDF files usually are not post processed. Also, keep in mind that your attachment URL will still be accessible if someone were to try and brute force the the site. You can advert this by closing that hole in the Refile gem. – kobaltz Mar 15 '15 at 03:32
  • @Jerome can you share some details on how you managed to the get the S3 urls, some sample code maybe? Also would you be kind enough to share a link to the github issue where you discussed this with jnicklas? – Mohamad Aug 01 '15 at 12:04