Skip to main content

Declarative relationship-based access control (ReBAC) for Django, backed by SpiceDB

Project description

django-spicedb

Declarative, relationship-based access control (ReBAC) for Django, backed by SpiceDB. Define your authorization model once in Python; let Django enforce it everywhere.


Features

  • Model-centric configuration - Define relations and permissions directly on Django models via RebacMeta
  • Automatic tuple sync - FK and M2M changes automatically sync to SpiceDB via signals
  • Permission inheritance - Build hierarchies with parent->permission expressions
  • Group-based access - Role-based group membership (member/manager) out of the box
  • Query integration - Filter querysets by permission with .accessible_by(user, 'view')
  • FK change tracking - Automatically cleans up stale tuples when relationships change

Installation

pip install django-spicedb

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    'django_rebac',
]

Configure SpiceDB connection:

REBAC = {
    'adapter': {
        'endpoint': 'localhost:50051',
        'token': 'your-spicedb-token',
        'insecure': True,  # For local development
    },
}

Quickstart

1. Start SpiceDB

docker run -d --name spicedb -p 50051:50051 \
  authzed/spicedb serve --grpc-preshared-key devkey

2. Define Your Models

from django.db import models
from django_rebac.models import RebacModel
from django_rebac.integrations.orm import RebacManager

class Document(RebacModel):
    title = models.CharField(max_length=255)
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    folder = models.ForeignKey('Folder', on_delete=models.CASCADE, null=True)

    objects = RebacManager()

    class RebacMeta:
        relations = {
            'owner': 'owner',      # FK field name
            'parent': 'folder',    # FK to parent resource
        }
        permissions = {
            'view': 'owner + parent->view',
            'edit': 'owner + parent->edit',
        }

3. Publish Schema & Backfill

python manage.py publish_rebac_schema
python manage.py rebac_backfill

4. Check Permissions

from django_rebac.runtime import can

# Simple check
if can(request.user, 'view', document):
    # User can view this document

# Query accessible objects
documents = Document.objects.accessible_by(request.user, 'view')

Group-Based Access Control

For team/department-based permissions, use the Group pattern:

from django.db import models
from django_rebac.models import RebacModel
from django_rebac.integrations.orm import RebacManager

class Group(RebacModel):
    """A group with role-based membership."""
    name = models.CharField(max_length=255)

    objects = RebacManager()

    class RebacMeta:
        type_name = 'group'
        relations = {
            # Manual relations - no field binding, synced via GroupMembership
            'member': {'subject': 'user'},
            'manager': {'subject': 'user'},
        }
        permissions = {
            'view': 'member + manager',
            'manage': 'manager',
        }


class GroupMembership(models.Model):
    """Through table for group membership with roles."""
    ROLE_MEMBER = 'member'
    ROLE_MANAGER = 'manager'
    ROLE_CHOICES = [
        (ROLE_MEMBER, 'Member'),
        (ROLE_MANAGER, 'Manager'),
    ]

    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    role = models.CharField(max_length=32, choices=ROLE_CHOICES, default=ROLE_MEMBER)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=('group', 'user'), name='unique_membership'),
        ]


class Verification(RebacModel):
    """A resource that inherits permissions from its parent group."""
    title = models.CharField(max_length=255)
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)

    objects = RebacManager()

    class RebacMeta:
        type_name = 'verification'
        relations = {
            'owner': 'owner',
            'parent': 'group',
        }
        permissions = {
            'view': 'owner + parent->view',    # Owner OR group members
            'manage': 'owner + parent->manage', # Owner OR group managers
        }

Then create signal handlers for GroupMembership to sync tuples:

# signals.py
from django.db import transaction
from django.db.models.signals import post_save, post_delete
from django_rebac.adapters import factory
from django_rebac.adapters.base import TupleKey, TupleWrite

def handle_membership_save(sender, instance, **kwargs):
    def do_sync():
        factory.get_adapter().write_tuples([
            TupleWrite(key=TupleKey(
                object=f'group:{instance.group_id}',
                relation=instance.role,
                subject=f'user:{instance.user_id}',
            ))
        ])
    transaction.on_commit(do_sync)

