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.
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
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