RBAC, Record Rules & Row-Level Security — menelusuri jalur dari seorang user yang klik menu sampai ke baris SQL yang benar-benar dikembalikan.
Bukan sihir. Setiap request melewati 5 lapisan yang bekerja otomatis — beberapa di database, beberapa cuma di UI.
Model NIST RBAC: user diberi role, role diberi permission berupa pasangan (object, operation). Tapi Odoo punya jebakan terminologi:
| Istilah Odoo | Padanan RBAC standar |
|---|---|
| res.users | User |
| res.groups | Role (meski namanya "group") |
| implied_ids | Role hierarchy / graph |
| ir.model.access + ir.rule | Permission |
| res.groups.privilege | label UI saja BUKAN permission |
| all_group_ids | Effective role set (closure) |
Di Odoo, "group" berfungsi sebagai role, dan "privilege" cuma kategori untuk form user — tidak ada enforcement. Jangan tertukar saat implementasi ulang.
L3 + L4 ditegakkan di ORM & SQL (security sungguhan). L5 menu/view sebagian besar cuma UX — data tetap diamankan oleh L3/L4. sudo() melewati L3–L5 sekaligus.
Sebelum bicara peran atau izin, Odoo harus membuktikan identitas user di setiap request — bukan sekadar saat login.
extension 2FA/OAuth/WebAuthn hanya override _check_credentials — tak perlu sentuh login flow.
Seorang user punya dua versi keanggotaan group:
Setiap cek downstream (ACL, rule, menu) hanya membaca all_group_ids, tidak pernah group_ids mentah.
Group bisa meng-imply group lain: "setiap anggota Manager otomatis juga User".
Anggota Administrator otomatis dapat semua permission di rantai bawahnya.
Tiap baris ACL = (model, group, r/w/c/d). Sebuah user boleh mode pada model jika salah satu group efektifnya punya grant.
User B (portal) tidak punya baris ACL untuk account.move → AccessError instan.
Inilah jantungnya. Tiap rule punya domain_force — sebuah filter — yang disuntikkan langsung ke klausa WHERE di setiap pencarian.
kunci Forbidden row tidak "dihabis setelahnya" — domain sudah masuk WHERE. Tidak ada post-filter.
Ini sifat paling penting & paling sering disalahpahami. Beberapa rule untuk user+model digabung jadi satu domain:
Karena global di-AND, rule group tidak pernah bisa melemahkan rule global.
Itulah kenapa isolasi multi-company tidak bisa di-bypass dengan memberi role tambahan — syarat global tetap berlaku.
Field bisa ditandai hanya boleh dibaca/ditulis oleh group tertentu. Ditegakkan di server, bukan cuma di UI.
| Mekanisme | Tipe |
|---|---|
| field groups= | server enforce |
| view node groups= | UI strip |
| menu groups | UX only |
User B tidak melihat kolom harga karena dua hal: view node di-strip dan field-nya juga punya groups=. Bekasnya cuma UX, keduanya diamankan di server.
Menu terlihat jika group_ids-nya kosong atau beririsan dengan peran user, dan model action-nya boleh dibaca (ACL).
Menu yang disembunyikan tidak melindungi data. User yang tahu URL action tetap bisa invoke — tapi kemudian ACL & record rule akan menolak datanya.
Jangan pernah andalkan "user tidak bisa lihat tombol" untuk keamanan.
→ User A lihat 50 baris. User B dapat AccessError di langkah 5.
sudo() menyalakan env.su — diperiksa di setiap lapisan dan langsung di-short-circuit:
User id 1 (base.user_root) selalu superuser. Tidak bisa dinonaktifkan; harus ada ≥1 admin.
⚠ hati-hati sudo menyeberang batas perusahaan — bisa campur record terisolasi. Pakai hanya untuk operasi internal terpercaya.
Setiap panggilan ORM melewati rantai yang sama. Hafalkan ini dan 90% "kenapa saya tidak bisa lihat X?" jelas.
ACL & rule ditegakkan di ORM + SQL. Row asing tidak pernah dikembalikan DB.
Menu & view hiding cuma pengalaman. Data tetap diamankan oleh L3/L4.
Satu flag lewati semua, menyeberang perusahaan. Pakai terukur.
→ Read the practical guide (debug, write rules, cheatsheet)
// full source-level docs: docs/security-architecture/