ابدأ برباعية الوصول
كل عملية محمية يمكن وصفها بأربع قيم: الفاعل، والمستأجر، والإجراء، والمورد. إن كانت إحداها مجهولة، فالنظام لا يملك بعد ما يكفي ليسمح بالعملية.
هذه الرباعية البسيطة تمنع خطأً شائعًا: أن تتحقق من أن المستخدم يملك الصلاحية في مكان ما، ثم تحمّل موردًا يخص مستأجرًا آخر. الصلاحية والملكية تُقيَّمان معًا.
public sealed record AccessRequest(
Guid ActorId,
Guid TenantId,
string Action,
string ResourceType,
Guid? ResourceId); مدخلات القرار حدّد المستأجر من سياق موثوق
معامل في المسار أو جسم الطلب قد يسمّي مستأجرًا، لكنه لا يثبت العضوية فيه. حدّد المستأجر النشط من العضوية بعد المصادقة، ثم قارن به أي معرّف مستأجر يأتي في الطلب.
العمل الإداري الذي يعبر المستأجرين يحتاج مسارًا صريحًا بسياسة وقواعد تدقيق منفصلة. إخفاؤه داخل محلّل المستأجر العادي يجعل فهم الكود العادي أصعب بكثير.
- أنشئ سياق مستأجر واحدًا لكل طلب بعد المصادقة.
- ارفض المستأجر المفقود أو الملتبس قبل أن يبدأ منطق العمل.
- مرّر معرّف المستأجر صراحةً إلى حدود التطبيق والوصول إلى البيانات.
- لا تقبل معرّف مستأجر من رسالة أو مهمة مجدولة دون أن تتحقق ممن أنشأ ذلك العمل.
افصل الأدوار عن الصلاحيات
الأدوار أداة إدارة، والصلاحيات هي الإجراءات التي يحميها التطبيق فعلًا. الكود الذي يسأل عن اسم دور تكثر فيه الاستثناءات، مثل «مدير أو مالك أو دعم فني». والكود الذي يسأل عن صلاحية يُبقي قرار العمل في مكان واحد.
استخدم أسماء صلاحيات ثابتة تصف إجراءً، مثل invoice.read أو member.invite. واربط أدوار المستأجر بهذه الصلاحيات في الإعدادات أو في البيانات. التطبيق يفحص الصلاحية، وواجهة الإدارة تقرر أي الأدوار تنالها.
في Shield خمسة أنواع من المستخدمين: الإدارة، والشركة، والمنشأة، والحارس، والأمن الوقائي. لكل نوع مجموعة صلاحيات خاصة به، وكل شركة جديدة تحصل على دور مسؤول خاص بها يحمل مجموعة صلاحيات الشركات.
- السياسة العامة. قواعد مثل حالة الحساب، والعضوية في المستأجر، ووصول فريق الدعم، تنطبق قبل تحميل أي مورد. في Amlaak يبقى الحساب مقيّدًا إن كان دوره يستطيع إدارة المستخدمين أو الأدوار أو التكاملات، أو تصدير بيانات شخصية، حتى يفعّل التحقق الثنائي TOTP.
- سياسة الصلاحية. هل يجوز للفاعل أن يحاول هذا الإجراء في المستأجر النشط.
- سياسة المورد. الملكية، أو الحالة، أو القسم، أو غيرها من الحقائق التي لا تُفحص إلا مقابل المورد نفسه. في Question Banking توصل صلاحية ReadQuestionBank الطلب إلى البنك فقط، ثم يقرر البنك: يدخل مالكه، ويدخل الجميع إن كان عامًا، ويدخل من يحمل مشاركة لم تنتهِ. وكل من عدا ذلك يتلقى «غير موجود».
الاستعلامات جزء من حدود الأمان
فحص الصلاحيات ثم استعلام غير مقيّد بالمستأجر ما زال تسريبًا للبيانات. ضع شرط المستأجر في استعلام قاعدة البيانات الذي يحمّل المورد أو يغيّره. وهذا يعالج أيضًا حالة معرّف مورد صحيح يخص مستأجرًا آخر.
فلاتر الاستعلام العامة في Entity Framework Core تقلل التكرار، لكن لا ينبغي أن تصير سحرًا خفيًا. استعلامات SQL المباشرة، والمهام الخلفية، واستعلامات الإدارة، وتعطيل الفلاتر، كلها تحتاج مراجعة متعمدة. والكتابات الحساسة أسلم في دالة مستودع أو تطبيق تطلب معرّف المستأجر صراحةً، بدل أن تقرأه من قيمة ثابتة عامة.
Zotic يُخرج تقييد المستأجر من الاستعلامات الفردية: فلتر استعلام واحد يُسجَّل لكل كيان يرث من صنفه الأساسي متعدد المستأجرين، ومعترض (interceptor) عند SaveChanges يكتب المستأجر على كل صف جديد. أما CAS فيحدد النطاق بالشخص لا بالمستأجر: قائمة دورات الطبيب لا تضم إلا الدورات التي يدرّسها، وقائمة موظف الاستقبال لا تضم إلا دورات الأطباء المسند إليهم.
var order = await db.Orders
.SingleOrDefaultAsync(
x => x.Id == request.OrderId
&& x.TenantId == tenant.Id,
cancellationToken); حدّد نطاق المورد داخل الاستعلام احمِ مسار الكتابة كله
ما تُظهره الواجهة أو تخفيه ليس تفويضًا. إخفاء زر يساعد المستخدم على فهم المنتج، لكن القرار الأخير للواجهة البرمجية أو لمعالج الأمر. والقاعدة نفسها تنطبق على الاستيراد الجماعي، ومستهلكي الرسائل، والمهام المجدولة، والأدوات الداخلية.
في المهام الخلفية، احفظ المستأجر ومن بدأ العمل مع المهمة. وعند التنفيذ، أعد بناء سياق تفويض محدود، أو استخدم هوية خدمة باسم واضح. لا تدع عاملًا يرث وصولًا واسعًا لمجرد أنه يعمل داخل الخادم.
- حدّد الفاعل والمستأجر.
- افحص صلاحية الإجراء.
- حمّل المورد داخل حدود المستأجر.
- طبّق قواعد المورد نفسه.
- غيّر الحالة واكتب حدث التدقيق في عملية التطبيق نفسها.
التخزين المؤقت للصلاحيات يحتاج خطة إبطال
تخزين مجموعة الصلاحيات مؤقتًا يوفر قراءات متكررة من قاعدة البيانات، لكن مفتاحًا مبنيًا على معرّف المستخدم وحده غير آمن في نظام متعدد المستأجرين. أدخل في المفتاح المستأجر، ونسخة السياسة، أو نسخة إسناد الأدوار، أو أي قيمة أخرى تتغير حين يُعدَّل الوصول.
قرّر كم يجب أن يكون سحب الصلاحية سريعًا. انتهاء قصير قد يكفي لقراءة قليلة المخاطر، أما الإجراءات الحساسة فقد تحتاج فحصًا جديدًا أو إبطالًا مباشرًا. هذا قرار منتج وأمان، لا مجرد إعداد للتخزين المؤقت.
Amlaak يحفظ صلاحيات المستخدم في الذاكرة ثلاث دقائق، بمفتاح مكوّن من معرّف المستخدم وبصمة (hash) لختم الأمان (security stamp) في رمز دخوله. تغيير الدور يبدّل الختم، ورمز الدخول الذي يحمل ختمًا قديمًا يُرفض، فلا يقرأ رمز جديد مدخلًا قديمًا أبدًا.
permissions:{tenantId}:{actorId}:{assignmentVersion} شكل أكثر أمانًا لمفتاح التخزين المؤقت دقّق القرارات المهمة
حدث التدقيق المفيد يقول من حاول ماذا، وفي أي مستأجر، وعلى أي مورد، وهل سُمح له. ويسجل أيضًا السياسة أو الصلاحية المعنية. ولا ينسخ إلى السجلات رموز الدخول، ولا أجسام الطلبات، ولا الحقول الحساسة.
محاولات الوصول المرفوضة قد تكون كثيرة، فاجمع الرفض الروتيني في أرقام، واحتفظ بسجلات مفصلة للعمليات الحساسة. فريق الدعم يحتاج سياقًا يكفي للتحقيق، دون أن يتسلم البيانات المحمية نفسها.
Amlaak يضع على كل عملية يدققها وسمًا (attribute) يسمّي الإجراء، مثل User.Block أو Inquiry.Export. وطبقة في آخر مسار MediatR تكتب السجل بعد نجاح المعالج فقط، وتخفي قبل الحفظ أي حقل يوحي اسمه بكلمة مرور أو رمز دخول أو سر.
اختبر الحدود، لا المسارات السعيدة فقط
اختبارات التفويض يجب أن تثبت أن الحد يصمد حين تكون المعرّفات صحيحة لكنها تخص مكانًا آخر. والاختبار الذي لا يستخدم إلا معرّفات غير موجودة لا يكشف القراءة عبر المستأجرين.
| الحالة | النتيجة المتوقعة |
|---|---|
| عضو، في المستأجر الصحيح، بصلاحية مسموحة، على مورد يملكه | سماح |
| عضو، في المستأجر الصحيح، بلا الصلاحية | رفض دون تحميل بيانات محمية |
| عضو، من مستأجر آخر، بمعرّف مورد صحيح | رفض أو «غير موجود»، بثبات |
| سُحبت الصلاحية بعد إنشاء مدخل في التخزين المؤقت | رفض خلال مهلة السحب المتفق عليها |
| مهمة خلفية أُزيل وصول من بدأها | اتباع سياسة الخدمة أو سياسة البادئ الموثقة |
| مسار الإدارة يُستخدم عبر نقطة مستأجر عادية | رفض |
المملّ هو الهدف
نقطة API جديدة لا ينبغي أن تُلزم كاتبها باختراع تسلسل تفويض. أعطها سياق مستأجر واحدًا، واسم صلاحية واحدًا، واستعلام مورد واحدًا محصورًا بالمستأجر، ومكانًا واحدًا للقرار الأخير. عندها يبحث المراجعون عن خطوة ناقصة، بدل أن يفككوا نموذج أمان مختلفًا في كل ميزة.