La carga directa en S3 con almacenamiento local lanza NoMethodError en lugar de ser rechazada

Encontré y resolví este problema por mi cuenta después de que un cliente lo reportara. Usé IA para generar el informe del error porque estaba perezoso y lo hace mejor que yo; además, mientras lo hacía, detectó un error en el núcleo, que verifiqué manualmente.

No es nada grave, pero podría tener sentido comprobar si este patrón está causando problemas en otros lugares también.

Todo comenzó cuando alguien habilitó enable_direct_s3_uploads sin haber configurado y activado S3, lo que rompió todas las cargas, devolviendo errores 500 en segundo plano. Pensé que el error 500 merecía una investigación.

Resumen

Cuando enable_direct_s3_uploads está habilitado mientras el almacén de carga activo es local, intentar una carga puede provocar:

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'

Esto parece ser causado por una combinación de un estado de configuración inválido y una sobrescritura de la callback before_action.

Configuración

El estado problemático es, en esencia:

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

Para el almacenamiento local, Discourse.store no implementa:

signed_request_for_temporary_upload

lo cual es esperable, ya que esa operación solo tiene sentido para un almacén externo/S3.

Comportamiento esperado

Si las cargas directas a S3 están habilitadas mientras el almacén activo es local, la solicitud debería ser rechazada limpiamente por external_store_check.

Idealmente, también se debería prevenir o validar la combinación inválida de la configuración del sitio.

Comportamiento actual

La solicitud llega a ExternalUploadManager.create_direct_upload, que llama a:

store.signed_request_for_temporary_upload(...)

en un FileStore::LocalStore, lo que resulta en un NoMethodError.

Posible causa

ExternalUploadHelpers registra la siguiente callback:

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

Esto debería impedir que la solicitud llegue al código de carga externa cuando el almacén es local.

Sin embargo, UploadsController registra posteriormente otra callback utilizando el mismo método de filtro:

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

Rails trata la registración repetida de la misma callback como una redefinición, por lo que esta última parece reemplazar las condiciones only: anteriores.

image

Como resultado, external_store_check ya no se ejecuta para generate_presigned_put, permitiendo que un almacén local llegue a la ruta de código de carga directa.

Reproducción

  1. Configura una instancia de Discourse con almacenamiento de carga local.
  2. Asegúrate de que:
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true
  1. Intenta cargar un archivo.
  2. Observa el NoMethodError desde FileStore::LocalStore.

Una verificación del estado en la consola se puede hacer con:

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

Solución sugerida

Las registraciones de callbacks no deberían sobrescribirse entre sí.

Por ejemplo, incluye las acciones de carga segura en la callback existente external_store_check:

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
  ]

Alternativamente, usa un método de callback separado para las acciones de carga segura.

También podría ser valioso agregar validaciones para que enable_direct_s3_uploads no pueda habilitarse mientras las cargas S3/externas estén deshabilitadas.

Solución temporal

Para sitios que utilizan almacenamiento de carga local:

SiteSetting.enable_direct_s3_uploads = false

previene el error.

2 Me gusta

Gracias, debería estar corregido según:

1 me gusta