Permissions in Odoo: access rights vs record rules (the confusion that costs you)
Why a user «can't see their records» —or worse, sees everyone's—. Odoo's two security layers, how they fit together, and how to debug them without losing your mind.
Part of our complete guide: Odoo module development.
There are two calls an Odoo consultant dreads. The first: "a salesperson says they can't see any opportunities". Annoying, but harmless. The second: "a salesperson says they can see everyone else's opportunities". That second call is the one that keeps you up at night, because it's a data leak. And almost always, both are born from the same misunderstanding: confusing Odoo's two security layers.
People mix them up because they sound similar, but they do different things. Let's pull them apart once and for all.
Layer 1 — Access rights: can it touch this model?
The first layer answers a coarse question: can this group of users read, create, modify or delete records of this model? Regardless of which ones. It lives in a CSV and it's the bare minimum any model needs to exist for the user:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_lead_user,lead.user,model_crm_lead,base.group_user,1,1,1,0
access_lead_manager,lead.manager,model_crm_lead,sales_team.group_sale_manager,1,1,1,1
Read it out loud: "regular users can read, write and create leads, but not delete them; managers can also delete". Note something important: access rights are cumulative. If you belong to two groups, you add up their permissions. They never subtract. This surprises people coming from systems where a "deny" always wins.
What this layer does not do is decide which leads each person sees. To Odoo, at this layer, a lead is a lead. All or nothing.
Layer 2 — Record rules: can it touch this record?
The second layer filters row by row. A record rule (ir.rule) carries a domain: a condition each record must meet for the user to see it (or edit it). This is where you actually get "each salesperson sees only their own":
<record id="lead_rule_own" model="ir.rule">
<field name="name">Leads: own only</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
That rule says: "a regular user only sees leads whose salesperson is themselves". The access right gave them permission over the model; the rule narrows down which rows of that model.
The trap behind both calls
And here's the nuance almost nobody has clear, the one that causes both the locked-out user and the data leak:
- Global rules (no group) are combined with AND logic: you must satisfy all of them. They're fences nobody jumps.
- Group rules are combined with OR logic among themselves: it's enough to satisfy one of the rules from your groups.
In plain terms: if you give someone an extra group that brings its own, more permissive rule, that rule widens what they see, it doesn't restrict it. That's where the leak is born: you add a salesperson to the "Sales Manager" group so they can see their team, and you accidentally strip away their "own only" filter, because the new group's rule is more open and wins. The reverse causes the other call: an overly strict global rule nobody remembers adding leaves half the staff seeing nothing.
Groups are the glue (and where it breaks)
Both layers hang off groups. A group is a role: "Sales User", "Manager", "Read only". You assign permissions and rules to groups, and users to groups. The problem is that in a real implementation groups multiply, inherit from one another, and nobody documents what each one implies. Six months later, no one in the company can explain why So-and-so sees what they see.
That's why, when we design security, we start from the business question —"who should see what?"— and not from the checkboxes. The checkboxes come afterwards.
How to debug it without losing your head
When something doesn't add up, this is the order I follow, and it rarely fails:
- Is it layer 1 or layer 2? If the user can't see the menu or open the model at all, it's an access right. If they see the menu but the list comes back empty (or too full), it's the record rules.
- Turn on developer mode and check, under Settings → Technical, the access rights and rules for the model in question. Seeing them all together is usually enough to spot the culprit.
- Read the domain out loud. 90% of the mess is in a badly written domain or a group rule that opens up too much.
Security in Odoo isn't complicated once you separate the two layers in your head: one decides about the model, the other about the record. What's expensive is discovering the flaw the day a client tells you they saw data they shouldn't have.
If you handle sensitive information —customer data, case files, salaries— it's worth having someone review your setup before an incident does. In a quick audit we go over your groups, access rights and rules and tell you where the gaps are. And if you're going to build custom modules, make sure security is in from the start: you'll see it in our anatomy of a module and in how we approach development.
Comments (0)
Be the first to comment.
Sign in to leave a comment.
Sign inComments are reviewed before publishing.
Related articles
Automated actions in Odoo: let the ERP do the boring work
Server actions, automation rules and scheduled jobs: the three pieces that make Odoo react, notify and run on its own —without anyone having to remember.
PDF reports with QWeb in Odoo: from the form to the document that closes deals
QWeb is HTML that turns into PDF. We show you how to build a report, loop over records with t-foreach, and why t-field saves you a thousand headaches.
Inheritance in Odoo: the feature that decides whether your code survives upgrades
Classical extension, delegation and view inheritance. The three ways to modify Odoo without touching the core —and why inheriting instead of copying saves you every migration.