Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send ActiveStorage File to User Via Custom Controller Action

I am aware of the ActiveStorage url_for(user.avatar) method. Example:

<%= link_to "Download this file", url_for(@user.avatar) %>

This is great, but it appears like it has no authorization built around it. Anyone with this link can download this document.

In the past when I used paperclip, I had a link that went to a custom controller action. That custom controller action did authorization, and if all is good, then I used send_file to send the file to the user. It was something like this:

def deliver_the_file
  authorize :my_authorization
  send_file @user.avatar.path
end

How can I do this with active_storage? All I have gotten to work is the url_for implementation which does not authorize the user at all.

I am looking specifically at the Download Files part of the Rails Guides on ActiveStorage.

Relevant example code:

# model 
class Book < ApplicationRecord
  has_one_attached :book_cover
end


class BooksController < ApplicationController
 ... 

  # custom action to authorize and then deliver the active_storage file
  def deliver_it
    # ... assume authorization already happened
    send_file rails_blob_path(@book.book_cover, disposition: "attachment")
  end
end

This errors out and says:

Cannot read file

I also attempted this:

def deliver_it
  # ... assume authorization already happened
  send_file @book.book_cover.download
end

This returned the error:

string contains null byte

And I also attempted this:

def deliver_it
  @book.book_cover.download
end

This returned the error:

BooksController#deliver_it is missing a template for this request

I have also tried this:

def deliver_it
  send_file @book.book_cover.blob
end

This errors out and says:

no implicit conversion of ActiveStorage::Blob into String

like image 837
Neil Avatar asked May 10 '18 23:05

Neil


3 Answers

Your best bet is to use ActiveStorage::Blob#service_url to redirect to a signed, short-lived URL for the blob:

class Books::CoversController < ApplicationController
  before_action :set_active_storage_host, :set_book

  def show
    redirect_to @book.cover.service_url
  end

  private
    def set_active_storage_host
      ActiveStorage::Current.host = request.base_url
    end

    def set_book
      @book = Current.person.books.find(params[:book_id])
    end
end

Alternatively, serve the file yourself. You can use ActionController::Live to stream the file to the client instead of reading the entire file into memory at once.

class Books::CoversController < ApplicationController
  include ActionController::Live

  before_action :set_book

  def show
    response.headers["Content-Type"] = @book.cover.content_type
    response.headers["Content-Disposition"] = "attachment; #{@book.cover.filename.parameters}"

    @book.cover.download do |chunk|
      response.stream.write(chunk)
    end
  ensure
    response.stream.close
  end

  # ...
end
like image 70
George Claghorn Avatar answered Nov 07 '22 01:11

George Claghorn


I derived this solution from a comment on this ActiveStorage issue

def deliver_it
  send_data @book.book_cover.download, filename: @book.book_cover.filename.to_s, content_type: @book.book_cover.content_type
end
like image 22
Neil Avatar answered Nov 06 '22 23:11

Neil


I used redirect_to here and it worked:

redirect_to rails_blob_path(@book.book_cover, disposition: "attachment")
like image 3
kangkyu Avatar answered Nov 07 '22 01:11

kangkyu