Enable Dark Mode!
an-overview-of-multi-company-security-in-odoo-19.jpg
By: Abhijith CK

An Overview of Multi-Company Security in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

Managing different companies from a single database instance is one of the most important features of Odoo. It allows for centralization of work while isolating all information belonging to a particular company. Nevertheless, poor setup of security policies, access permissions, or custom modules may cause unintentional leaking of data between companies.

This blog post will provide useful recommendations on developing secure multi-company applications using Odoo 19. We will also discuss how Odoo implements company isolation, typical security problems, and some techniques that developers need to apply in custom modules.

Understanding Multi-Company in Odoo 19

Multi-company setup permits multiple companies to work under one Odoo database while keeping their accounts separate from each other.

For instance, assume an organization that has:

  • Company A
  • Company B
  • Company C

And then, each of these companies requires access only to its own:

  • Customers
  • Suppliers
  • Sales Orders
  • Purchase Orders
  • Employees
  • Accounts
  • Warehouses

The above can be achieved by using:

  • Company-Aware Models
  • Record Rules
  • Access Control List (ACL)
  • Company Dependent Fields
  • Company Context

Access Control in Odoo

The user is provided with:

  • Default company
  • Accessible company or companies

Both can be accessed through the environment as follows:

  1. self.env.company
  2. Retrieves the current company

  3. self.env.companies
  4. Retrieves all companies currently accessible to the user

Example:

current_company = self.env.company
allowed_companies = self.env.companies
print(current_company.name)

Best Practice 1: Always Add a Company Field

When there is any relationship between the record and an organization, it should be defined with a company field.

from odoo import fields, models
class LibraryBook(models.Model):
    _name = "library.book"
    name = fields.Char(required=True)
    company_id = fields.Many2one(
        "res.company",
        default=lambda self: self.env.company,
        required=True,
        index=True,
    )

Benefits:

  • Enables company-based filtering
  • Simplifies security rules
  • Prevents cross-company data mixing

Best Practice 2: Set Proper Access Control Rules

The access control rules represent the primary means of limiting access to the records.

Example:

<record id="library_book_company_rule" model="ir.rule">
    <field name="name">Library Book Multi Company</field>
    <field name="model_id" ref="model_library_book"/>
    <field name="domain_force">
        [('company_id', 'in', company_ids)]
    </field>
</record>

This ensures that the user can see the records belonging to only the authorized companies.

In the absence of this rule, the user would see the records belonging to some other companies as well.

Best Practice 3: Use self.env.company Instead of Hardcoding Companies

Avoid writing code like this:

company = self.env["res.company"].browse(1)

This implies the presence of a Company ID 1, which is inconsistent.

Instead, use:

company = self.env.company

Advantages include:

  • Suitable for all databases
  • Multi-company compatible
  • Easy maintenance

Best Practice 4: Change Company Properly

In certain cases, it may be required that some actions be done on behalf of another company.

Use:

other_company = self.env.ref("base.main_company")
records = self.with_company(other_company)

Example:

product = self.with_company(other_company).product_id

Avoid modifying company_id manually during processing.

Best Practice 5: Use with_company() Instead of sudo()

Developers commonly abuse:

self.sudo()

to sidestep access controls.

Although sudo() functions without any security constraints, it poses a risk to organizations by exposing information to third parties through careless usage.

Instead:

self.with_company(company)

sudo() should only be considered when it is absolutely needed.

Best Practice 6: Respect the Active Company in Searches

Incorrect:

partners = self.env["res.partner"].search([])

Better:

partners = self.env["res.partner"].search([
    ("company_id", "=", self.env.company.id)
])

Or, if records may be shared:

partners = self.env["res.partner"].search([
    "|",
    ("company_id", "=", False),
    ("company_id", "=", self.env.company.id),
])

Best Practice 7: Handle Shared Records Properly

Shared models deliberately exist between organizations.

Some examples include:

  • Countries
  • Currencies
  • Measurements
  • Products (if configured accordingly)

When configuring record rules, determine whether you want the records to be global.

For example:

[
"|",
("company_id", "=", False),
("company_id", "in", company_ids),
]

Best Practice 8: Use Company-Dependent Fields When Needed

Field values vary from one organization to another.

Example:

price = fields.Float(
    company_dependent=True
)

An organization may keep its value without duplicating it.

It is applied to the following:

  • Product accounts
  • Property fields
  • Financial configurations

Best Practice 9: Test with Multiple Companies

However, many security issues emerge only in the multi-organizations scenario.

An efficient testing process should include:

  • Creation of two or more organizations
  • Creation of users with varying access to organizations
  • Changing the current organization
  • Validation of searches
  • Validation of reports
  • Validation of dashboards
  • Validation of scheduled actions

One should never rely on the code working for one organization only within the multi-organizations database.

Best Practice 10: Be Careful with sudo()

Example:

records = self.sudo().search([])

This bypasses:

  • Record Rules
  • Access Rights
  • Company Restrictions

A safer approach is:

records = self.with_company(self.env.company).search([])

If sudo() is required, apply additional filters to prevent exposing unrelated company data.

Best Practice 11: Use Company Context in Business Logic

Odoo stores the active company in the execution context.

Example:

company = self.env.context.get("allowed_company_ids")

Or simply rely on:

self.env.company

This keeps your code aligned with the user's current working company.

Best Practice 12: Secure Custom Controllers and APIs

Custom HTTP controllers should never expose unrestricted data.

Incorrect:

products = request.env["product.product"].sudo().search([])

Better:

products = request.env["product.product"].search([
    ("company_id", "in", request.env.companies.ids)
])

Always validate that API responses respect company access.

Common Mistakes

Common mistakes to avoid:

  • Hardcoding of company IDs
  • Over usage of sudo()
  • Missing company_id for company specific models
  • Missing record rules
  • Ignoring shared records
  • Testing using only one company
  • Sending unrestricted search in controller response

Performance Considerations

Efficiency is an important attribute for a good multi-company security system.

Recommendations:

  • Index company_id
  • Avoid using sudo() when it’s unnecessary
  • Ensure that rule domains are simple
  • Batch whenever you can
  • Always leverage the current environment rather than creating new environments

Security Checklist

Before installing a custom module, check whether:

  • A company_id field exists for each company model.
  • Record rules limit access according to allowed companies.
  • The company_ids are not hard coded.
  • The search takes into account the active company.
  • The company restrictions are taken care of by controllers and APIs.
  • sudo() is called where required.
  • Multicompany restrictions are kept in mind while testing.

Designing a secure multi-company environment in Odoo 19 is far from simply adding the company_id field to your implementation. It means that you need to implement your models, record rules, business logic, and controllers in such a way as to ensure that the user gets access only to the information they should get access to.

With the help of these best practices, such as self.env.company usage, proper record rules implementation, avoidance of unnecessary use of sudo(), taking into account the company context, and testing with multiple companies, you can develop an efficient Odoo application.

In a nutshell, a good multi-company design is not only about securing information but also providing a smooth experience for companies that run their operations in several companies via one Odoo database.

To read more about Complete Overview of Security in Odoo 19, refer to our blog Complete Overview of Security in Odoo 19.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



WhatsApp