def handle_membership_delete(sender, instance, **kwargs):
    def do_delete():
        factory.get_adapter().delete_tuples([
            TupleKey(
                object=f'group:{instance.group_id}',
                relation=instance.role,
                subject=f'user:{instance.user_id}',
            )
        ])
    transaction.on_commit(do_delete)

post_save.connect(handle_membership_save, sender=GroupMembership)
post_delete.connect(handle_membership_delete, sender=GroupMembership)

Now permissions flow naturally:

  • Group members can view all verifications in their group
  • Group managers can view and manage all verifications in their group
  • Owners always have full access to their own verifications

RebacMeta Reference

Relations

Map Django fields to SpiceDB relations:

class RebacMeta:
    relations = {
        # FK binding - field name maps to relation
        'owner': 'owner_field',

        # M2M binding - field name maps to relation
        'member': 'members_field',

        # Manual relation - no field, synced manually
        'manager': {'subject': 'user'},

        # Parent relation for hierarchy
        'parent': 'parent_folder',
    }

Permissions

SpiceDB permission expressions:

class RebacMeta:
    permissions = {
        'view': 'owner + member',           # OR: owner or member
        'edit': 'owner',                     # Direct relation
        'admin': 'owner + parent->admin',   # Inherited from parent
        'manage': 'manager + parent->manage',
    }

Binding Kinds

Bindings are auto-inferred from field types:

  • FK fieldskind: 'fk' - Uses field_id cache, tracks changes
  • M2M fieldskind: 'm2m' - Syncs on post_add, post_remove, post_clear
  • Manual{'subject': 'type'} - No auto-sync, handle via signals

How It Works

  1. Schema Generation: RebacMeta on models compiles to SpiceDB schema DSL
  2. Tuple Sync: Django signals (post_save, post_delete, m2m_changed) write/delete tuples
  3. FK Tracking: pre_save captures old FK values; post_save deletes stale tuples
  4. Transaction Safety: All SpiceDB writes happen in transaction.on_commit()
  5. Permission Checks: can() and .accessible_by() query SpiceDB via gRPC

Management Commands

# Publish schema to SpiceDB
python manage.py publish_rebac_schema

# Backfill tuples from existing Django data
python manage.py rebac_backfill

DRF Integration

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['django_rebac.drf.ReBACPermission'],
}

Development

See developers.md for development setup, testing, and contributing guidelines.

# Install dependencies
poetry install

# Start SpiceDB
docker compose up -d spicedb

# Run tests
poetry run pytest

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

django_spicedb-0.1.1.tar.gz (44.2 kB view details)

Uploaded Source

Built Distribution

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

django_spicedb-0.1.1-py3-none-any.whl (59.2 kB view details)

Uploaded Python 3

File details

Details for the file django_spicedb-0.1.1.tar.gz.

File metadata

  • Download URL: django_spicedb-0.1.1.tar.gz
  • Upload date:
  • Size: 44.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.11.11 Darwin/22.6.0

File hashes

Hashes for django_spicedb-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c9cb8f830dcec9a1b242d6d3f37c58954b3e5a0af2ff7090fce6c5acd97c2666
MD5 187109ce05d82b0fc06fcd6c5c415f7a
BLAKE2b-256 9844204c7db33bec433b5bff119bcf16acd5c0df27abbe23627608a52a6a8f1b

See more details on using hashes here.

File details

Details for the file django_spicedb-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: django_spicedb-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 59.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.11.11 Darwin/22.6.0

File hashes

Hashes for django_spicedb-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cc4751193c3b94f4cd2ef03f04694b32ad72df065b2719b77f877783725b06c8
MD5 f2b078dab93540388b71e22cd7d0e129
BLAKE2b-256 36744246313e039695d38c02758cdc565c92a7f06f8b24b2d635b2e66e1175ef

See more details on using hashes here.

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