Skip to main content

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 during list and create.
  • 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_prefix defaults 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 resource System::"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.
  • Transaction-aware CRUD mixins:
    • CreateModelMixin, UpdateModelMixin, DestroyModelMixin
    • Hooks: post_* and post_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,...).

drf_cedar.models

  • Entity abstract model with created_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/:

  • GET authorizes ListVehicles against User::{owner}
  • POST authorizes CreateVehicle against User::{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|CreateBooking when resource.owner.id == principal.id.
  • Scoped list for council users:
    • Permit ListBookings on Council when resource.id == principal.council.id.

This demonstrates why scoped routes matter: list/create can be decided by parent resource attributes without special-case Python checks.


Quick Start

  1. Install drf-cedar
  2. Add settings:
    • CEDAR_POLICY_PATH
    • optional CEDAR_PRINCIPAL_ATTRIBUTE_PROVIDERS
  3. Define Cedar policy actions to match your view action naming.
  4. Build views from ModelCrudView / ReadOnlyModelView / mixin composition.
  5. Add authz_fields() to domain models referenced in policy conditions.
  6. Prefer scoped nested routes for create/list operations where parent context matters.

AI Agent Implementation Checklist

When adding a new endpoint:

  1. Choose base class:
    • CRUD: ModelCrudView
    • Read-only: ReadOnlyModelView or retrieve/list mixins + BaseCedarAuthzView
    • Nested scope: include ScopedViewMixin
  2. Set queryset and serializer_class.
  3. For scoped views set:
    • scope_model
    • scope_kwarg
    • optional scope_attr for relation traversal
  4. Confirm generated Cedar action names; set action_prefix if needed.
  5. Add/adjust Cedar policies for those actions.
  6. Add authz_fields() to models if policy conditions need related IDs.
  7. For derived principal context, add a provider class and register it in settings.
  8. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drf_cedar-0.1.1.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drf_cedar-0.1.1-py3-none-any.whl (11.8 kB view details)

Uploaded Python 3

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

Hashes for drf_cedar-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6333c0e490e0d8edf36eb45f1293cb2853e7577a6d90a2dc020989f45f852abe
MD5 50797378164992ff977f1a2ca5116191
BLAKE2b-256 4016f6062e18aab50d7360e941ef1cec6b920ec39f41679172132d3bab0f6f9d

See more details on using hashes here.

Provenance

The following attestation bundles were made for drf_cedar-0.1.1.tar.gz:

Publisher: pypi-publish.yaml on andycaine/drf-cedar

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for drf_cedar-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9e5f2c4d01277cc23e5a552bf7874f7e3bca4cff723c936aa91630b148d39f4c
MD5 28c674af014a9b83bbd7e0e5acd4a228
BLAKE2b-256 dca4d23068b745264ae4d96216357ed4ce516437e8c125201d497b33b77e5709

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page