🐈

ActiveStorageダイレクトアップロードでファイルサイズの制限を設けるには

2023/01/26に公開

ActiveStorageのダイレクトアップロード機能を利用すると、ブラウザから直接クラウドストレージにアップロードできるため、Railsアプリで大容量のデータを受け取ることなく、大容量ファイルのアップロードも可能です。

しかしながら、無制限に受け入れていては、想定外の大容量ファイルがアップロードされてしまう可能性があります。たとえば、数MBの画像の想定のところ1TBの動画がアップロードされると困ってしまいますね。

ActiveStorageでファイル容量のバリデーションを行うには

ActiveStorageのバリデーションを実装するgemはいくつかあります。

https://github.com/igorkasyanchuk/active_storage_validations

次のように書くと、1MB以上のファイルを禁止することができます。

class Post < ApplicationRecord
  has_one_attached :image
  validates :image, attached: true, size: { less_than: 1.megabytes , message: 'is too large' }
end

しかし、これだけでは、Postの保存は防げますが、クラウドストレージへのダイレクトアップロードは防げません。

ダイレクトアップロード可能なファイル容量のバリデーションを行うには

ActiveStorageのダイレクトアップロードは以下の手順で行われます。

  1. ダイレクトアップロードのための署名付きURLとHTTPヘッダーを生成
    • アップロードするファイルの情報をPOST( /rails/active_storage/direct_uploads
      • ファイル容量やファイル種別などをActiveStorageに伝える
      • ActiveStorage::Blob.create_before_direct_upload!
      • ダイレクトアップロードに必要な署名付きURLなどを取得
  2. クラウドストレージへのアップロード
    • 1で生成した署名付きURLとHTTPヘッダーを使用
    • 署名により、事前に申告したファイル容量・ファイル種別以外はアップロードできない
  3. PostActiveStorage::Blob の紐づけ

Postモデルのバリデーションは上記手順の3で行うため、2のアップロードを防げません。
アップロード自体を禁止するためには、1でエラーを返す必要があります。

クラウドストレージへのダイレクトアップロードを禁止する方法としては、例えば次のような方法があります。

  • S3 POST Policy を使う
    • S3バックエンドの場合、S3ではPOST Policyという仕組みでアップロードについてのルールを設定できます
  • パラメータのチェックを行う、カスタム DirectUploadsController を作成して、fileフィールドのdirect-upload-url= で指定する
  • ActiveStorage::Blob にバリデーションを追加する

S3 POST Policy を使う

クラウドストレージへのアップロード時のHTTPヘッダーとしてPOST PolicyとPolicyを含むSignatureを送る必要がありますが、ActiveStorageではこれらに対応することはできません。
従ってこの方法は使えません。

パラメータのチェックを行う、カスタム DirectUploadsController を作成して、fileフィールドのdirect-upload-url= で指定する

この方法では f.file_field :file, data: { 'direct-upload-url': my_direct_uploads_path } のように指定する必要があり、使い勝手が悪いです。また、標準の rails_direct_uploads_path が残っていると、そちらからアップロードされる可能性も残るので確実ではありません。従って避けるべきだと考えます。

ActiveStorage::Blob にバリデーションを追加する

オープンクラスであることを利用して、ActiveStorage::Blob にバリデーションを追加する方法です。
これにより、要件を満たさない場合にダイレクトアップロードに必要な署名付きURLやヘッダーを生成できず、ダイレクトアップロードを事前に防止することができました。
ただし、このバリデーションはattachされた特定のモデルに対してではなく、Railsアプリ全体のダイレクトアップロードに適用される点に注意してください。

# config/initializers/set_byte_size_limit_on_blobs.rb
ActiveSupport.on_load(:active_storage_blob) do
  validates :byte_size, numericality: { less_than: 10.megabytes }
end

ダイレクトアップロードできない

ActiveSupport.on_load(:active_storage_blob)

ActiveSupport.on_load(:active_storage_blob, &block)block は、ActiveStorage::Blob が読み込まれた際に、ActiveStorage::Blob のコンテキストで実行されます。

https://github.com/rails/rails/blob/cd2ed0186bd8bc0eca743b7f1cbc149ab1dc27fd/activestorage/app/models/active_storage/blob.rb#L405

https://railsguides.jp/engines.html#railsフレームワークの読み込みを回避する

まとめ

ActiveRecord::Blobをattachするモデルのバリデーション(モデルごとのバリデーション)と、ActiveRecord::Blobのバリデーション(アプリケーション全体のバリデーション)を組み合わせることで、

  • 巨大なファイルのダイレクトアップロードを防止する
  • 特定のモデルでさらに細かな制限を設ける

ことができます。

タケユー・ウェブ株式会社

Discussion