Se rendre au contenu

Odoo Security: Record Rules and Access Rights Tutorial 2026

27 avril 2026 par
Odoo Security: Record Rules and Access Rights Tutorial 2026
Odoo Skillz, Odoo Skillz
| Aucun commentaire pour l'instant

TL;DR

2
Security layers
4
Access permissions
100%
Data isolation
  • Access rights control model-level permissions
  • Record rules restrict data within models
  • Both must be configured together for complete security

Odoo's security system operates on two distinct levels, and confusing them is the most common cause of AccessError exceptions in custom modules. Access rights determine what a user can do with a model. Record rules determine which records they can access within that model. Get both right, and your multi-company deployment runs smoothly. Get either wrong, and users either see everything or nothing at all.

This guide covers the complete security configuration in Odoo, from basic CSV access rights to complex record rule domains, with practical examples you can apply directly to your implementation.

Access rights configuration interface

Understanding Odoo Security Layers

Odoo uses a two-layer security model that many implementers overlook. Understanding both layers is essential before writing any custom security rules.

Layer 1: Access Rights (ACLs) are defined in CSV files and control model-level permissions. They specify which groups can read, write, create, or delete records of a given model. Without an access right entry, no user can access the model at all.

Layer 2: Record Rules (ir.rule) filter the records available within a model. Even if a user has read access to the "Sale Order" model, a record rule can restrict them to only seeing their own orders, or only orders from their company.

Both layers work together. A user must pass the ACL check first, then the record rule check. If either fails, the access is denied.

Record rules filtering data

Configuring Access Rights with CSV Files

Access rights are defined in CSV files within your module's security directory. The file naming convention is ir.model.access.csv.

The CSV format requires five columns:

  • id: Unique identifier for the access rule
  • model_id:id: The model reference (e.g., model_project_project)
  • group_id:id: The user group (e.g., group_project_user)
  • perm_read: Read permission (1 = allowed, 0 = denied)
  • perm_write: Write permission
  • perm_create: Create permission
  • perm_unlink: Delete permission

A typical access rights file looks like this:

id,model_id:name,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_user,project.project,group_project_user,1,1,1,0
access_project_manager,project.project,group_project_manager,1,1,1,1

In this example, project users can read, write, and create projects but cannot delete them. Project managers have full permissions including deletion.

Key rule: If a user belongs to multiple groups, Odoo grants the union of all permissions. If any group grants write access, the user can write.

Multi-company security isolation

Creating Record Rules

Record rules are defined in XML files within your module's security directory. Each rule specifies a domain that filters which records a user group can access.

A basic record rule XML definition:

<record id="rule_project_user_own" model="ir.rule">
    <field name="name">Own Projects Only</field>
    <field name="model_id" ref="model_project_project"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('group_project_user'))]"/>
    <field name="perm_read" eval="True"/>
    <field name="perm_write" eval="True"/>
    <field name="perm_create" eval="False"/>
    <field name="perm_unlink" eval="False"/>
</record>

The domain_force field uses Odoo's domain syntax to filter records. Common patterns include:

  • User-based: [('user_id', '=', user.id)] - Only records assigned to the current user
  • Company-based: [('company_id', 'in', company_ids)] - Records from the user's allowed companies
  • Parent-child: [('parent_id.user_id', '=', user.id)] - Records where the parent is owned by the user
  • Global: [(1, '=', 1)] - All records (effectively no restriction)

Multi-Company Security Configuration

Multi-company deployments require careful security planning. Each company's data must be isolated while allowing designated users to access multiple companies.

The standard approach uses company_id domains on record rules:

<record id="rule_multi_company" model="ir.rule">
    <field name="name">Multi-Company Rule</field>
    <field name="model_id" ref="model_res_partner"/>
    <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
    <field name="global" eval="True"/>
</record>

This rule allows access to records that either have no company set (shared records) or belong to one of the user's allowed companies. The company_ids variable is automatically populated by Odoo based on the user's allowed company list.

Important: The global flag applies the rule to all users. Use it for system-wide restrictions like multi-company isolation. For group-specific rules, use the groups field instead.

Debugging security issues

Debugging Security Issues

When users report AccessError exceptions or missing records, follow this debugging checklist:

Step 1: Verify Access Rights

Check that the user's groups have ACL entries for the model. Go to Settings, Technical, Security, Access Rights and search for the model name. If no entry exists for the user's group, they cannot access the model at all.

Step 2: Check Record Rules

Review all active record rules for the model. Go to Settings, Technical, Security, Record Rules. Look for rules that might be overly restrictive. Multiple record rules on the same model are evaluated with OR logic for read access, meaning any rule that grants access is sufficient.

Step 3: Test with sudo()

Use sudo() temporarily in your code to bypass security checks and confirm the data exists:

# Test: can we see the record with elevated privileges?
record = self.env['model.name'].sudo().browse(record_id)
print(f"Record exists: {record.exists()}")

If the record exists with sudo() but not without it, the issue is in your record rules, not your data.

Step 4: Check Company Context

For multi-company issues, verify the user's allowed companies match the record's company:

allowed = self.env.companies
print(f"Allowed companies: {allowed.mapped('name')}")
print(f"Record company: {record.company_id.name}")

Best Practices for Security Configuration

  • Start restrictive: Grant minimum permissions and add access as needed
  • Test with real users: Create test users in each group and verify access from their perspective
  • Document rule interactions: When multiple rules apply to the same model, document how they combine
  • Avoid sudo in business logic: Use sudo() only for system operations, not in user-facing code
  • Review after upgrades: Odoo version updates may modify default security rules
Security best practices checklist

Key Takeaways

  • Two layers: ACLs control model access, record rules control data access
  • Both required: Missing ACLs block all access, missing record rules expose all data
  • Test thoroughly: Always verify security from the end user's perspective
  • Use sudo carefully: Only for system operations, never in user-facing logic
  • Document everything: Security rules are easy to break during upgrades

Frequently Asked Questions

What is the difference between access rights and record rules?

Access rights (ACLs) determine whether a user can access a model at all (read, write, create, delete). Record rules filter which specific records within that model the user can see or modify.

How do multiple record rules combine?

Multiple record rules on the same model are combined with OR logic for read access. If any rule grants access to a record, the user can read it. For write, create, and delete, all applicable rules must allow the operation.

Can I bypass record rules in custom code?

Yes, using sudo() bypasses all security checks. However, this should only be used for system-level operations like background jobs. Never use sudo() in user-facing code as it creates security vulnerabilities.

How do I make a record visible to all users?

Set the company_id field to False (empty) on the record. Most multi-company record rules include a condition that allows access to records with no company set, making them globally visible.

Why do I get AccessError after creating a new group?

New groups start with no access rights. You must add CSV entries granting access to each model the group needs. Without ACL entries, users in the group cannot access any models.

Secure Your Odoo Implementation

Explore our complete guide to Odoo security, user management, and access control best practices.

Read All Guides Explore Products

References

Partager cet article
Se connecter pour laisser un commentaire.