Cedar authorization for Django REST Framework apps
Project description
drf-cedar
drf-cedar is a lightweight Django REST framework for policy-first authorization with Cedar.
It is designed so route handlers stay small while authorization logic stays centralized in policy files.
What drf-cedar gives you
- Enforced Cedar checks on all CRUD endpoints built from Crucible views.
- Consistent action naming (
ListVehicles,CreateBooking,AdminGetProduct, etc.). - Scoped factory views for nested routes like
/vehicles/{vehicle}/bookings/, enabling policy decisions on parent resources duringlistandcreate. - Model-to-Cedar entity mapping using
authz_fields()so policy context can be expressed in Cedar instead of view code. - Optional principal attribute providers for injecting extra principal attributes (for example, council membership).
- Transaction-safe lifecycle hooks (
post_create,post_commit_create, etc.) for side effects.
Core Concepts
1) Policy-first route authorization
Every HTTP operation on drf-cedar base views is mapped to an action and authorized before business logic runs.
GET /resource/{pk}->Get<Resource>GET /resource/->List<Resources>POST /resource/->Create<Resource>PUT/PATCH /resource/{pk}->Update<Resource>DELETE /resource/{pk}->Delete<Resource>
Action names are generated from:
{action_prefix}{Verb}{ModelNameOrPlural}
Where:
action_prefixdefaults to""(can be overridden, for example"Admin").- Model name comes from
queryset.model._meta.verbose_name(or plural form for list).
2) Resource selection for authorization
drf-cedar authorizes against:
- Object endpoints: the target object from
get_object(). - List/create endpoints:
get_scope_resource()(if provided), else system-global resourceSystem::"global".
This is what makes scoped nested routes powerful for policy checks.
3) Entity attribute projection
Models can define:
def authz_fields(self) -> dict:
...
Those fields become Cedar entity attributes and can be used in policy conditions.
4) Principal enrichment
Principal attributes can be injected by pluggable providers listed in settings, letting policies refer to derived/related attributes on the principal.
Components
drf_cedar.authz
Authz.authorize(user, action, resource)- Builds principal/entity graph.
- Builds Cedar request and calls
cedarpy.is_authorized(...). - Raises DRF
PermissionDenied("Forbidden")when denied.
- Supports anonymous principal as
Anonymous::"guest". - Supports user groups as Cedar parents (
Group::<name>).
drf_cedar.views
BaseCedarAuthzView- Handles authz for HTTP methods.
- Loads policy file from settings.
- Loads principal attribute providers from settings.
ModelCrudView- Full CRUD + list with authorization.
ReadOnlyModelView- Retrieve + list with authorization.
ScopedViewMixin- For nested resources:
- Filters queryset by parent scope.
- Binds parent object on create.
- Uses parent object as authz resource for list/create.
- For nested resources:
- Transaction-aware CRUD mixins:
CreateModelMixin,UpdateModelMixin,DestroyModelMixin- Hooks:
post_*andpost_commit_*.
drf_cedar.serializers
BaseModelSerializer- Standard read-only timestamps and id fields.
_get_attr(...)helper for create/update validations.
DataURLImageField- Accepts multipart file or data URL (
data:image/png;base64,...).
- Accepts multipart file or data URL (
drf_cedar.models
Entityabstract model withcreated_at/updated_at.money(...)helper for decimal quantization.
Settings Contract
CEDAR_POLICY_PATH = "path/to/policies.cedar"
CEDAR_PRINCIPAL_ATTRIBUTE_PROVIDERS = [
"myapp.authz.SomePrincipalAttrProvider",
]
Provider contract:
class SomePrincipalAttrProvider:
def get_attributes(self, user) -> dict:
return {}
Example:
class CouncilUserAttrProvider:
def get_attributes(self, user):
if hasattr(user, "council_user"):
return {"council": {"id": str(user.council_user.council.id)}}
return {}
Usage Patterns
1) Standard CRUD endpoint with policy enforcement
class CouncilCrudView(ModelCrudView):
serializer_class = CouncilSerializer
queryset = Council.objects.all().order_by("name")
No explicit permission code required. Actions become:
ListCouncils,GetCouncil,CreateCouncil,UpdateCouncil,DeleteCouncil
2) Read-only endpoint
class CustomerProductView(
RetrieveModelMixin,
ListModelMixin,
BaseCedarAuthzView,
):
serializer_class = ProductSerializer
queryset = Product.objects.all().order_by("id")
3) Action prefix for role-segmented policy namespace
class AdminProductCrudView(ModelCrudView):
action_prefix = "Admin"
serializer_class = AdminProductSerializer
queryset = Product.objects.all().order_by("id")
This yields actions like AdminListProducts, AdminGetProduct, etc.
4) Scoped factory view for nested list/create
class VehiclesForUserView(
ScopedViewMixin, CreateModelMixin, ListModelMixin, BaseCedarAuthzView
):
serializer_class = VehicleSerializer
queryset = Vehicle.objects.all().order_by("-created_at")
scope_model = User
scope_kwarg = "owner"
With route /users/{owner}/vehicles/:
GETauthorizesListVehiclesagainstUser::{owner}POSTauthorizesCreateVehicleagainstUser::{owner}
The created Vehicle.owner is auto-bound from URL scope.
5) Scoped list where query path differs from URL kwarg
class BookingsForCouncilView(ScopedViewMixin, ListModelMixin, BaseCedarAuthzView):
serializer_class = BookingSerializer
queryset = Booking.objects.all().order_by("-created_at")
scope_model = Council
scope_kwarg = "council"
scope_attr = "vehicle__council"
scope_attr allows filtering nested relations while still authorizing on Council.
6) Custom endpoint action outside CRUD convention
class UpdateInfoView(UpdateModelMixin, BaseCedarAuthzView):
serializer_class = UserSerializer
queryset = User.objects
def patch(self, request, *args, **kwargs):
resource = self.get_object()
self.authz.authorize(request.user, "UpdateUserInfo", resource)
return self.partial_update(request, *args, **kwargs)
Use this pattern when action naming must be explicit or domain-specific.
7) Model attributes for policy conditions
Vehicle.authz_fields():
def authz_fields(self):
return {"owner": {"id": str(self.owner.id)}}
Booking.authz_fields():
def authz_fields(self):
return {
"vehicle": {
"owner": {"id": str(self.vehicle.owner.id)},
"council": {"id": str(self.vehicle.council.id)},
},
"installer": {"user": {"id": str(self.installer.user.id)}},
}
These fields drive Cedar conditions like owner/council matching.
8) Post-save hooks with transaction semantics
Crucible mixins support:
post_create,post_update,post_destroy(inside transaction)post_commit_create,post_commit_update,post_commit_destroy(after commit)
Cedar policy examples
From this project's policy file:
- Admin full access:
principal in Group::"Admins"permits all actions/resources.
- Driver-owned vehicle access:
- Permit
GetVehicle|UpdateVehicle|DeleteVehicle|CreateBookingwhenresource.owner.id == principal.id.
- Permit
- Scoped list for council users:
- Permit
ListBookingsonCouncilwhenresource.id == principal.council.id.
- Permit
This demonstrates why scoped routes matter: list/create can be decided by parent resource attributes without special-case Python checks.
Quick Start
- Install
drf-cedar - Add settings:
CEDAR_POLICY_PATH- optional
CEDAR_PRINCIPAL_ATTRIBUTE_PROVIDERS
- Define Cedar policy actions to match your view action naming.
- Build views from
ModelCrudView/ReadOnlyModelView/ mixin composition. - Add
authz_fields()to domain models referenced in policy conditions. - Prefer scoped nested routes for create/list operations where parent context matters.
AI Agent Implementation Checklist
When adding a new endpoint:
- Choose base class:
- CRUD:
ModelCrudView - Read-only:
ReadOnlyModelViewor retrieve/list mixins +BaseCedarAuthzView - Nested scope: include
ScopedViewMixin
- CRUD:
- Set
querysetandserializer_class. - For scoped views set:
scope_modelscope_kwarg- optional
scope_attrfor relation traversal
- Confirm generated Cedar action names; set
action_prefixif needed. - Add/adjust Cedar policies for those actions.
- Add
authz_fields()to models if policy conditions need related IDs. - For derived principal context, add a provider class and register it in settings.
- Keep business logic in serializer hooks /
post_*hooks; keep auth decisions in Cedar.
Security Notes
- Deny-by-default: any missing policy/action mapping returns
403 Forbidden. - Avoid bypassing
BaseCedarAuthzView. - Keep policy file under version control and code review changes like code.
- Treat
authz_fields()as part of your security boundary; changes can alter access.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drf_cedar-0.1.1.tar.gz.
File metadata
- Download URL: drf_cedar-0.1.1.tar.gz
- Upload date:
- Size: 41.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6333c0e490e0d8edf36eb45f1293cb2853e7577a6d90a2dc020989f45f852abe
|
|
| MD5 |
50797378164992ff977f1a2ca5116191
|
|
| BLAKE2b-256 |
4016f6062e18aab50d7360e941ef1cec6b920ec39f41679172132d3bab0f6f9d
|
Provenance
The following attestation bundles were made for drf_cedar-0.1.1.tar.gz:
Publisher:
pypi-publish.yaml on andycaine/drf-cedar
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drf_cedar-0.1.1.tar.gz -
Subject digest:
6333c0e490e0d8edf36eb45f1293cb2853e7577a6d90a2dc020989f45f852abe - Sigstore transparency entry: 1125582141
- Sigstore integration time:
-
Permalink:
andycaine/drf-cedar@4e262b2070e25f61825c7e09ec34a1de9e0e500b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/andycaine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@4e262b2070e25f61825c7e09ec34a1de9e0e500b -
Trigger Event:
push
-
Statement type:
File details
Details for the file drf_cedar-0.1.1-py3-none-any.whl.
File metadata
- Download URL: drf_cedar-0.1.1-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e5f2c4d01277cc23e5a552bf7874f7e3bca4cff723c936aa91630b148d39f4c
|
|
| MD5 |
28c674af014a9b83bbd7e0e5acd4a228
|
|
| BLAKE2b-256 |
dca4d23068b745264ae4d96216357ed4ce516437e8c125201d497b33b77e5709
|
Provenance
The following attestation bundles were made for drf_cedar-0.1.1-py3-none-any.whl:
Publisher:
pypi-publish.yaml on andycaine/drf-cedar
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drf_cedar-0.1.1-py3-none-any.whl -
Subject digest:
9e5f2c4d01277cc23e5a552bf7874f7e3bca4cff723c936aa91630b148d39f4c - Sigstore transparency entry: 1125582202
- Sigstore integration time:
-
Permalink:
andycaine/drf-cedar@4e262b2070e25f61825c7e09ec34a1de9e0e500b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/andycaine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@4e262b2070e25f61825c7e09ec34a1de9e0e500b -
Trigger Event:
push
-
Statement type: