Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

References/Permission

From OHC Network Wiki
Revision as of 09:38, 6 July 2026 by Admin (talk | contribs) (Automated edit (via update-page on MediaWiki MCP Server))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
referenceaccess-governanceCARE 3.0+

A permission is the ability to perform one action in one context — read it as "Action on Resource", like "Can Create Patient" in the PATIENT context. Permissions are the atoms of Care's access control: code declares them, the sync command writes them to the database, roles group them, and permission associations bind them to resources. You rarely touch this table directly; you reference its rows by slug when building roles.

The Django PermissionModel is only storage; two other places do the real work. The permission registry (care/security/permissions/) declares every permission as a Python enum member carrying a name, description, context, and the default roles that hold it. The Pydantic resource specs (care/emr/resources/role/spec.py) define the read API schema. This doc covers all three.

Source:

Models

Model Purpose
PermissionModel A single, named permission (action) that can be granted to a user in a context

PermissionModel extends BaseModel, the lowest-level Care base — not EMRBaseModel. That means no created_by/updated_by, no history, no meta columns. The inherited fields:

Field Type Notes
external_id UUIDField default=uuid4, unique, indexed. Opaque public identifier — never expose the integer pk
created_date DateTimeField auto_now_add; nullable, indexed
modified_date DateTimeField auto_now; nullable, indexed
deleted BooleanField default=False, indexed. Soft-delete flag; the default manager hides deleted=True rows

PermissionModel fields

Field Type Required Default Notes
slug CharField(1024) yes
name CharField(1024) yes
description TextField no
context CharField(1024) yes
temp_deleted BooleanField no

context values (PermissionContext)

context is a free-form CharField in the database, but the registry only writes one of these enum values ([https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py constants.py]):

Value Meaning
GENERIC Not scoped to a specific resource type
FACILITY Scoped to a facility
PATIENT Scoped to a patient
QUESTIONNAIRE Scoped to a questionnaire
ORGANIZATION Scoped to a (govt/role) organization
FACILITY_ORGANIZATION Scoped to a facility organization
ENCOUNTER Scoped to an encounter

The value is load-bearing: when resolving access for a serialized resource, the mixins below filter on permission__context__in=[...], so only permissions in the matching context count.

Registry declaration — Permission dataclass

Permissions are authored in code, not the database. Each one is a member of a *Permissions enum, one per resource area (PatientPermissions, EncounterPermissions, and so on). The member name becomes the slug; the member value is a Permission dataclass ([https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/constants.py constants.py]):

Field Type Maps to PermissionModel Notes
name str name
description str description
context PermissionContext context (.value)
roles list[Role] — (drives RolePermission)

Example (care/security/permissions/patient.py):

can_create_patient = Permission(
    "Can Create Patient", "", PermissionContext.PATIENT,
    [STAFF_ROLE, DOCTOR_ROLE, NURSE_ROLE, ADMINISTRATOR, ADMIN_ROLE, FACILITY_ADMIN_ROLE],
)

PermissionController ([https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py base.py]) aggregates every handler enum:

  • get_permissions() → dict[slug, Permission] — the full declared registry (cached).
  • get_enum() → Enum — a dynamically built str enum of all permission slugs, used by RoleCreateSpec to constrain the permissions write field to known slugs.

Sync command (sync_permissions_roles)

The management command [https://github.com/ohcnetwork/care/blob/develop/care/security/management/commands/sync_permissions_roles.py sync_permissions_roles] is the only writer of this table. It is idempotent, Redis-locked, and runs in a single transaction:

  1. Mark every PermissionModel temp_deleted=True.
  2. For each declared permission, upsert by slug — setting name, description, context (from Permission.context.value) and clearing temp_deleted.
  3. Hard-delete any row still temp_deleted=True (no longer declared).
  4. Upsert system roles, then rebuild RolePermission rows from each Permission.roles list, using the same mark-then-prune pattern.

RolePermission

Permissions reach users only through roles. RolePermission is the join table (role FK, permission FK, temp_deleted) connecting a RoleModel to a PermissionModel. A user's effective access is the union of permissions across their role bindings, filtered to active grants (rolepermission__temp_deleted=False).

Resource specs (API schema)

The Pydantic specs build on EMRResource ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py base.py]), whose serialize (DB → spec) and de_serialize (spec → DB) drive read and write, with perform_extra_serialization/perform_extra_deserialization hooks for side effects.

