setting mime type for image files before they are encrypted with Shrine and Rails

940 views Asked by At

My app encrypts and uploads certain files, and then makes them visible to admins. To achieve the latter functionality, my encryption gem's documentation suggests a controller action that looks like this:

def show
 user = User.find(params[:id])
 lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "id_docs", attribute: "image"))
 send_data lockbox.decrypt(user.id_docs.image.read), type: user.id_docs.image.mime_type, disposition: 'inline'
end

I want the file to stream, but the browser does not know how to interpret it, and downloads instead. This happens the files are encrypted before upload, and Shrine sets the mime type of these files to application/octet-stream.

My create action looks like this:

def create
 image = params.require(:id_doc).fetch(:image)
 respond_to do |format|
  if Shrine.mime_type(image) == 'image/jpeg'
   lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "id_docs", attribute: "image"))
   encrypted_image = lockbox.encrypt_io(image)
   @id_doc = IdDoc.create(user_id: current_user.id, image: encrypted_image)
   @id_doc.save
   format.html { redirect_to root_path }
  else
   format.html { redirect_to current_path }
  end
 end
end

If I do not encrypt the files, the mime type is saved as image/png or image/jpeg, which is what I want.

IdDoc.rb has a virtual attribute called :image which maps to an image_data field in the database:

class IdDoc < ApplicationRecord
 belongs_to :user
 validates_presence_of :image
 include IdDocUploader::Attachment(:image)
end

schema.rb

create_table "id_docs", force: :cascade do |t|
 t.bigint "user_id"
 t.text "image_data"
end

The data saved to image_data is saved in json format: {\"id\":\"iddoc/1/image/2de32e77f306f0e95aed24623e930683.png\",\"storage\":\"store\",\"metadata\":{\"filename\":\"Screen Shot 2020-10-31 at 7.24.08 AM.png\",\"size\":47364,\"mime_type\":\"application/octet-stream\"}}

How would I change the value of mime_type before the file creates? Is there any way to do this with Shrine, or should I go super hacky and parse that json directly?

1

There are 1 answers

0
Clemens Kofler On BEST ANSWER

Simply said: I think you're not quite doing this in the way Shrine intends and there's multiple ways to remedy this. I'll rank them from (in my opinion, based on complexity/appropriateness) best to worst:

  • Encryption could/should be treated as a part of Shrine's processing/derivatives concept. So you'd want to perform the processing there and set the metadata accordingly. The Shrine author himself has outlined solutions to process the original file in a thread here: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50.
  • You can override metadata manually: https://shrinerb.com/docs/metadata#controlling-extraction. This would probably mean not using attribute based assignment but instead calling the attacher directly (see below).
  • You can define your own MIME type determining logic as set out here: https://shrinerb.com/docs/plugins/determine_mime_type.
  • You're using Shrine.mime_type already. Use the value you get from this and store it in a separate database column mime_type on your id_docs model and then use it when you're reading from the database. Note that this probably implicitly means that the value in this column and the value you get from the file's metadata's mime type are out of sync.

Using the attacher directly:

mime_type = Shrine.mime_type(image)

# ...

@id_doc = IdDoc.create(user_id: current_user.id) do |id_doc|
  id_doc.image_attacher.model_assign(encrypted_image, metadata: { mime_type: mime_type })
end