Odoo Security Layers
Odoo has three layers of access control, each more granular than the last:
- Security Groups — Who can access which menus and features
- Model Access (ir.model.access) — CRUD permissions per model per group
- Record Rules (ir.rule) — Which specific records a user can see/edit
Security Groups
Groups define roles: Sales User, Sales Manager, Accounting User, etc. Users belong to one or more groups.
<!-- Define a custom group -->
<record id="group_equipment_user" model="res.groups">
<field name="name">Equipment User</field>
<field name="category_id" ref="base.module_category_operations"/>
</record>
<record id="group_equipment_manager" model="res.groups">
<field name="name">Equipment Manager</field>
<field name="category_id" ref="base.module_category_operations"/>
<field name="implied_ids" eval="[(4, ref('group_equipment_user'))]"/>
</record>implied_ids creates inheritance: Equipment Manager automatically gets all Equipment User permissions.
Model Access (ir.model.access.csv)
Controls CRUD at the model level — can this group create/read/update/delete records on this model?
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_equip_user,equipment.user,model_equipment_item,group_equipment_user,1,1,1,0
access_equip_manager,equipment.manager,model_equipment_item,group_equipment_manager,1,1,1,1Users can read/write/create but NOT delete. Managers can do everything.
Record Rules (ir.rule)
Controls which records a user can see — row-level security.
<!-- Users see only their department's equipment -->
<record id="rule_equipment_department" model="ir.rule">
<field name="name">Equipment: own department</field>
<field name="model_id" ref="model_equipment_item"/>
<field name="groups" eval="[(4, ref('group_equipment_user'))]"/>
<field name="domain_force">
[('department_id.member_ids.user_id', 'in', [user.id])]
</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- Managers see all equipment (global rule) -->
<record id="rule_equipment_manager" model="ir.rule">
<field name="name">Equipment: manager sees all</field>
<field name="model_id" ref="model_equipment_item"/>
<field name="groups" eval="[(4, ref('group_equipment_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>Global vs Group Rules
| Type | groups field | Behavior |
|---|---|---|
| Global rule | Empty (no groups) | AND — applies to everyone, restricts access |
| Group rule | Has groups | OR — within same group, rules are combined with OR |
Multi-Company Record Rule
<record id="rule_equipment_multi_company" model="ir.rule">
<field name="name">Equipment: multi-company</field>
<field name="model_id" ref="model_equipment_item"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
</record>This is a global rule (no groups) — it restricts ALL users to seeing only their company's records.
Field-Level Access
<!-- Only managers can see purchase cost -->
<field name="purchase_cost" groups="equipment_tracking.group_equipment_manager"/>The groups attribute on fields works in both Python models and XML views.
Common Patterns
"Own Records Only" Rule
[('user_id', '=', user.id)]"Same Company" Rule
[('company_id', 'in', company_ids)]"Active Records Only" Rule
[('active', '=', True)]Debugging Access Issues
# Check what groups a user belongs to
user.groups_id.mapped('full_name')
# Check model access
self.env['ir.model.access'].check('equipment.item', 'read')
# Check record rules (returns domain)
self.env['ir.rule']._compute_domain('equipment.item', 'read')
# Bypass security (use carefully!)
self.env['equipment.item'].sudo().search([])Common Mistakes
- No ir.model.access for custom model → AccessError for all users
- Global rule too restrictive → Even admin cannot see records
- Using sudo() to fix access issues → Masks the real problem, creates security holes
- Forgetting company_id rule → Data leaks between companies
DeployMonkey Security
DeployMonkey's AI agent understands Odoo security. Ask it to diagnose access errors, generate security rules, or audit your custom module's permissions.