Security Contexts in Odoo
Odoo's ORM enforces access controls based on the current user. Sometimes you need to bypass these controls (sudo) or switch to a different user's context (with_user). Understanding the difference — and the security implications — is critical for writing correct and secure Odoo code.
sudo() — Superuser Mode
What It Does
sudo() switches the recordset's environment to the superuser (SUPERUSER_ID = 1), bypassing all access rules, record rules, and field-level security:
# Regular access — respects user's permissions
partners = self.env['res.partner'].search([])
# Sudo access — sees all records, ignores access rules
all_partners = self.env['res.partner'].sudo().search([])When to Use sudo()
- Reading configuration data the current user should not directly access
- Creating records in models the user has no create access to (e.g., creating log entries)
- Automated actions that need full access (cron jobs, server actions)
- Cross-model operations where the user has access to model A but not model B
- Counting or aggregating data for display without exposing individual records
Common Patterns
# Read config parameter (users normally can't read ir.config_parameter)
api_key = self.env['ir.config_parameter'].sudo().get_param('my.api_key')
# Create audit log entry (user may not have create access)
self.env['my.audit.log'].sudo().create({
'action': 'login',
'user_id': self.env.uid,
})
# Search across companies
all_orders = self.env['sale.order'].sudo().search([
('state', '=', 'sale'),
])with_user() — User Impersonation
What It Does
with_user(user) switches the environment to a specific user, applying that user's access rights, record rules, and company context:
# Execute as a specific user
user = self.env['res.users'].browse(target_user_id)
records = self.env['sale.order'].with_user(user).search([])
# Only returns records this user has access toWhen to Use with_user()
- Testing what a specific user can see or do
- Creating records that should appear as authored by a specific user
- Processing tasks on behalf of another user
- Preserving correct authorship in chatter messages and tracking
Common Patterns
# Create sale order as the salesperson (not the admin running the script)
order = self.env['sale.order'].with_user(salesperson).create({
'partner_id': customer.id,
})
# Post message as the assigned user (not sudo superuser)
record.with_user(assigned_user).message_post(
body='Task completed.',
)sudo() vs with_user() Comparison
| Aspect | sudo() | with_user(user) |
|---|---|---|
| User context | SUPERUSER_ID (1) | Specified user |
| Access rules | Bypassed entirely | Applied for target user |
| Record rules | Bypassed entirely | Applied for target user |
| Company context | Keeps original company | Switches to user's company |
| Chatter author | Shows as OdooBot/Superuser | Shows as target user |
| Use case | Bypass security | Act as another user |
Security Best Practices
Minimize sudo() Scope
# BAD — entire method runs as sudo
def process_order(self):
order = self.sudo()
order.write({'state': 'confirmed'})
order.create_invoice()
order.send_email()
# Everything bypasses security!
# GOOD — sudo only where needed
def process_order(self):
self.write({'state': 'confirmed'})
# Only the log entry needs sudo
self.env['my.audit.log'].sudo().create({
'action': 'confirm',
'order_id': self.id,
})
self.create_invoice() # Normal security
self.send_email() # Normal securityNever Return sudo() Records to the User
# BAD — user gets sudo access to the partner
def get_partner(self):
return self.env['res.partner'].sudo().browse(partner_id)
# GOOD — read the data you need, return without sudo
def get_partner_name(self):
partner = self.env['res.partner'].sudo().browse(partner_id)
return partner.name # Return the value, not the recordUse SUPERUSER_ID for Sale Order Creation
Odoo requires SUPERUSER_ID for certain operations like sale order creation with specific pricing:
from odoo import SUPERUSER_ID
order = self.env['sale.order'].with_user(SUPERUSER_ID).create(vals)Note: env(user=SUPERUSER_ID) and sudo() are similar but sudo() is the modern API.
Common Pitfalls
- sudo() leaking — sudo() returns a new recordset. If you store it and pass it around, downstream code runs with elevated privileges unintentionally.
- Tracking with sudo() — Field tracking under sudo() shows changes as made by Superuser/OdooBot. Use with_user() to preserve the actual user.
- sudo() in controllers — Extremely dangerous. A public controller using sudo() can expose data to unauthenticated users. Always authenticate first.
- Forgetting to sudo() — AccessError in automated actions usually means you need sudo(). But audit whether the access is legitimate before adding it.