Skip to main content

ambit

Scoped, multi-dimensional authorization for Django.

Most authorization answers one question: what may this user do? ambit answers the one that actually shapes real organisations: what, and where. A department head may edit staff — but only in their department. A regional manager sees orders — but only in their region. A teacher marks work — but only their own classes.

Django's built-in permissions are global: hold orders.change_order and you can change every order. ambit adds the missing axis — scope — without changing how you write permissions.

has_authorized_scope(user, "orders.change_order", WITHIN_REGION)   # anywhere in reach?
authorized_queryset(user, "orders.view_order", Order.objects.all(), WITHIN_REGION)  # the rows they may see
can_access_object(user, "orders.change_order", order, WITHIN_REGION)  # this exact one?

The idea: the engine is invariant, the dimensions are yours

A dimension is an axis access can be limited along. ambit ships none — you register the ones your organisation actually has. A school registers stage / department / classroom / own-teaching; a company registers region / department / team / own. The scope math, the roles and grants, the strict-intersection semantics — all identical. The semantics change; the functionality doesn't.

That's the whole design: the same engine authorises a school and a sales org, because it never knew what a "classroom" or a "region" was — only that it's a dimension with values a grant may constrain.

Install

pip install django-ambit
# settings.py
INSTALLED_APPS = [
    # ...
    "ambit",
]
python manage.py migrate

Quickstart

1. Register your dimensions (once, e.g. in an AppConfig.ready()):

from ambit.dimensions import register_dimension

# row-backed: a grant points at a Team row
register_dimension("team", "Team", model_label="sales.Team")

# self-resolving: no stored row — resolves to the user's own value at request time
register_dimension("own", "Own records", resolves_to=lambda user: user)

2. Describe how a permission reaches your models — a policy maps each dimension to a lookup path on the object being checked:

from ambit.policies import ScopePolicy, ScopeBinding

WITHIN_REACH = ScopePolicy({
    "team": ScopeBinding(queryset_lookup="team"),
    "own":  ScopeBinding(queryset_lookup="owner"),
})

3. Grant access — a role (a bundle of Django permissions) at a scope (a point in the space your dimensions define):

from ambit.models import Role, RoleAssignment
from ambit.services.scope_builder import scope_for

role = Role.objects.create(name="Rep")
role.permissions.add(view_order_permission)

# "Alice is a Rep in Team North"
RoleAssignment.objects.create(user=alice, role=role, scope=scope_for({"team": north}))

4. Check — anywhere in a view, queryset, or template helper:

from ambit.services.authorization import (
    has_authorized_scope, authorized_queryset, can_access_object,
)

authorized_queryset(alice, "sales.view_order", Order.objects.all(), WITHIN_REACH)
# -> only orders in Team North

Or drop the mixins onto class-based views and never write a check by hand:

from ambit.mixins import ScopedQuerysetMixin

class OrderListView(ScopedQuerysetMixin, ListView):
    model = Order
    permission = "sales.view_order"
    authorization_policy = WITHIN_REACH

Concepts

Concept What it is
Dimension An axis access is limited along. Row-backed (points at a row, e.g. a Team) or self-resolving (resolves to the user's own value, e.g. "own records").
Scope A set of per-dimension constraints. Strict intersection: {team: North, own} means in North and their own. A scope with no constraints is global.
Policy Which dimensions a permission is checked against, and the lookup path to each on the target model.
Role A named bundle of Django permissions.
Grant (RoleAssignment) A user holds a role at a scope.
Catalog An optional, human-worded map of the permissions a console should offer (register_catalog).

Status

ambit is the authorization engine extracted from a production multi-tenant Django system and generalised to any organisation. It's early (alpha) but the core is battle-tested. The API may still shift before 1.0.

License

MIT — see LICENSE.

Release files for django-ambit 0.1.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-ambit 0.1.0
File Size Uploaded
django_ambit-0.1.0.tar.gz 24.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-ambit 0.1.0
File Interpreter ABI Platform
django_ambit-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 50.2 kB

Release files / django_ambit-0.1.0.tar.gz

Download URL django_ambit-0.1.0.tar.gz
Size 24.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9d595aa45e65e7b9ea574ba208d0862c0c78571c5a4f50f73d71fff2b7e81f9b
BLAKE2b-256 checksum
How to use checksums
017a42689e34af7ca7c115b9b54679ef593694e0c9a4f88a549ba5a111a43647
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / django_ambit-0.1.0-py3-none-any.whl

Download URL django_ambit-0.1.0-py3-none-any.whl
Size 25.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
19abb8c511d7c4851b4bd1ef44fb41de4b28eca2dbfaab4058cdac00a2b4fad2
BLAKE2b-256 checksum
How to use checksums
17c2d5997877d516f79539e0510c7b6052af4366d05ef0dfa648d1ec040d36e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

0.1.0 This release

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