Skip to the content.

AgriOps — RBAC Design

Version: 1.0 Date: March 2026 Status: Phase 2 Complete — fully implemented


Overview

AgriOps uses a four-level role hierarchy enforced via system_role on CustomUser. All permission decisions in both the Django view layer and the DRF API layer trace to this single field.

See ADR 002 for the rationale behind the hybrid system_role + job_title architecture.


Role Hierarchy

org_admin  (level 4)  — Full control within their tenant
manager    (level 3)  — Can delete records, cannot manage users or roles
staff      (level 2)  — Can create and edit records, cannot delete
viewer     (level 1)  — Read-only access

Roles are strictly hierarchical. A higher level includes all permissions of lower levels.


Permission Matrix

Action Viewer Staff Manager OrgAdmin
View records
Create records
Edit records
Delete records
Manage users
Change system_role
Manage company settings
View compliance reports
Export compliance data

Django View Layer — Permission Mixins

Location: apps/users/permissions.py

RoleRequiredMixin (base)
  ├── StaffRequiredMixin    — required_role = 'staff'   (level 2+)
  ├── ManagerRequiredMixin  — required_role = 'manager' (level 3+)
  └── OrgAdminRequiredMixin — required_role = 'org_admin' (level 4)

All mixins extend LoginRequiredMixin — unauthenticated users are redirected to /login/. Authenticated users who fail the role check receive 403 PermissionDenied.

Usage pattern:

class SupplierDeleteView(ManagerRequiredMixin, DeleteView):
    ...

Mixin order matters — permission mixin always comes before the Django generic view class.


API Layer — DRF Permission Classes

Location: apps/api/permissions.py

IsTenantMember        — user is authenticated and belongs to a company
IsManagerOrAbove      — IsTenantMember + system_role level >= 3
IsOrgAdmin            — IsTenantMember + system_role == 'org_admin'

All API viewsets inherit from TenantScopedViewSet which uses IsTenantMember by default and enforces IsManagerOrAbove for delete operations.


Tenant Isolation

RBAC and tenant isolation are enforced together. A user with org_admin role can only manage users and records within their own company — not across tenants.

All querysets in views and viewsets filter by request.user.company. Cross-tenant access raises Http404 — not 403 — to avoid leaking the existence of records in other tenants.


system_role Change Control

system_role can only be changed via the dedicated UserSystemRoleUpdateView at /users/<pk>/role/. This view is protected by OrgAdminRequiredMixin.

system_role is excluded from the general UserUpdateView fields list — it cannot be changed via the standard profile edit form under any circumstances.

Every system_role change is captured in the AuditLog.