Khichdi InfoTech
Skip to content

Odoo Development · intermediate · 12 min read

Security: Groups and Record Rules

Odoo access rights: ir.model.access.csv, security groups, record rules, field-level restrictions, and multi-company patterns that enforce server-side.

Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice

Table of contents

Odoo enforces security in layers: model access (CRUD per group), record rules (row-level domains), field groups on views, and optional Python checks in methods. UI-only hiding is not security.

ir.model.access.csv grants read, write, create, unlink flags per model per group. Record rules further filter rows — global rules combine with AND; group-specific rules add OR branches within the same rule group.

Multi-company deployments rely on company_id fields, allowed_company_ids in context, and rules referencing user.company_ids. Test with non-admin users on staging before production.

Every custom model ships with implicit superuser access only until you add ACL. Record rules are easy to get wrong — overly broad domains leak data; overly tight domains break legitimate workflows.

This article walks through defining groups, wiring CSV access, writing record rules, and validating with real users — not only the admin account.

res.groups define roles (Sales / User: Own Documents Only). implied_ids chain inheritance — manager groups imply user groups. Category in module_data.xml groups related security entries in Settings.

Assign default groups via res.users template data sparingly. Custom groups should name your module prefix to avoid clashing with standard group xml ids.

  • module_category_data.xml for Settings grouping
  • group_system bypasses most checks — limit admin accounts
  • portal and public groups for website controllers — separate ACL

Each line: id, name, model_id:id, group_id:id, perm_read, perm_write, perm_create, perm_unlink. Use model_id ref format module.model_model_name matching ir.model external id.

Ship minimal access first — read-only for observers, write for operators, create/unlink only where business requires. Missing CSV line means only superuser sees the model.

ir.rule records attach domain_force Python expressions evaluated per user. global=True applies to everyone unless bypassed; groups limit rule to members.

Common pattern: ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] for multi-company. Own-documents-only uses ('user_id', '=', user.id) on models with responsible field.

HTTP routes with auth='user' still run as that uid — ACL applies. auth='public' with sudo in controller is a frequent data leak — validate tokens and scope queries.

RPC and XML-RPC/JSON-RPC honor the authenticated user. Cron runs as specific user — set user_id on ir.cron so rules match intended visibility.

Create test users per role on staging. Attempt read/write via UI and RPC. TransactionCase tests can switch user with self.env['model'].with_user(user).

When rules fail mysteriously, inspect combined domain in log or use shell to print env['ir.rule']._compute_domain.

  • Add security XML and CSV in the same PR as new models — not a follow-up ticket.
  • Mirror company rules on all custom models with company_id.
  • Avoid sudo in business methods; push elevation to controlled utility layers.
  • Document which group unlocks each menu and sensitive field.
  • Review implied_ids when cloning groups from demo modules.
  • Use read-only ACL for audit/report roles instead of write with hidden buttons.

  • !No ACL file — model works for admin only in UAT, fails for users at go-live.
  • !Record rule domain referencing field user cannot read — recursive denial.
  • !global=True rule conflicting with group rules — unexpected empty lists.
  • !Testing only as admin then blaming Odoo permissions.
  • !Portal user with excessive model write access via overly broad CSV.
  • !Cron running as superuser when business needs company-scoped processing.

Security is server-enforced or it is optional. Ship ACL and record rules with every model, validate as real users, and treat sudo as audited exception.

Automated jobs and APIs inherit the same rules — configure cron users and integration accounts deliberately.

Request → ACL check → record rules → field groups → method override.
1

Authenticate

User session or API key.

2

Groups

Feature access.

3

Record rules

Row-level visibility.

4

Field access

Read/write restrictions.

Per-role: UI list count, form open, write attempt, RPC call.
  • Owners named
  • UAT scripts signed
  • Rollback documented
  • Hypercare roster ready

Frequently asked questions

Do record rules apply to sudo()?

sudo bypasses both ACL and record rules unless re-applied with a specific user via with_user. Treat sudo as breaking the security stack.

How are multiple record rules combined?

Global rules AND together. Group-specific rules for the same model OR within their set, then AND with globals. Exact evaluation is implemented in ir.rule — test edge cases.

Can I restrict a single field?

Use groups= on field in form view for UI. For true enforcement, override write/create to reject changes from unauthorized groups or use computed readonly fields.

Where do I define security in the module?

Typically security/security.xml for groups and rules, security/ir.model.access.csv for ACL. Load groups before CSV and rules in __manifest__.py order.

Permissions chaos after go-live or upgrade?

We audit ACL, record rules, and integration users — then fix leaks without disabling multi-company isolation.

Business technology and software delivery