Le téléchargement direct S3 avec stockage local lève une NoMethodError au lieu d’être rejeté

J’ai trouvé et résolu ce problème moi-même après qu’un client l’ait signalé. J’ai utilisé une IA pour générer le rapport de bug car j’étais paresseux et qu’elle fait un meilleur travail que moi — et elle a découvert un bug fondamental en le faisant, que j’ai vérifié manuellement.

Ce n’est pas grave, mais il pourrait être utile de vérifier si ce schéma provoque des problèmes dans d’autres endroits également.

Tout a commencé quand quelqu’un a coché enable_direct_s3_uploads sans réellement configurer et activer S3, ce qui a cassé tous les téléversements, renvoyant des erreurs 500 en coulisses. J’ai pensé que la valeur 500 méritait d’être investiguée.

Résumé

Lorsque enable_direct_s3_uploads est activé alors que le magasin de stockage actif est local, la tentative de téléversement peut provoquer :

NoMethodError (undefined method 'signed_request_for_temporary_upload' for an instance of FileStore::LocalStore)

app/services/external_upload_manager.rb:34:in 'ExternalUploadManager.create_direct_upload'
lib/external_upload_helpers.rb:56:in 'ExternalUploadHelpers#generate_presigned_put'
app/controllers/application_controller.rb:443:in 'block in ApplicationController#with_resolved_locale'
app/controllers/application_controller.rb:443:in 'ApplicationController#with_resolved_locale'

Cela semble être causé par une combinaison d’un état de configuration invalide et d’un rappel before_action écrasé.

Configuration

L’état problématique est effectivement :

SiteSetting.enable_direct_s3_uploads == true
SiteSetting.enable_s3_uploads == false
Discourse.store.is_a?(FileStore::LocalStore) == true

Pour le stockage local, Discourse.store n’implémente pas :

signed_request_for_temporary_upload

ce qui est attendu, car cette opération n’a de sens que pour un magasin externe/S3.

Comportement attendu

Si les téléversements directs S3 sont activés alors que le magasin actif est local, la requête devrait être rejetée proprement par external_store_check.

Idéalement, la combinaison de paramètres de site invalide devrait également être prévenue ou validée.

Comportement réel

La requête atteint ExternalUploadManager.create_direct_upload, qui appelle :

store.signed_request_for_temporary_upload(...)

sur un FileStore::LocalStore, ce qui entraîne une NoMethodError.

Cause possible

ExternalUploadHelpers enregistre le rappel suivant :

before_action :external_store_check,
  only: %i[
    generate_presigned_put
    complete_external_upload
    create_multipart
    batch_presign_multipart_parts
    complete_multipart
    abort_multipart
  ]

Cela devrait empêcher la requête d’atteindre le code de téléversement externe lorsque le magasin est local.

Cependant, UploadsController enregistre plus tard un autre rappel en utilisant la même méthode de filtre :

before_action :external_store_check,
  only: %i[_show_secure_deprecated show_secure]

Rails traite l’enregistrement répété du même rappel comme une redéfinition, de sorte que le dernier semble remplacer les conditions only: antérieures.

image

En conséquence, external_store_check n’est plus exécuté pour generate_presigned_put, permettant à un magasin local d’atteindre le chemin de code de téléversement direct.

Reproduction

  1. Configurer une instance Discourse avec un stockage de téléversement local.
  2. S’assurer que :
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true
  1. Tenter de téléverser un fichier.
  2. Observer la NoMethodError provenant de FileStore::LocalStore.

Une vérification de l’état via la console peut être effectuée avec :

[
  SiteSetting.enable_direct_s3_uploads,
  SiteSetting.enable_s3_uploads,
  Discourse.store.class,
  Discourse.store.external?
]

Correction suggérée

Les enregistrements de rappel ne devraient pas s’écraser mutuellement.

Par exemple, inclure les actions de téléversement sécurisé dans le rappel external_store_check existant :

before_action :external_store_check,
  only: %i[
    generate_presigned_put
    complete_external_upload
    create_multipart
    batch_presign_multipart_parts
    complete_multipart
    abort_multipart
    _show_secure_deprecated
    show_secure
  ]

Alternativement, utiliser une méthode de rappel distincte pour les actions de téléversement sécurisé.

Il peut également être judicieux d’ajouter une validation afin que enable_direct_s3_uploads ne puisse pas être activé alors que les téléversements S3/externes sont désactivés.

Solution de contournement

Pour les sites utilisant un stockage de téléversement local :

SiteSetting.enable_direct_s3_uploads = false

empêche l’erreur.

2 « J'aime »

Merci, cela devrait être corrigé par :

1 « J'aime »