Skip to main content

django-opa-permissions

Object-level Django permissions backed by OPA (Rego) policies stored in the database, evaluated in-process via opa-golib-python-bindings — no OPA server needed.

Features

  • Policies as DB objects — Policy (UUID pk, Rego source) grouped into PolicySets via an ordered membership (a policy may belong to several sets); a PolicySetBinding maps a Django model (ContentType) to the policyset governing it. Unbound models deny everything (superusers excepted).
  • Auth backend — OpaPermissionBackend implements user.has_perm("app.view_book", obj) so Django and DRF (DjangoObjectPermissions) work out of the box. Designed for subclassing: many small hook methods (extend_input, get_builtins, is_bypass, filter_result, …).
  • Minimal input document — policies receive only the user pk, app label, model name, action, and (except for browse/create) the object pk. Data is fetched from inside the policy via custom builtins (django_opa_fetch, django_opa_fetch_old, django_opa_query) — ORM-backed, no SQL.
  • Browse partial evaluation — Model.objects.for_user(user) compiles the policy's filter rule with the object left unknown (OPA partial evaluation → UCAST → Django Q/Exists), filtering listings in SQL; relation traversal and $some/$all/$none quantifiers are supported. The authoritative full check (with the object pk) runs in allowed_for_user / has_perm.
  • Admin integration — OpaModelAdminMixin wires a ModelAdmin to OPA: the changelist is gated by the browse permission and filtered with the browse prefilter; object pages check view/change/delete per object. Classic Django model permissions remain a fallback (see Security notes).
  • Admin policy debugger — evaluate a policyset as any (visible) user against any bound model/object: print() output inline next to the policy line, per-line coverage highlighting, the full output document as a tree, and the residual browse prefilter. No trace logs. Requires the change permission on the policy (see Security notes).

Install

pip install django-opa-permissions
INSTALLED_APPS = [..., "django_opa_permissions"]
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "django_opa_permissions.backends.OpaPermissionBackend",
]

Quick start

from django_opa_permissions.managers import OpaPermissionedModel

class Book(OpaPermissionedModel):
    title = models.CharField(max_length=300)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    published = models.BooleanField(default=False)

Create a PolicySet, add a Policy, and bind it to the Book content type (all in the admin). Every policy must declare package policies; the entry points are allow (full evaluation) and filter (browse partial evaluation):

package policies

import rego.v1

# Owners may do anything with their own books.
allow if {
    obj := django_opa_fetch(input.app_label, input.model, input.object.id)
    obj.owner_id == input.user.id
}

# Anyone may view or browse published books.
allow if {
    input.action in {"view", "browse"}
    obj := django_opa_fetch(input.app_label, input.model, input.object.id)
    obj.published == true
}

# Browse prefilter: the object is UNKNOWN here — reference its columns via
# input.object; this compiles to SQL through the ORM.
filter if {
    input.object.published == true
}

filter if {
    input.object.owner_id == to_number(input.user.id)
}
Book.objects.for_user(user)          # SQL prefilter (partial evaluation)
Book.objects.allowed_for_user(user)  # prefilter + authoritative per-object pass
user.has_perm("library.view_book", book)
book.user_can(user, "change")        # or "delete", custom actions, ...
modified.user_can(user, "change", old=snapshot)  # policies may fetch both states

See docs/ for the input schema, builtins, filter-rule grammar (relation paths and quantifiers), and subclassing the backend; example_project/ is a runnable sqlite demo:

cd example_project
python manage.py migrate
python manage.py load_example_policies
python manage.py create_demo_users   # alice/alice-password, bob/bob-password,
                                     # admin/admin-password (superuser)
python manage.py runserver

Security notes

Read this before rolling out in a hostile environment. The design trades a few guarantees for admin ergonomics; every point below is deliberate and configurable.

  • Admin falls back to classic Django permissions. With OpaModelAdminMixin, every OPA decision is OR-ed with the classic ModelAdmin check: a user holding a traditional Django model permission (e.g. view_book via groups + ModelBackend) gets full, unfiltered access to that model in the admin — OPA row-level denies do not apply to them. This keeps stock Django workflows working, but it means your Django groups must not hold permissions on OPA-governed models if you want OPA to be the sole authority. Audit django.contrib.auth.models.Permission assignments on bound models accordingly.
  • Policy authors can read your whole database. The builtins (django_opa_fetch, django_opa_fetch_old, django_opa_query) serialize every concrete column of any model — including sensitive ones such as User.password (the hash), tokens or secrets stored on rows — and they use _base_manager, bypassing any permission-filtering manager. Anyone who can create or edit a Policy (and anyone able to open the policy debugger) can therefore read data your application policies are meant to protect. Treat policy authorship as a highly privileged role; if you need redaction, override serialize_instance / get_builtins in a backend subclass to strip sensitive fields.
  • Superusers bypass OPA by default. DJANGO_OPA_SUPERUSER_BYPASS (default True) lets superusers skip all policy checks, including the default-deny for unbound models. Set it to False in settings if superusers must be policy-governed too.
  • The full OPA builtin surface is available to policies. Beyond the Django builtins, policies run on the embedded OPA engine (opa-golib-python-bindings), so whatever builtins that engine exposes — potentially including http.send, net.*, opa.runtime and crypto.* — is callable from a policy. A policy author can make network requests or read runtime configuration from inside your request path. Verify the engine's capabilities for your deployment (e.g. via a capabilities file) and restrict who may author policies.

License

MIT

Metadata

Release files for django-opa-permissions 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-opa-permissions 0.4.0
File Size Uploaded
django_opa_permissions-0.4.0.tar.gz 47.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-opa-permissions 0.4.0
File Interpreter ABI Platform
django_opa_permissions-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 80.1 kB

Release files / django_opa_permissions-0.4.0.tar.gz

Download URL django_opa_permissions-0.4.0.tar.gz
Size 47.2 kB
Tags Source
SHA-256 checksum
How to use checksums
85ee6136dfc47d958bcb032f96fc2647566405d690a9a3a4808fc85e1289ee1d
BLAKE2b-256 checksum
How to use checksums
1522267d6b556d774b0d03fb7e44556933b725aa994d329200960da0ec4964ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / django_opa_permissions-0.4.0-py3-none-any.whl

Download URL django_opa_permissions-0.4.0-py3-none-any.whl
Size 32.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6f05e02063d2c18b6cf973af79eb0ced7a8e1c4d13113aa697abb753e6099b7
BLAKE2b-256 checksum
How to use checksums
fcfb2620f4b9a914210d5dc9cf58589f6e384a9d61521142d2ef09bd63ef7783
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page