Extended, flexible and powerful object-level and rule-driven permissions for Django & Django REST framework
Project description
permissible is a module to make it easier to configure object-level permissions,
and to help unify the different places performing permissions checks (including DRF
and Django admin) to create a full permissions check that can work without any
further architectural pondering.
It is built on top of django-guardian but can be easily configured for other object-level libraries.
Introduction
This module allows us to define permission requirements in our Models (similarly to how django-rules does it in Model.Meta). Given that different view engines (e.g. DRF vs Django's admin) have different implementations for checking permissions, this allows us to centralize the permissions configuration and keep the code clear and simple. This approach also allows us to unify permissions checks across both Django admin and DRF (and indeed any other place you use PermissibleMixin).
Installation
-
Install the package (use of
django-guardianis optional but needed for most Features below):pip install permissible # Django, djangorestframework pip install permissible[guardian] # Same + django-guardian, djangorestframework-guardian
-
If using
django-guardian, make sure to add theObjectPermissionsBackendto yourAUTHENTICATION_BACKENDS(otherwise enable object permissions in your own desired way):AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend', )
Features
Feature 1: Consistent permissions configuration
In its simplest form, permissible can be used just for its permissions
configuration. This has no impact on your database, and does not rely on any
particular object-level permissions library. (It does require one; we prefer
django-guardian.)
Here, we add the PermissibleMixin to each model we want to protect, and
define "permissions maps" that define what permissions are needed for each action
that is taken on an object in the model (e.g. a "retrieve" action on a "survey").
(We can also use classes like PermissibleDefaultPerms to define good default
permission maps for our models.)
With the permissions configured, now we can force different views to use them:
- If you would like the permissions to work for API views (via
django-rest-framework): AddPermissiblePermsto thepermission_classesfor the viewsets for our models - If you would like the permissions to work in the Django admin: Add
PermissibleAdminMixinto the admin classes for our models
That's it. Actions are now protected by permissions checks. But there is no easy way to create the permissions in the first place. That's where the next two features come in.
Feature 2: Simple, role-based permissions assignment using "domain" models (RBAC)
The permissible library can also help automatically assign permissions based on
certain "domain" models. The domain model is the model we should check permissions
against. For instance, the domain model for a "project file" might be a "project",
in which case having certain permissions on the "project" would confer other
permissions for the "project files", even though no specific permission exists
for the "project file". Loosely speaking, a domain "owns" other models. The concept of
"domains" is fairly consistent in RBAC.
Of course, it's easy to link a "project" to a "project file" through a foreign key.
But permissible solves the problem of tying this to the Django Group model,
which is what we use for permissions, according to roles.
Each resulting Group (managed on the backend) corresponds to a single role.
To accomplish this, permissible provides 3 base model classes that you should use:
PermDomain: Make the domain model (e.g.Team) derive fromPermDomainPermDomainRole: Create a new model that derives fromPermDomainRoleand has aForeignKeyto the domain model - and definesROLE_DEFINITIONSPermDomainMember: Create a new model that derives fromPermDomainMemberand has aForeignKeyto the domain model (this model automatically adds and removes records when a user is a member of the appropriatePermDomainRole)
Then, use DomainOwnedPermMixin on any models
You can then simply adjust your permissions maps in PermissibleMixin to
incorporate checking of the domain model for permissions. See the documentation for
PermDef and PermissibleMixin.has_object_permissions for info and examples.
Remember: PermDomain is the core model on which roles are defined (eg Project or
Team) and PermDomainRole is the model that represents a single role (and
therefore a single Django auth.Group) for a single PermDomain - eg Team Admins.
The PermDomainRole.ROLE_DEFINITIONS defines what object permissions will be
given to each role/group for every PermDomain.
You can also use PermDomainAdminMixin to help you manage the PermDomain records
and the subsequent role-based access control:
Feature 3: Assignment on record creation
permissible can automatically assign object permissions on object creation,
through use of 3 view-related mixins:
admin.PermissibleObjectAssignMixin(for admin classes - give creating user all permissions)serializers.PermissibleObjectAssignMixin(for serializers - give creating user all permissions)serializers.PermDomainObjectAssignMixin(for serializers for domain models like "Team" or "Project - add creating user to all domain model's Groups)
NOTE: this feature is dependent on django-guardian, as it uses the assign_perm
shortcut. Also, admin.PermissibleObjectAssignMixin extends the
ObjectPermissionsAssignmentMixin mixin from djangorestframework-guardian.
Core concepts
PermissibleMixin:
- Add
PermissibleMixinto any model you want to protect - Define
global_action_perm_mapandobj_action_perm_mapon each model, otherwise use mixins inpermissible.models.permission_mixinthat define them out of the box (egPermissibleDenyPerms,PermissibleDefaultPerms)- If defining
global_action_perm_mapandobj_action_perm_mapon your own, remember that (just like Django's permission checking normally) both global and object permissions must pass - Both
global_action_perm_mapandobj_action_perm_mapuse the same format: a map of actions to a list ofPermDefobjects - Actions are the same as those defined by DRF (for convenience):
list,create,retrieve,update,partial_update,destroy, and any others you want to define and check later
- If defining
- See below for
PermDefexplanation
PermDef
- A simple data structure to hold permissions configuration. Each action inside
global_action_perm_mapandobj_action_perm_maphas a list ofPermDef - Each
PermDefis defined with the following:short_perm_codes: A list of short permission codes, e.g. ["view", "change"]obj_getter: A function/str that takes the object we are checking, and returns a potentially different object on whom we will actually check permissions. (For instance if you want to check a related parent object to determine whether the user has access to the child object. This is critical for PermDomain behavior.)condition_checker: An ADDITIONAL check, on top of the usual permissions-checking (user.has_perms).
Example flow
- The application has the following models:
User(inherits Django's base abstract user model)Group(Django's model)Team(inheritsPermDomain)TeamGroup(inheritsPermDomainRole)TeamUser(inheritsPermDomainMember)TeamInfo(contains a foreign key toTeam)
Create a team
- A new team is created (via Django admin), which triggers the creation of appropriate
groups and assignment of permissions:
Team.save()creates severalTeamGrouprecords, one for each possible role (e.g. member, owner)- For each
TeamGroup, thesave()method triggers the creation of a newGroup, and assigns permissions to each of these groups, in accordance withPermDomainRole.role_definitions:TeamGroupwith "Member" role is given no permissionsTeamGroupwith "Viewer" role is given "view_team" permissionTeamGroupwith "Contributor" role is given "contribute_to_team" and "view_team" permissionsTeamGroupwith "Admin" role is given "change_team", "contribute_to_team" and "view_team" permissionsTeamGroupwith "Owner" role is given "delete", "change_team", "contribute_to_team" and "view_team" permissions- (NOTE: this behavior can be customized)
- Note that no one is given permission to create
Teamto begin with - it must have been created by a superuser or someone who was manually given such permission in the admin
Create a user
- A new user is created (via Django admin), and added to the relevant groups (e.g. members, admins)
- A
TeamUserrecord is added automatically when this user joins those groups. Note that if the user is removed from ALL of those groups for thisTeam, they will automatically have theirTeamUserrecord removed.
Edit a team-related record
- The user tries to edit a
TeamInforecord, either via API (django-rest-framework) or Django admin, triggering the following checks:- View/viewset checks global permissions
- View/viewset checks object permissions:
- Checking object permission directly FAILS (as this user was not given any permission for this object in particular)
- Checking permission for domain object (i.e. team) SUCCEEDS if the user was added to the correct groups
Create a team-related record
- The user tries to create a
TeamInforecord, either via API (django-rest-framework) or Django admin, triggering the following checks:- View/viewset checks global permissions
- View/viewset checks creation permissions:
- Checking object permission directly FAILS as this object doesn't have an ID yet, so can't have any permissions associated with it
- Checking permission for domain object (i.e. team) SUCCEEDS if the user was added to the correct groups
- View/viewset does not check object permission (this is out of our control, and makes sense as there is no object)
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 permissible-0.7.0.tar.gz.
File metadata
- Download URL: permissible-0.7.0.tar.gz
- Upload date:
- Size: 46.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a616d66647830b0ccb9b55ff910ccf8681eb2efbcb6e88e94afa1fc0250eac76
|
|
| MD5 |
6465c7db9a045401ecdcfb3fd2208c5f
|
|
| BLAKE2b-256 |
b0f4dc7910f971df1a6aee4a18b5abc96c5a26351befb346db3c9983b8d2ec63
|
Provenance
The following attestation bundles were made for permissible-0.7.0.tar.gz:
Publisher:
build.yml on gaussian/permissible
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
permissible-0.7.0.tar.gz -
Subject digest:
a616d66647830b0ccb9b55ff910ccf8681eb2efbcb6e88e94afa1fc0250eac76 - Sigstore transparency entry: 175189136
- Sigstore integration time:
-
Permalink:
gaussian/permissible@a14c1258e8de1d3c87b78d03a5f57e7631cb2837 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gaussian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@a14c1258e8de1d3c87b78d03a5f57e7631cb2837 -
Trigger Event:
pull_request_target
-
Statement type:
File details
Details for the file permissible-0.7.0-py3-none-any.whl.
File metadata
- Download URL: permissible-0.7.0-py3-none-any.whl
- Upload date:
- Size: 44.3 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 |
c7da34d02c7887ef18072a90db2574c902a664823c50e6ecd5367f630be10bfb
|
|
| MD5 |
944e536351b607cdac53ae4fa1cf7723
|
|
| BLAKE2b-256 |
d794d89ef2ab9eaa91dffd4552aa3d67afe0fcf56c4da247bbca2ed98126f41b
|
Provenance
The following attestation bundles were made for permissible-0.7.0-py3-none-any.whl:
Publisher:
build.yml on gaussian/permissible
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
permissible-0.7.0-py3-none-any.whl -
Subject digest:
c7da34d02c7887ef18072a90db2574c902a664823c50e6ecd5367f630be10bfb - Sigstore transparency entry: 175189137
- Sigstore integration time:
-
Permalink:
gaussian/permissible@a14c1258e8de1d3c87b78d03a5f57e7631cb2837 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gaussian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@a14c1258e8de1d3c87b78d03a5f57e7631cb2837 -
Trigger Event:
pull_request_target
-
Statement type: