هندسة
لماذا يعيش كل فحص صلاحيات في نظامنا داخل ملف واحد
By Clasify Team · · 5 min read
معظم الأنظمة الداخلية تحصل على صلاحياتها بالطريقة نفسها: مسار يريد منع المعلمين من تقرير الإدارة، فيكتب أحدهم فحصًا للدور هناك مباشرةً داخل المعالج. وينجح الأمر. ثم يتكرر في المعالج التالي، ثم الذي يليه، وخلال سنة يصبح الجواب الحقيقي على سؤال "من يستطيع رؤية هذا؟" موزّعًا على ثمانين ملفًا ولا أحد يقدر على نطقه.
والفشل الذي يعقب ذلك ليس دراماتيكيًا. إنه معالج واحد لم يصله الفحص — أحدثها غالبًا، ومكتوب على عجل في الأغلب — والخلل غير مرئي لأن الكود الغائب لا يبدو شيئًا على الإطلاق.
الاستدعاء الغائب مرئي، والمقارنة الغائبة ليست كذلك
هذه الملاحظة هي ما غيّر طريقتنا. فإن كان المسار يقرّر الصلاحيات بمقارنة دور في مكانه، فنسيان القرار يُنتج معالجًا بلا فحص، ولا شيء على الشاشة يلفت النظر. أما إن وجب على كل مسار استدعاء حارس بالاسم، فالنسيان يُنتج معالجًا ينقصه **استدعاء** — والاستدعاء الغائب شيء يستطيع المراجع أو الـ linter أو الاختبار رؤيته فعلًا.
لذلك القاعدة في كودنا مطلقة عن قصد:
إن لم يكن منطق الصلاحيات داخل وحدة الصلاحيات، فهو غير موجود.
المسارات لا تقارن دورًا مباشرةً أبدًا. كل قرار يمرّ عبر وحدة واحدة بسطح عام صغير ومسمّى: تحقّق من هوية المُستدعي، اشترط دورًا، اشترط صلاحية، أكّد ملكية سجل، أكّد أن المعلم يملك ما يمسّه، أكّد أنه مدير، أكّد أنه مشرف عام، أكّد أن الطالب مسجَّل. وكل ما عدا ذلك في الملف — مصفوفة الصلاحيات، ومُوحِّد الأدوار، ومُهيّئ الطلب، ومساعدات تحديد نطاق القوائم — بنية تحتية بُنيت عليها تلك الحرّاس، لا مدخل ثانٍ.
والقيمة ليست في أن الفحوص أفضل بمعزل؛ فأيٌّ منها يمكن كتابته في مكانه. القيمة أن هناك موضعًا واحدًا للقراءة، وموضعًا واحدًا للتغيير، وموضعًا واحدًا للتدقيق.
الأدوار أكثر فوضى من المخطط
كل نظام حقيقي يراكم فجوة بين أسماء الأدوار في المواصفة ونصوص الأدوار في قاعدة البيانات. نظامنا يخزّن `COMPANY_ADMIN` حيث قال التصميم الأصلي `ADMIN`.
والإصلاح المغري هو ترحيل يعيد التسمية. أما الأرخص والأأمن فهو مُوحِّد: تابع واحد يطوي النصوص المخزّنة والمرادفة إلى طبقة قياسية، ويعيد null لأي شيء لا يعرفه. والدور المجهول يعني لا دور، وهذا فشل مُغلق.
هذا التابع أثمن مما يبدو. فهو الموضع الوحيد في النظام الذي يملك رأيًا في معنى نصّ الدور، فتصبح أي إعادة تسمية مستقبلًا سطرًا واحدًا لا ترحيل بيانات. ولأنه يعيد null بدل أن يرمي أو يخمّن، فالقيمة التالفة أو غير المتوقعة تمنع الوصول بدل أن تطابق طبقةً بالمصادفة.
والطبقات نفسها تبقى قليلة عن عمد: مشرف عام عابر للمستأجرين، ومدير محصور في مستأجره، ومعلم محصور في حصصه وطلابه، وطالب محصور في نفسه وفيما هو مسجَّل فيه. ومعظم طلبات إضافة دور جديد هي في الحقيقة طلبات إضافة صلاحية جديدة، والمصفوفة تستوعبها دون إضافة طبقة يجب بعدها التفكير فيها في كل مكان.
وتفصيل يستحق النقل: الوصول العابر للمستأجرين لدى المشرف العام ليس ضمنيًا في الطبقة، بل يتطلب راية صريحة عند موضع الاستدعاء. فكونك الدور الأقوى شيء، وقراءتك بيانات الجميع افتراضيًا بصمت شيء آخر — وجعلها صريحة يعني أن كل قراءة عابرة للمستأجرين قابلة للبحث.
سجّل الرفض، لا الموافقة فقط
الجزء الذي تتخطاه الفرق هو أثر التدقيق، عادةً لأن النسخة البديهية منه — سجّل كل إجراء ناجح — تنتج سيلًا لا يقرؤه أحد.
اقلبها. نحن نكتب سجل تدقيق للصلاحيات حين **يُرفض** القرار، ويحمل المُستدعي، ودوره لحظة القرار، ومستأجره، والإجراء المُقيَّم، والمورد الذي حاول الوصول إليه، والوجهة، وعنوان الـ IP، وسببًا مقروءًا للبشر.
وهذا الجدول يستحق مكانه بثلاث طرق مختلفة، واحدة منها فقط أمنية:
- سؤال دعم — "لماذا لا يستطيع هذا المعلم فتح التقرير؟" — يُجاب عنه بصف يحوي السبب حرفيًا، بدل إعادة إنتاج جلسة المستخدم.
- النشرة السيئة تظهر كارتفاع مفاجئ في حالات الرفض لإجراء كان ينجح، وهي إشارة أبكر بكثير من تذكرة دعم.
- محاولة الاستكشاف الفعلية تبدو كمُستدعٍ واحد يولّد رفضًا عبر موارد كثيرة، وهذا شكل يمكن التنبيه عليه.
وتسجيل السبب نصًا لا رمزًا هو ما يجعل الاثنتين الأوليين تعملان. فالسبب يُكتب مرة واحدة، حيث يُتخذ القرار ويكون السياق معلومًا، ويظل مفهومًا بعد أشهر لشخص لم يقرأ ذلك التابع قط.
ماذا يشتري هذا، وبكم
الكلفة حقيقية: طبقة وساطة واحدة بين المسار وقراره، وقاعدة على الفريق الالتزام بها. والمهندسون الجدد يسألون: لماذا لا نفحص الدور مباشرةً؟ والجواب أن القاعدة لا تحمي الفحص، بل تحمي القدرة على إيجاد كل فحص لاحقًا.
وما تشتريه هو القدرة على الإجابة عن أسئلة يستحيل الإجابة عنها في أداة داخلية: من يصل إلى هذا السجل، وما الذي تغيّر حين نشرنا ذلك، ولماذا رُفض هذا الشخص تحديدًا في هذه اللحظة تحديدًا.
وإن كنت تبني النظام الذي يحلّ محل جدول البيانات، فهذا هو الجزء الذي يجب إتقانه مبكرًا. فتوحيد الصلاحيات رخيص في اليوم الأول، ومكلف حين تحاول تركيبه بعد أن صار لثمانين معالجًا رأيٌ خاص.
نبني أنظمة كهذه لفرق أخرى ضمن تطوير برمجيات مخصصة، وكيف بنينا Clasify يغطي المعمارية المحيطة.
كل المقالات