Spec class Role File Notes
PermissionSpec read · list + detail role/spec.py The only permission-facing spec. Served read-only by PermissionViewSet
PermissionsMixin read augmentation permissions.py Adds the requesting user's computed permissions list to other resources' serialized output

No PermissionCreateSpec or PermissionUpdateSpec exists: permissions are reference data, never client-writable. Write specs exist for roles instead — see References/Role.

PermissionSpec

__model__ = PermissionModel. A flat read schema with no extra serialization hooks; it inherits the base mapping that sets id = external_id:

Field Type Notes
name str From PermissionModel.name
description str From PermissionModel.description
slug SlugType str, min_length=5, max_length=50, must match ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ (URL-safe; starts and ends alphanumeric). See [https://github.com/ohcnetwork/care/blob/develop/care/emr/utils/slug_type.py slug_type.py]
context str One of the PermissionContext values above
id UUID4 Added by serialize as external_id (inherited base behaviour)

Served by [https://github.com/ohcnetwork/care/blob/develop/care/security/api/viewsets/permissions.py PermissionViewSet], an EMRModelReadOnlyViewSet (list + retrieve only) with lookup_field="slug", filterable by name (icontains). No create, update, or delete endpoint exists.

Computed permissions on other resources (PermissionsMixin)

[https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py permissions.py] defines mixins that other read specs inherit, so a serialized resource carries the current user's effective permission slugs for that object. For authenticated users, PermissionsMixin.perform_extra_user_serialization calls add_permissions(mapping, user, obj) to populate a permissions: list[str] field:

Mixin Output fields Resolution
PatientPermissionsMixin permissions Roles on the patient; permission slugs where context in ["PATIENT", "FACILITY"]
EncounterPermissionsMixin permissions Roles on the encounter; permission slugs where context in ["ENCOUNTER", "PATIENT"]
FacilityPermissionsMixin permissions, root_org_permissions, child_org_permissions Union of root-org and sub-org role permissions; child_org_permissions excludes the can_update_facility slug

The frontend reads these lists to gate UI by capability, skipping a separate authorization round-trip.

Methods & save behaviour

PermissionModel adds no custom save(), delete() override, validators, or signals — everything comes from BaseModel, including soft-delete (delete() setting deleted=True) and the default manager that filters out soft-deleted rows. The lifecycle runs externally through sync_permissions_roles (above), which stages with temp_deleted and hard-deletes undeclared rows.

PermissionSpec runs no extra serialization or deserialization: just the base serialize flow plus the inherited id = external_id mapping.

API integration notes

  • Permissions are platform-maintained reference data: declared in the registry, synced into this table by sync_permissions_roles, read-only over the API. PermissionViewSet exposes list + retrieve only.
  • Reference a permission by slug — it is stable, unique, and the API lookup_field. Treat external_id as the opaque public ID; never expose the integer pk.
  • You can't grant a permission directly to a user. A role collects permissions (via RolePermission), and a permission association binds that role to a resource — organization, patient, encounter, and so on. Effective access is the union of active permissions across a user's role bindings.
  • context has no DB choices but is always a PermissionContext enum string. The mixins filter resource permissions by context, so the value matters.
  • When you write a role, PermissionController.get_enum() constrains the permissions field to known slugs; an unknown slug fails validation. See References/Role.

Pages that link here:

Wiki: ohcnwiki.tellmey.fyi