10.12. CORS وCSRF

إن CORS و CSRF هما الحمايتان على جانب المتصفح اللتان تحتاجهما كاميرا مفتوحة على الإنترنت إلى جانب HTTPS وتسجيل الدخول. ويتطلب كلٌّ منهما بضعة أسطر للإعداد. وتُعرِّف الأقسام أدناه المصطلح وتُظهر التكامل مع microdot.

10.12.1. ما يفعله CORS

إن مشاركة الموارد عبر الأصول المختلفة (CORS) هي آلية المتصفح التي تتيح للخادم أن يختار السماح لأصول أخرى محددة بقراءة استجاباته. فسياسة الأصل نفسه الافتراضية للمتصفح تحظر تلك القراءة: إذ لا يمكن لشيفرة JavaScript على https://example.com قراءة استجابات من https://yard-cam.example.com، لأن اختلاف المضيف يُعدّ أصلًا مختلفًا. وCORS هو الطريقة على جانب الخادم لمنح استثناءات لنظراء مختارين.

إذا كانت لوحة المعلومات تُقدَّم من الكاميرا نفسها، فإن كل طلب يكون من الأصل نفسه ولا يؤدي CORS أي شيء. وتظهر أهمية الإعداد عندما تكون لوحة المعلومات في مكان آخر -- عنوان URL عام مثل https://app.example.com يتحدث إلى كاميرا على https://yard-cam.example.com:

from microdot.cors import CORS

cors = CORS(
    app,
    allowed_origins=['https://app.example.com'],
    allow_credentials=True,
    max_age=86400,
)

إن allowed_origins هي قائمة الأصول المسموح لها بقراءة استجابات الكاميرا. أصل لوحة المعلومات فقط لا غير -- وليس * -- بحيث لا يستطيع موقع طرف ثالث قراءة استجابات الكاميرا عرضًا.

إن allow_credentials=True تتيح للطلبات عبر الأصول المختلفة أن تتضمن ملف ارتباط الجلسة، وهو ما تحتاجه لوحة المعلومات للبقاء مُسجَّلة الدخول عبر حدود الأصل.

إن max_age=86400 تُخبر المتصفح أنه يمكنه تخزين نتيجة الفحص التمهيدي (preflight) في ذاكرة التخزين المؤقت ليوم كامل. فالمتصفحات تطلق طلب OPTIONS إضافيًا قبل أي استدعاء عبر الأصول المختلفة يستخدم طرقًا غير GET/HEAD/POST أو يرسل ترويسات مخصصة؛ وتُقلِّص max_age تلك التكلفة إلى فحص تمهيدي واحد في اليوم لكل مسار.

10.12.2. ما يفعله CSRF

إن تزوير الطلب عبر المواقع (CSRF) هو الهجوم الذي تجعل فيه صفحة خبيثة متصفحَ المستخدم يطلق طلبًا مُصادَقًا عليه ضد خادم موثوق. وحتى مع وجود CORS، فإن عنصر <form> مخفيًا على evil.com يرسل POST إلى https://yard-cam.example.com/config سيصل إلى الكاميرا، وسيُرفِق المتصفح ملف ارتباط جلسة الكاميرا -- إذ تتبع ملفات الارتباط المضيف الوجهة، لا أصل الصفحة التي تُصدر الطلب -- فتعالج الكاميرا طلب POST بسرور كما لو كان من المالك.

ترفض حماية CSRF تلك الطلبات. ويضيف microdot.csrf.CSRF برمجية وسيطة تفحص ترويسة Sec-Fetch-Site على كل طلب مُغيِّر للحالة وترفض أي شيء غير مُصنَّف same-origin (أو قادم من أصل مسموح به في CORS):

from microdot.csrf import CSRF

CSRF(app, cors=cors)

إن تمرير نسخة cors يتيح للبرمجية الوسيطة استثناء أصل لوحة المعلومات المسموح به ضمنًا -- فلا تزال الكاميرا تقبل طلبات POST من لوحة المعلومات رغم أنها عبر أصول مختلفة.

تضبط المتصفحات الحديثة Sec-Fetch-Site تلقائيًا؛ ولا يتعين على الكاميرا فعل أي شيء على جانب العميل. أما المتصفحات الأقدم التي لا ترسل الترويسة، فإن قائمة السماح في CORS هي الفحص الاحتياطي.

10.12.3. إعفاء روابط الويب (webhooks)

إذا احتاجت الكاميرا إلى نقطة نهاية رابط ويب (webhook) لقبول طلبات POST من خدمة سحابية لطرف ثالث -- مثل استدعاء عكسي من مزوِّد الأرشفة -- فضع على المسار علامة @csrf.exempt لتسمح البرمجية الوسيطة بمروره. ويكون المعالج مسؤولًا عن التحقق من الطلب بطريقة أخرى -- عادةً رمز مصادقة الرسالة القائم على التجزئة (HMAC) على الحمولة، يُحسَب بسرّ مشترك بين الكاميرا والطرف الثالث، وهو ما يثبت أن الطلب جاء ممن يعرف السرّ. ولا تملك كاميرا الفناء الخلفي أيًا من ذلك، لكن المُزخرِف موجود عندما تحتاج إليه.

10.12.4. الأساس المؤلف من أربعة أسطر

بمجرد وجود HTTPS، فإن الحزمة المُوصى بها لأي نشر لكاميرا موجَّهة نحو الإنترنت هي:

Session(app, secret_key=SECRET,
        cookie_options={'http_only': True, 'secure': True})
login = Login()
cors = CORS(app, allowed_origins=[...], allow_credentials=True)
CSRF(app, cors=cors)

الجلسة وتسجيل الدخول في وقت سابق من هذا الفصل، وCORS وCSRF هنا، وHTTPS من الموضوع السابق. وتتراكم القطع الأربع فوق بعضها بعضًا وتبقى بعيدة عن طريق كل مسار.

أصبحت الكاميرا الآن آمنة لمواجهة الإنترنت المفتوح -- HTTPS، وتسجيل دخول، وCSRF، وCORS.