直接 S3 上传并启用本地存储时,未抛出拒绝错误,而是引发 NoMethodError

我在一名用户报告后自行发现并解决了此问题。我使用 AI 生成了 bug 报告,因为我比较懒,而且它做得比我好——在这个过程中,它还发现了一个核心 bug,我随后进行了手动验证。

虽然问题不大,但检查这种模式是否在其他地方也导致了问题可能是有意义的。

一切始于有人勾选了 enable_direct_s3_uploads,但实际上并未配置和启用 S3,这导致所有上传功能失效,后台返回 500 错误。我认为这个 500 错误值得深入调查。

摘要

enable_direct_s3_uploads 已启用,但当前活动的存储后端为本地存储时,尝试上传可能会引发以下错误:

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'

这似乎是由无效的设置状态与被覆盖的 before_action 回调组合导致的。

配置

有问题的状态实际上是:

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

对于本地存储,Discourse.store 没有实现:

signed_request_for_temporary_upload

这是符合预期的,因为该操作仅对外部/S3 存储有意义。

预期行为

如果直接 S3 上传已启用,但当前存储后端为本地存储,请求应由 external_store_check 干净地拒绝。

理想情况下,还应防止或验证这种无效的网站设置组合。

实际行为

请求到达了 ExternalUploadManager.create_direct_upload,该方法在 FileStore::LocalStore 实例上调用了:

store.signed_request_for_temporary_upload(...)

从而导致 NoMethodError

可能的原因

ExternalUploadHelpers 注册了以下回调:

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

当存储后端为本地时,这应该能阻止请求到达外部上传代码。

然而,UploadsController 随后使用相同的过滤方法注册了另一个回调:

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

Rails 将重复注册相同的回调视为重新定义,因此后者似乎替换了早期的 only: 条件。

image

结果是,external_store_check 不再针对 generate_presigned_put 运行,导致本地存储进入了直接上传的代码路径。

复现步骤

  1. 配置一个使用本地上传存储的 Discourse 实例。
  2. 确保:
SiteSetting.enable_s3_uploads = false
SiteSetting.enable_direct_s3_uploads = true
  1. 尝试上传文件。
  2. 观察来自 FileStore::LocalStoreNoMethodError

可以通过控制台检查状态:

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

建议的修复方案

回调注册不应相互覆盖。

例如,将安全上传操作包含在现有的 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
  ]

或者,为安全上传操作使用单独的回调方法。

此外,增加验证以防止在 S3/外部上传禁用的情况下启用 enable_direct_s3_uploads 也是值得考虑的。

临时解决方案

对于使用本地上传存储的网站:

SiteSetting.enable_direct_s3_uploads = false

可以防止此错误。

2 个赞

谢谢,应该已经按照以下方式修复了:

1 个赞