django-scoped-access
Hierarchical Scoped Authorization (ABAC) + RBAC + Temporal Lifecycle + Step-Up Re-Authentication for Django & DRF.
Reference implementation of the Scoped Access Specification.
📖 Overview
django-scoped-access brings fine-grained, hierarchical multi-tenant access control to Django without polluting your business models or hardcoding organizational trees into database schemas.
Whether building a single-tenant app (flat RBAC), a multi-tenant B2B SaaS (Organization → Team), or a complex organizational system (National → Regional → District → Facility → Department), the hierarchy is pure configuration.
┌──────────────────────────────┐
│ NATIONAL │ (Root level)
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ REGIONAL │
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ DISTRICT │
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ FACILITY │ (Can manage custom roles)
└─────────────────────────────┘
✨ Key Features
- 🌲 Hierarchy as Configuration, Not Schema: Configure any arbitrary tree depth or degrade to standard RBAC at depth 0.
- 🎯 Domain Agnostic: The engine never owns your models. Hierarchy nodes and role owners link via generic relations (
GenericForeignKey). - 🔒 Inclusive Downward Scope Coverage: A role grant at
REGIONALcovers all descendant nodes (Districts, Facilities, Units) in that subtree, and never upwards. - 🛡️ Anti-Escalation Protection (Rule R5): Tenant admins can manage custom roles within their scope, but can never grant permissions they do not possess themselves.
- ⏱️ Temporal Validity & Audit Trail: Assignments support
valid_from/valid_untilwindows and status transitions (ACTIVE⇄SUSPENDED→REVOKED). Assignments are never hard-deleted. - 🔐 Step-Up Re-Authentication (ReAuth): Require fresh, single-use proof of identity (passwords, PIN, WebAuthn, TOTP) for high-risk actions. Superusers are not exempt.
- ⚡ Database-Level Query Filtering: SQL-level filtering (
scope_filter_q()) for collection views—no in-memory Python iteration. - 🚀 Full Django REST Framework (DRF) Integration: Drop-in permissions (
ScopedModelPermission,ScopeObjectPermission), query mixins (ScopeQuerySetMixin), write guards (ScopeWriteGuardMixin), and introspection endpoints (GET /me/access/).
⚡ Quickstart (5 Minutes)
1. Installation
# With pip
pip install "django-scoped-access[drf]"
# With uv
uv add "django-scoped-access[drf]"
2. Configuration (settings.py)
INSTALLED_APPS = [
...,
"scoped_access",
"rest_framework",
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"scoped_access.backends.ScopedPermissionBackend",
]
SCOPED_ACCESS = {
"HIERARCHY": [
{"level": "GLOBAL"},
{"level": "ORGANIZATION", "model": "accounts.Organization"},
{"level": "TEAM", "model": "accounts.Team", "parent": "organization"},
],
"ROLE_OWNER_LEVELS": ["ORGANIZATION"],
"GRANTABLE_PERMISSIONS": "self",
"REAUTH": {"ENABLED": True, "TTL": 300},
}
Apply database migrations:
python manage.py migrate
3. Register Resource Anchors
Declare the path from your business models to their hierarchy node:
# helpdesk/apps.py
from django.apps import AppConfig
class HelpdeskConfig(AppConfig):
name = "helpdesk"
def ready(self):
from scoped_access import register
from helpdesk.models import Ticket
# A Ticket is anchored to a Team
register(Ticket, anchor="team")
4. Create Roles & Grant Assignments
from scoped_access import RoleService, ScopeAssignment
from helpdesk.models import Team
support_team = Team.objects.get(name="Support")
# 1. Create a System Role
agent_role = RoleService.create(
name="Support Agent",
permissions=[view_ticket_perm, change_ticket_perm],
by=admin_user,
)
# 2. Grant the role to Alice at the Team scope
ScopeAssignment.objects.grant(
user=alice,
role=agent_role,
scope=support_team,
by=admin_user,
)
5. Protect DRF Endpoints
# helpdesk/views.py
from rest_framework.viewsets import ModelViewSet
from scoped_access.drf import (
RequireReAuth,
ScopedModelPermission,
ScopeObjectPermission,
ScopeQuerySetMixin,
ScopeWriteGuardMixin,
)
class TicketViewSet(ScopeWriteGuardMixin, ScopeQuerySetMixin, ModelViewSet):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
permission_classes = [ScopedModelPermission, ScopeObjectPermission]
def get_permissions(self):
permissions = super().get_permissions()
if self.action == "destroy":
# Deleting tickets requires Step-Up Re-Authentication
permissions.append(RequireReAuth())
return permissions
🔍 Introspection Endpoint (GET /me/access/)
Wire the standard introspection and reauth views in urls.py:
from django.urls import path
from scoped_access.drf import MeAccessView, ReAuthView
urlpatterns = [
path("api/me/access/", MeAccessView.as_view(), name="me-access"),
path("api/auth/reauth/", ReAuthView.as_view(), name="reauth"),
]
Example JSON output from GET /api/me/access/:
{
"principal": {"id": "42", "superuser": false, "active": true},
"permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"],
"assignments": [
{
"role": {"id": "7", "name": "Support Agent", "system": true},
"level": "TEAM",
"scope": {"id": "12", "label": "Customer Support — Acme Corp"},
"status": "ACTIVE",
"valid_until": null,
"permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"]
}
]
}
🧪 Testing & Conformance
Django Scoped Access is verified against a language-agnostic test suite defined in SPEC.md and conformance/:
uv run pytest
📚 Documentation
Complete documentation is available at https://django-scoped-access.readthedocs.io/:
- Quickstart Guide
- Core Concepts & Architecture
- Configuration Reference
- Django REST Framework (DRF) Integration
- Step-Up Re-Authentication (ReAuth)
- Lifecycle & Auditability
- Security Guide & Threat Model
- Formal Specification (SPEC)
📄 License
This project is licensed under the terms of the MIT License.
Metadata
Release files for django-scoped-access 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_scoped_access-0.1.0.tar.gz | 115.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_scoped_access-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 152.2 kB
Release files / django_scoped_access-0.1.0.tar.gz
| Download URL | django_scoped_access-0.1.0.tar.gz |
|---|---|
| Size | 115.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
616c6b02321d1b165c576454eb704d0889026274fe3f6f6c04497652c2037072
|
|
BLAKE2b-256 checksum How to use checksums |
ebe86477a3f799fb46ac7bf6b98b343259ccce93ab0c5dea9b10b14941a2b2d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / django_scoped_access-0.1.0-py3-none-any.whl
| Download URL | django_scoped_access-0.1.0-py3-none-any.whl |
|---|---|
| Size | 36.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8c5fe4fcc60fc6263d4058ecd3945788eecae1351bc498f53b28ff9c8b3e5ab2
|
|
BLAKE2b-256 checksum How to use checksums |
36b036cd5890d9dd4f0f09517a4a03ff0318899dae827263abf26e8b64b4c597
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for django-scoped-access 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.