تطوير أودو

أمان الوصول في أودو — ir.model.access.csv وقواعد السجلات (أودو 16–19)

مرجع شامل للمطوّر حول نظام الأمان ثنائي الطبقة في أودو: صلاحيات CRUD على مستوى النموذج عبر ir.model.access.csv والتصفية على مستوى السجل عبر ir.rule. موثق لأودو 18 و19.

iWesabe Editorial Team١ مايو ٢٠٢١9 دقائق للقراءة

يُطبّق أودو التحكم في الوصول على طبقتين مستقلتين. الطبقة الأولى — الوصول على مستوى النموذج — تحدد مجموعات المستخدمين القادرين على تنفيذ عمليات الإنشاء والقراءة والكتابة والحذف (CRUD) على نموذج ما ككل. الطبقة الثانية — قواعد السجلات — تُقيّد بشكل أكبر السجلات المحددة التي يمكن للمستخدم رؤيتها أو تعديلها ضمن النموذج. يجب تهيئة كلتا الطبقتين بشكل صحيح لوحدة آمنة. يُعدّ غياب التحكم في الوصول على نموذج مخصص أحد أكثر أسباب استثناءات `AccessError` وتسرب البيانات غير المقصود شيوعًا.

طبقتا الأمان

أمان وصول أودو — نظرة عامة على الطبقتين
الطبقةالآليةالملف / النموذجالتحكم فيالتقييم
1 — الوصول إلى النموذجir.model.access (ACL)security/ir.model.access.csvهل يمكن للمجموعة X تنفيذ CRUD على النموذج Y أصلًا؟يُفحص أولًا. إذا رُفض هنا، لا تُقيَّم قواعد السجلات أبدًا.
2 — الوصول إلى السجلir.rule (قواعد السجلات)security/*.xml (سجلات ir.rule)أي سجلات محددة يمكن للمجموعة X قراءتها/كتابتها/إنشاؤها/حذفها؟يُفحص ثانيًا. يُطبَّق مرشح النطاق على الاستعلام.

الجزء 1 — الوصول إلى النموذج: ir.model.access.csv

يجب أن يحتوي كل نموذج مخصص على سجل `ir.model.access` واحد على الأقل، وإلا يُرفض كل وصول إليه افتراضيًا. النهج القياسي هو تعريف هذه السجلات في ملف CSV في `security/ir.model.access.csv` والإعلان عنه في `__manifest__.py` تحت مفتاح `data`.

ir.model.access.csv — مرجع الأعمدة

أعمدة ir.model.access.csv — أودو 16–19
العمودمطلوبالصيغة / مثالملاحظات
idنعمaccess_my_model_userمعرّف خارجي لصف ACL هذا. يجب أن يكون فريدًا في الوحدة. الاتفاقية: access_<model>_<group_suffix>.
nameنعمaccess_my_model_userتسمية قابلة للقراءة من قِبل الإنسان. يمكن أن تطابق id. تظهر في واجهة حقوق الوصول.
model_id:idنعمmodel_my_module_my_modelالمعرّف الخارجي للنموذج. الصيغة: model_ + اسم النموذج مع استبدال النقاط بشرطات سفلية. مثلًا: sale.order → model_sale_order.
group_id:idلاbase.group_userالمعرّف الخارجي للمجموعة. اتركه فارغًا لمنح الوصول لجميع المستخدمين (بدون قيود مجموعة). القيم الشائعة: base.group_user (المستخدمون الداخليون)، base.group_system (المسؤول).
perm_readنعم0 أو 11 = السماح بالقراءة. مطلوب لأي عرض قائمة أو نموذج للفتح.
perm_writeنعم0 أو 11 = السماح بالكتابة (تحرير السجلات الموجودة).
perm_createنعم0 أو 11 = السماح بالإنشاء (سجلات جديدة). يمكن للمستخدم الذي لديه صلاحية الكتابة دون الإنشاء التحرير لكن ليس إضافة السجلات.
perm_unlinkنعم0 أو 11 = السماح بالحذف. اضبطه على 0 للمستخدمين العاديين؛ احتفظ به للمديرين.

ir.model.access.csv — مثال كامل

text
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink

# Internal users — read + write + create, no delete
access_project_task_user,access_project_task_user,model_project_task,base.group_user,1,1,1,0

# Managers — full CRUD
access_project_task_manager,access_project_task_manager,model_project_task,project.group_project_manager,1,1,1,1

# Portal users — read only
access_project_task_portal,access_project_task_portal,model_project_task,base.group_portal,1,0,0,0

تعريف ملفات الأمان في __manifest__.py

python
{
    'name': 'My Module',
    'depends': ['base'],
    'data': [
        # Security files must come BEFORE views in the data list
        # so that groups are defined before views reference them.
        'security/groups.xml',           # custom group definitions
        'security/ir.model.access.csv',  # model-level ACL
        'security/record_rules.xml',     # record rules (ir.rule)
        # Views and other data come after:
        'views/my_model_views.xml',
    ],
}

الجزء 2 — المجموعات (res.groups)

المجموعات هي الحلقة الرابطة بين المستخدمين وحقوق الوصول. ينتمي المستخدم إلى مجموعة واحدة أو أكثر؛ وصفوف ACL وقواعد السجلات مرتبطة بمجموعات. يمكنك تعريف مجموعات مخصصة لوحدتك أو إعادة استخدام مجموعات أودو المضمّنة. يمكن للمجموعات أن ترث من مجموعات أخرى — يحصل المستخدم في مجموعة فرعية تلقائيًا على جميع صلاحيات وصول المجموعة الأم.

xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <!-- Define a module-specific group category -->
    <record id="module_category_my_module" model="ir.module.category">
        <field name="name">My Module</field>
        <field name="sequence">100</field>
    </record>

    <!-- User group — basic access -->
    <record id="group_my_module_user" model="res.groups">
        <field name="name">User</field>
        <field name="category_id" ref="module_category_my_module"/>
        <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
    </record>

    <!-- Manager group — inherits user group, adds extra rights -->
    <record id="group_my_module_manager" model="res.groups">
        <field name="name">Manager</field>
        <field name="category_id" ref="module_category_my_module"/>
        <field name="implied_ids" eval="[(4, ref('group_my_module_user'))]"/>
        <field name="users" eval="[(4, ref('base.user_admin'))]"/>
    </record>
</odoo>

الجزء 3 — قواعد السجلات (ir.rule)

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

قواعد السجلات العالمية مقابل الخاصة بالمجموعات — الفرق في المنطق
نوع القاعدةحقل groupsالمنطق مع قواعد أخرىحالة الاستخدام
قاعدة عالميةفارغ (بدون مجموعات)AND — تُطبَّق دائمًا، تُدمج مع جميع القواعد الأخرى باستخدام ANDفرض قيد على جميع المستخدمين — مثلًا عزل الشركة في متعدد الشركات
قاعدة خاصة بمجموعةمجموعة واحدة أو أكثرOR — بين القواعد لنفس المجموعة؛ AND مع القواعد العالميةمنح وصول إضافي لمجموعة محددة — مثلًا المديرون يرون جميع السجلات

مرجع حقل XML لـ ir.rule

مرجع حقل XML لـ ir.rule
الحقلالنوعمطلوبالوصف
nameCharنعمتسمية قابلة للقراءة تظهر في واجهة قواعد السجلات.
model_idMany2one → ir.modelنعمالنموذج الذي تنطبق عليه هذه القاعدة. الإشارة إليه بـ ref='<module>.<model_external_id>'.
domain_forceChar (سلسلة نطاق)نعممرشح النطاق. السجلات المطابقة لهذا النطاق يمكن الوصول إليها؛ الأخرى غير مرئية للمجموعة المتأثرة.
groupsMany2many → res.groupsلاالمجموعات التي تنطبق عليها هذه القاعدة. اتركه فارغًا للقاعدة العالمية.
perm_readBooleanلا (افتراضي True)تطبيق هذه القاعدة على عمليات القراءة.
perm_writeBooleanلا (افتراضي True)تطبيق هذه القاعدة على عمليات الكتابة.
perm_createBooleanلا (افتراضي True)تطبيق هذه القاعدة على عمليات الإنشاء.
perm_unlinkBooleanلا (افتراضي True)تطبيق هذه القاعدة على عمليات الحذف.

أنماط قواعد السجلات الشائعة

xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>

  <!-- Pattern 1: Own-records rule
       Users can only read/write records they created.
       Global rule — applies to all users. -->
  <record id="rule_my_model_own_records" model="ir.rule">
    <field name="name">My Model: Own Records Only</field>
    <field name="model_id" ref="model_my_module_my_model"/>
    <field name="domain_force">[('create_uid', '=', user.id)]</field>
    <!-- No groups → global rule, applies to everyone -->
  </record>

  <!-- Pattern 2: Manager bypass
       Managers can see ALL records (overrides the own-records rule above
       for members of the manager group via OR logic). -->
  <record id="rule_my_model_manager_all" model="ir.rule">
    <field name="name">My Model: Manager Sees All</field>
    <field name="model_id" ref="model_my_module_my_model"/>
    <field name="domain_force">[(1, '=', 1)]</field>
    <field name="groups" eval="[(4, ref('my_module.group_my_module_manager'))]"/>
  </record>

  <!-- Pattern 3: Multi-company isolation
       Global rule — users can only see records of their own company.
       Combining allowed_company_ids ensures multi-company switching works. -->
  <record id="rule_my_model_company" model="ir.rule">
    <field name="name">My Model: Company Isolation</field>
    <field name="model_id" ref="model_my_module_my_model"/>
    <field name="domain_force">
      ['|',
        ('company_id', '=', False),
        ('company_id', 'in', company_ids)]
    </field>
    <!-- Global rule — no groups field -->
  </record>

</odoo>

استكشاف الأخطاء

أخطاء أمان الوصول الشائعة وحلولها
الخطأ / الأعراضالسبب المحتملالحل
AccessError: ليس لديك الحق في الوصول إلى كائنات من نوع <model>لا يوجد صف ir.model.access لهذا النموذج ومجموعة المستخدمأضف صفًا إلى ir.model.access.csv للنموذج والمجموعة الصحيحة، ثم قم بترقية الوحدة.
قائمة السجلات فارغة أو لا يمكن فتح سجل معين — لا يظهر خطأنطاق domain_force لقاعدة سجل يُصفّي السجلات التي يتوقع المستخدم رؤيتهافي وضع المطوّر انتقل إلى الإعدادات → التقني → الأمان → قواعد السجلات وتحقق من القواعد على النموذج. اختبر النطاق في صدفة Python: self.env['my.model'].search([]).
External ID not found: <module>.group_xyz أثناء تثبيت الوحدةملف groups.xml مدرج بعد ir.model.access.csv في قائمة data في __manifest__.pyضع groups.xml قبل ir.model.access.csv في قائمة data — يجب تحميل المجموعات قبل صفوف ACL التي تستند إليها.
المستخدم المسؤول يتجاوز قواعد السجلات — هذا متوقع وليس خطأيتجاوز المستخدم الفائق في أودو (المسؤول، uid=1) جميع قواعد السجلات حسب التصميملاختبار قواعد السجلات، استخدم مستخدمًا غير مسؤول. في اختبارات الوحدة، استخدم self.env['my.model'].with_user(regular_user).search([]) لمحاكاة السياق غير المسؤول.
نطاق [(1, '=', 1)] في قاعدة المدير لا يزال يحجب بعض السجلاتقاعدة عالمية تُطبّق منطق AND فوق قاعدة OR للمدير، مما يُضيّق النتائجراجع جميع القواعد العالمية (بدون مجموعات مضبوطة) على النموذج — هذه تُطبّق AND دائمًا. إذا كانت القاعدة العالمية مقيّدة جدًا، حدّد نطاقها لمجموعات غير المديرين أو أزلها من النطاق الفعّال للمدير.

ملاحظات الإصدار

أمان الوصول عبر إصدارات أودو
إصدار أودوالتغييرات الرئيسية المؤثرة على أمان الوصول
أودو 16لا توجد تغييرات كاسرة على ملف CSV لـ ACL أو ir.rule. المتغير company_ids متاح في domain_force لقواعد متعددة الشركات. إعادة تسمية `<list>` لا تؤثر على الأمان.
أودو 17لا توجد تغييرات كاسرة لواجهة API لـ ACL أو قواعد السجلات. يُشدّد أودو 17 التحقق من سلاسل domain_force — النطاقات غير الصحيحة التي كانت تفشل بصمت سابقًا قد تُثير الآن أخطاءً.
أودو 18لا توجد تغييرات كاسرة. صيغة ir.model.access.csv لم تتغير. واجهة API لقواعد السجلات لم تتغير. إضافة مجموعات base جديدة لتدفقات الموقع/البوابة — تحقق من وراثة المجموعات عند ترقية الوحدات بقواعد البوابة.
أودو 19لا توجد تغييرات كاسرة لـ ACL أو قواعد السجلات. تحسينات أداء ORM تُقلل من عبء تقييم domain_force على الجداول الكبيرة.

تحتاج تدقيقًا أمنيًا أو تحكمًا مخصصًا في الوصول لوحدات أودو؟

يُصمّم مطورونا المعتمدون من أودو ويُدقّقون التحكم في الوصول للشركات السعودية — بما في ذلك البيانات المالية الحساسة لـ ZATCA وسجلات الموارد البشرية المتوافقة مع PDPL وعزل الشركات المتعددة عبر هياكل المجموعات.

واتساب

الأسئلة الشائعة

ماذا يحدث إذا قمت بتثبيت وحدة بدون ملف ir.model.access.csv؟
سيتلقى أي مستخدم ليس المستخدم الفائق في أودو (المسؤول، uid=1) استثناء `AccessError` عند محاولة قراءة أو كتابة أو إنشاء أو حذف سجلات على نموذجك المخصص. يتجاوز المستخدم الفائق جميع فحوصات ACL حسب التصميم. أضف دائمًا على الأقل صف ACL للقراءة فقط لـ `base.group_user` إذا كان يجب أن يكون النموذج متاحًا للمستخدمين العاديين.
هل يمكنني منح الوصول لجميع المستخدمين دون تعيين مجموعة محددة؟
نعم — اترك عمود `group_id:id` فارغًا في صف CSV. ينطبق صف ACL بدون مجموعة على كل مستخدم مصادق عليه. استخدم هذا بحذر: يمنح جميع المستخدمين الداخليين ومستخدمي البوابة والمستخدمين العامين نفس مستوى الوصول. للوصول عبر البوابة أو العام، من الأفضل عادةً تعيين `group_id:id` على `base.group_portal` أو `base.group_public` صراحةً.
ما الفرق بين perm_write وperm_create في ACL؟
يتحكم `perm_write` في ما إذا كان بإمكان المستخدم تعديل حقول سجل موجود (استدعاء ORM `write()`). يتحكم `perm_create` في ما إذا كان بإمكان المستخدم إنشاء سجلات جديدة (استدعاء ORM `create()`). يمكن للمستخدم الذي لديه `perm_write=1` و`perm_create=0` فتح السجلات الموجودة وتحريرها لكن لا يمكنه حفظ سجل جديد. كلاهما ضروريان للمستخدم الذي يجب أن يكون قادرًا على الإنشاء والتعديل. `perm_unlink` (الحذف) مستقل عنهما.
كيف تتفاعل قواعد السجلات العالمية مع القواعد الخاصة بالمجموعات؟
تُطبَّق القواعد العالمية (بدون مجموعات) دائمًا بمنطق AND — يجب أن يستوفيها كل مستخدم. تُطبَّق القواعد الخاصة بالمجموعات بمنطق OR بين القواعد لنفس المجموعة. النطاق الفعّال النهائي للمستخدم هو: (القاعدة العالمية 1 AND القاعدة العالمية 2 AND ...) AND (قاعدة المجموعة A OR قاعدة المجموعة B). هذا يعني أن القاعدة العالمية التي تُقيّد بـ company_id لا يمكن تجاوزها بقاعدة مجموعة — تُضيّق مجموعة النتائج بشكل دائم لجميع المستخدمين.
كيف أكتب قاعدة سجل تسمح للمستخدمين برؤية سجلات شركتهم فقط في إعداد متعدد الشركات؟
استخدم `company_ids` (وليس `user.company_id.id`) في domain_force لدعم تبديل الشركة المتعدد. `company_ids` هي قائمة بجميع معرّفات الشركات التي لدى المستخدم وصول نشط إليها. النطاق `[('company_id', 'in', company_ids)]` يعرض سجلات جميع الشركات التي تبدّل إليها المستخدم حاليًا. أضف `('company_id', '=', False)` كبديل OR إذا كان النموذج يحتوي على سجلات غير خاصة بشركة: `['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]`.
هل يمكن لقواعد السجلات تقييد الحقول التي يراها المستخدم، وليس فقط السجلات؟
لا. قواعد السجلات تُصفّي فقط السجلات المُعادة — لا تُقيّد رؤية الحقول. التحكم في الوصول على مستوى الحقل في أودو يُعالَج بطريقة مختلفة: استخدم سمة `groups` على عنصر `` في XML العرض لإخفاء حقل عن المستخدمين غير المنتمين لمجموعة محددة، أو استخدم تعبيرات `invisible` للإخفاء المشروط. للأمان الحقيقي على مستوى الحقل (منع الوصول من جهة الخادم)، استخدم تجاوزًا مخصصًا لـ `_fields_view_get()` أو قيود الوصول إلى `ir.model.fields` — كلاهما أنماط متقدمة تتجاوز قواعد السجلات القياسية.
iWesabe Editorial Team

iWesabe Editorial Team

رؤى عملية حول Odoo ERP وامتثال ZATCA والعمليات الرقمية للشركات السعودية — بقلم فرق الاستشارات والمالية والهندسة في iWesabe.

عن iWesabe

مقالات ذات صلة