Security & Multi-Company¶
Who this is for
The operator setting up users, and anyone verifying that the data-separation story actually holds up (e.g. before an audit). Day-to-day users usually don't need this page.
What you'll accomplish: understand who can see what, verify the separation works, and fix access-control problems when they surface.
The business rule in plain English¶
Two companies share one Odoo database:
- Reyder Enterprises — runs the operation, owns customer relationships, and handles Reyder sales orders and customer invoices. A Reyder-only user sees Reyder records plus consigned devices that an active agreement permits—not every record in both companies.
- Axis Mobile — owns the devices, receives settlement payments. Should see only their own devices and their own owner-settlement reports. Must not see Reyder's customer names, sale prices, SO numbers, or internal manifests.
This is not a “please don't look” convention. For normal users, Odoo record rules control
ORM operations used by the UI and API. They are not database row-level security: raw
SQL and Odoo SUPERUSER execution can bypass ORM controls, so those capabilities are
restricted to trusted system administrators and sit outside the operator-access boundary.
At-a-glance comparison¶
| An Axis user sees | A Reyder user sees |
|---|---|
| Their devices (IMEI, model, grade, status) | Reyder devices + permitted Axis devices (via consignment) |
| Axis owner settlement reports only | Reyder consignee settlement reports only |
| Their consignment agreements | Their consignment agreements |
Reserved state on their devices (via stock.lot) — not the reservation record itself |
Full retail reservations (since Reyder is the holding company) |
| Device traceability (where Axis's devices are) | Full warehouse operations |
| — no customer names | All customer data |
| — no sale prices | All pricing |
| — no SO numbers | All SOs |
| — no Reyder manifests | All manifests |
Set up users the right way¶
Assign people to the correct company¶
A user who works for Axis should belong only to Axis. A user who works for Reyder should belong only to Reyder. Only system administrators should belong to both.
Open Settings → Users & Companies → Users and edit the user:
- Companies — tick only the companies this user works for
- Default Company — what they log into
If someone legitimately works for both, they belong to both — and the system will auto-expand their company context (see the gotcha below). But defaulting everyone to both-companies breaks the separation test.
Test profiles¶
| Account type | Belongs to | Purpose |
|---|---|---|
| Dual-company administrator | Both companies | Administration and authorized cross-company investigation |
| Admin-provisioned Axis test user | Axis Mobile only | Verify that an Axis-only user cannot see Reyder data |
Always test with a real single-company profile
A dual-company administrator is not a valid separation test. Ask an administrator to provision an approved Axis-only test account, keep its password in the approved credential system, and never publish or share that password in a guide.
What enforces the separation¶
For normal user access, the security boundary combines record rules with model-level data
minimization. One-way links and invisible form fields further reduce accidental exposure,
but they are not independent API/export boundaries and do not replace the first two
controls. Raw SQL and SUPERUSER access remain administrator-only and outside this model.
Layer 1 — Record rules (the primary enforcement)¶
Odoo record rules filter ORM reads and writes made through the normal UI and API. The module adds these rules on top of the stock defaults:
| Model | Domain | Effect |
|---|---|---|
| Settlement report | company_id in company_ids |
Axis sees only their owner reports; Reyder sees only their consignee reports |
| Settlement report line | Follows parent report | Lines are only visible if you can see the report |
| Device manifest | company_id in company_ids |
Each company sees only their manifests |
| Consignment agreement | owner in company_ids OR consignee in company_ids |
Both parties to an agreement can see it |
stock.lot (device) |
Custom — see below | Consignee can see owner's devices when an agreement is active |
| Transfer order | source OR destination in company_ids |
Both parties see it |
| Retail sale | company_id in company_ids |
Company-scoped |
| Retail reservation | holding_company_id in company_ids |
Holding company only — owners do not see the reservation record directly; they see reserved state on stock.lot instead |
| SO line device allocation | SO company is allowed and the device is owned by an allowed company or visible to an authorized consignee | Requires both the allowed SO company and an allowed owner/consignee relationship |
Layer 2 — Sensitive sale detail is absent from owner report lines¶
When the system creates an owner settlement report, it deliberately omits customer, sale price, sale date, sale-order reference, and consignee commission from the owner line. The owner gets the IMEI, device description, and amount owed—what is needed to reconcile payment without exposing Reyder's commercial detail.
Layer 3 — One-way link reduces exposure¶
The paired_report_id field points only consignee → owner. The owner report has no
reverse navigation link to the consignee report. This reduces normal navigation and
accidental disclosure; record rules and data minimization remain the actual access
boundary.
Layer 4 — View-level hiding for usability and defense in depth¶
On the settlement report form, sale_order_id is wrapped in
invisible="report_type == 'owner'", so it is not rendered in the owner-side UI. This
keeps the form clear and reduces accidental exposure, but invisibility alone is never an
API or export security boundary.
The device visibility rule (stock.lot)¶
The module replaces Odoo's default stock.lot record rule with a custom one that handles consignment:
A user can see a device if any of these is true:
1. company_id is in their company list (the device belongs to them), OR
2. company_id is False (unassigned), OR
3. owner_company_id is in their company list (they own it — Axis sees Axis's devices), OR
4. consignee_company_ids contains one of their companies (they're an authorized consignee — Reyder sees Axis's devices that have an active Reyder-Axis agreement)
Rule 4 is the consignment magic. It's what makes Axis's inventory visible to Reyder the moment an agreement is activated, and invisible again the moment it's terminated.
See Consignment Agreements for the operator workflow.
Verifying the separation (manual test)¶
Want to confirm it works? Run this quick test:
- Log out.
- Log in with an administrator-provisioned Axis-only test account using your approved internal credential process.
- Try to open Device Ops → Fulfillment → Customer Manifests. You should see zero results for Reyder deliveries.
- Open Device Ops → Consignment → Settlement Reports. You should see only
SETTLE/OWN/*rows, neverSETTLE/CON/*. - Click into an owner report. There's no Customer field, no Sale Price, no Sale Order number.
If any of those tests show Reyder data, stop and escalate it as an access-control incident. The most common cause is that the test account was accidentally allowed into Reyder too.
Common problems¶
Axis user is seeing Reyder data
Check Settings → Users and open the Axis user. Confirm Companies lists only Axis Mobile. If Reyder is in there too, remove it. This is the #1 cause of leaks.
A dual-company administrator doesn't see separation
By design, that account is allowed into both companies and is not a valid isolation test. Use an administrator-provisioned Axis-only test account.
Reyder user can't see Axis devices
Confirm the agreement is Active and today's date is inside its Start/End dates. A suspended agreement uses Reactivate, not Activate. A valid agreement refreshes device visibility automatically; if a matching device is still missing, do not edit its company fields—send the agreement and IMEI references to support.
A settlement report is visible to both companies
Stop and escalate it as an access-control incident. Do not edit the report company, delete the report, or change its status. Send support the report reference, active company, user profile, and a screenshot of what was visible.
Cron jobs run as SUPERUSER and might post data that bypasses rules
Correct — SUPERUSER_ID=1 bypasses all record rules. This is intentional for crons and backend processes. The protection is at read time for user sessions. Cron-generated data is written by the system on both sides; it doesn't accidentally leak during a read because the record rule still filters what users see.
Administrator checkpoint¶
Before onboarding either company, test the same representative records with an Axis-only profile, a Reyder-only profile, and an authorized dual-company administrator. Record the test date and result. Detailed shell procedures and implementation notes belong in the private engineering runbook, not in an operator guide.
