الرفع المباشر إلى S3 مع التخزين المحلي يثير NoMethodError بدلاً من الرفض

اكتشفت هذه المشكلة وحللتها بنفسي بعد أن أبلغ عنها أحد العملاء. استخدمت الذكاء الاصطناعي لتوليد تقرير الخطأ لأنني كنت كسولًا، ولأنه يؤدي المهمة بشكل أفضل مما أفعل - وقد اكتشف خطأً في الكود الأساسي أثناء ذلك، والذي قمت بتحقق منه يدويًا.

ليست مشكلة كبيرة، لكن قد يكون من المنطقي التحقق مما إذا كان هذا النمط يسبب مشاكل في أماكن أخرى أيضًا.

بدأ الأمر عندما قام شخص ما بتمكين 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، الذي يستدعي:

store.signed_request_for_temporary_upload(...)

على FileStore::LocalStore، مما يؤدي إلى ظهور 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. لاحظ ظهور NoMethodError من FileStore::LocalStore.

يمكن إجراء فحص للحالة عبر وحدة التحكم باستخدام:

[
  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
  ]

أو، استخدم طريقة استدعاء منفصلة لإجراءات الرفع الآمنة.

قد يكون من الجدير أيضًا إضافة تحقق بحيث لا يمكن تمكين enable_direct_s3_uploads بينما تكون عمليات الرفع إلى S3/الخارجية معطلة.

حل مؤقت

للمواقع التي تستخدم تخزين الرفع المحلي:

SiteSetting.enable_direct_s3_uploads = false

يمنع هذا الخطأ.

إعجابَين (2)

شكرًا لك، يجب أن يكون قد تم إصلاحه وفقًا لـ:

إعجاب واحد (1)