Django authorization add-on for multiple organizations and object-level permission settings
Project description
# Django Trusts
##### Django authorization add-on for multiple organizations and object-level permission settings
### Introduction
`django-trusts` is a add-on to Django's (>= 1.7) builtin<sup>[1](#footnote1)</sup> authorization. It strives to be a **minimal** implementation, adding only a single concept, `trust`, to enable maintainable per-object permission settings for a django project that hosts users of multiple organizations<sup>[2](#footnote2)</sup> with a single user namespace.
A `trust` is a relationship whereby content access is permitted by the creator [`settlor`] to specific user(s) [`trustee`(s)] or `group`(s). Content can be an instance of a `Content` subclass, or of an existing model via a junction table. Access to multiple content can be permitted by a single `trust` for maintainable permssion settings. Django's builtin model, `group`, is supported and can be used to define reusuable permissions for a `group` of `user`s.
`django-trusts` also strives to be a **scalable** solution. Permissions checking is offloaded to the database by design, and the implementation minimizes database hits. Permissions are cached per `trust` for the lifecycle of `request user`. If a project's request lifecycle resolves most checked content to one or few `trusts`, which should be very typically the case, this design should be a winner in term of performance. Permissions checking is done against an individual content or a `QuerySet`.
`django-trusts` supports Django's builtins User models `has_perms()` / `has_perms()` and does not provides any in-addition.
<sup id="footnote1">[1] See, [Django Object Permissions](https://github.com/djangoadvent/djangoadvent-articles/blob/master/1.2/06_object-permissions.rst)</sup>
<sup id="footnote2">[2] Even `django-trusts` is incepted to support multiple organizations in a single project, it does not define or restrict oraganization model design. One natural approach is to model an organization as a special user. With this arrangment, an organization can be the `settlor` of `trusts`. Alternative approach is to create another model for organization. With this arrangment, the `settlor` of `trust`s can simple be the creating user and one might or might not have all permissions of organization's content.</sup>
---
### Usages
##### Installation
1. Put `django-trusts` in PYTHONPATH.
2. Replace `AUTHENTICATION_BACKENDS` in `settings.py`
```python
AUTHENTICATION_BACKENDS = (
'trusts.backends.TrustModelBackend',
)
```
##### Implementation
Alternative 1, use `Content`
```python
# app/models.py
from django.db import models
from trusts.models import Content
class Receipt(Content, models.Model):
account = models.ForeignKey(Account, null=True)
merchant = models.ForeignKey(Merchant, null=True)
# ... other field
```
Alternative 2, use `Junction`
```python
# app/models.py
from django.db import models
from django.contrib.auth.models import Group
from trusts.models import Junction
# New Junction to model that is not under your control
class GroupJunction(Junction, models.Model):
# field name must be named as `content` and unique=True, null=False, blank=False
content = models.ForeignKey(django.contrib.auth.models.Group, unique=True, null=False, blank=False)
```
##### Permission Assignments
```python
from django.contrib.auth.models import User, Group, Permission
from trusts.models import Trust
# Helper function
def grant_user_group_permssion_to_model(user, group_name, model_name, code='change', app='app'):
# Django's auth permission mechanism, nothing specific to `django-trust`
# get perm by name
perm = Permission.objects.get_by_natural_key('change_%s' % model_name, app, model_name)
group = Group.objects.get(name=group_name)
# connect them
user.groups.add(group)
perm.group_set.add(group)
# user.has_perm('%s.change_%s' % (app, model_name)) ==> True
# user.has_perm('%s.change_%s' % (app, model_name), obj) ==> False
# View
def create_receipt_object_for_user(request, title, details):
trust = Trust.objects.get_or_create_settlor_default(settlor=request.user)
content = Receipt(trust=trust, title=title, details=details)
content.save()
model_name = receipt.__class__.__name__.lower()
perm = Permission.objects.get_by_natural_key('%_%' % ('change', model_name), 'app', model_name)
tup = TrustUserPermission(trust=trust, entity=request.user, permission=perm)
tup.save()
# request.user.has_perm('%s.change_%s' % ('app', model_name), content) ==> True
# View
def give_user_change_permission_on_existing_group(request, user, group_name):
grant_user_permssion_to_model(request.user, group_name, code='change', app='auth')
group = Group.objects.get(name=group_name)
junction = GroupJunction(trust=trust, content=group)
junction.save()
# request.user.has_perm('auth.change_group', group) ==> True
```
##### Inheritance
Dependent model can inherit Trust from a related model. Such class need to be registered manually
with `fieldlookup` specified.
Consider ReceiptImage is a dependent model of Receipt, and ReceiptImageMeta is a dependent
model of ReceiptImage. The following code makes both model available for permission checking.
```python
Content.register_content(ReceiptImage, '%s__image' % Content.get_content_fieldlookup('app.Receipt'))
Content.register_content(ReceiptImageMeta, '%s__image' % Content.get_content_fieldlookup(ReceiptImage))
```
##### Permissions Checking
```python
def check_permission_to_a_specific_receipt(request, receipt_id):
return request.user.has_perm('app.change_receipt', Receipt.objects.get(id=receipt_id))
def check_permission_to_a_specific_group(request, group_id):
return request.user.has_perm('app.change_group', Group.objects.get(id=group_id))
```
##### Decorators
```python
from trusts.decorators import permission_required
from app.models import Xyz
@permission_required('app.change_xyz', fieldlookups_kwargs={'pk': 'xyz_id'})
def edit_xyz_view(request, xyz_id):
# ...
pass
@permission_required('app.change_xyz', fieldlookups_kwargs={'pk': 'xyz_id'})
@permission_required('app.read_project', fieldlookups_kwargs={'pk': 'project_id'})
def move_xyz_to_project_view(request, xyz_id, project_id):
# ...
pass
```
##### Permission Conditions
In additional `Group` and `Permission` based check, object level condition can be used.
For example, a user should be let to modify a `Receipt` if he was the creation owner. In
this case, a condition code should be registered.
```python
Content.register_permission_condition(Receipt, 'own', lambda u, p, o: u == o.user)
```
To check `own` permission, a colon and the condition name should be added after the condition name:
```python
def check_permission_to_a_specific_receipt(request, receipt_id):
return request.user.has_perm('app.change_receipt:own', Receipt.objects.get(id=receipt_id))
```
Condition can also be used with decorator as follow:
```python
@permission_required('app.change_receipt:own', fieldlookups_kwargs={'pk': 'pk'})
def edit_receipt_view(request, pk):
# ...
pass
```
##### Customization
The folllowing settings (django.conf) allow for customization and adaptation.
###### Initial options
Warning: Changing the options below affects the construction of foreign keys and many-to-many
relationships. If you intend to set these options, you should set it before creating any
migrations or running `manage.py migrate` for the first time, and should not be changed afterward.
Changing this setting after you have tables created is not supported by `makemigrations` and
will result in you having to manually fix your schema, port your data from the old user table,
and possibly manually reapply some migrations.
* TRUSTS_ENTITY_MODEL -- The model name for `settlors` and `trustees` field. Must be specified in contenttypes format, ie, 'app_label.model_name'. (default: `settings.AUTH_USER_MODEL`.)
* TRUSTS_GROUP_MODEL -- The model name for `groups` field. (default: `auth.Group`)
* TRUSTS_PERMISSION_MODEL -- The model name for `Permission`. (default: `auth.Permission`)
* TRUSTS_CREATE_ROOT -- A boolean set to True indicates root Trust model object to be created during the initial migration. (default: True)
* TRUSTS_ROOT_PK -- The `pk` of the root trust model object. (default: 1)
* TRUSTS_ROOT_SETTLOR -- The `pk` of settlor of the root trust object. (default: None)
* TRUSTS_DEFAULT_SETTLOR -- The default value for `settlor` field on Trust model. (default: None)
* TRUSTS_ROOT_TITLE -- The title of root rust object. (default: "In Trust We Trust")
##### Django authorization add-on for multiple organizations and object-level permission settings
### Introduction
`django-trusts` is a add-on to Django's (>= 1.7) builtin<sup>[1](#footnote1)</sup> authorization. It strives to be a **minimal** implementation, adding only a single concept, `trust`, to enable maintainable per-object permission settings for a django project that hosts users of multiple organizations<sup>[2](#footnote2)</sup> with a single user namespace.
A `trust` is a relationship whereby content access is permitted by the creator [`settlor`] to specific user(s) [`trustee`(s)] or `group`(s). Content can be an instance of a `Content` subclass, or of an existing model via a junction table. Access to multiple content can be permitted by a single `trust` for maintainable permssion settings. Django's builtin model, `group`, is supported and can be used to define reusuable permissions for a `group` of `user`s.
`django-trusts` also strives to be a **scalable** solution. Permissions checking is offloaded to the database by design, and the implementation minimizes database hits. Permissions are cached per `trust` for the lifecycle of `request user`. If a project's request lifecycle resolves most checked content to one or few `trusts`, which should be very typically the case, this design should be a winner in term of performance. Permissions checking is done against an individual content or a `QuerySet`.
`django-trusts` supports Django's builtins User models `has_perms()` / `has_perms()` and does not provides any in-addition.
<sup id="footnote1">[1] See, [Django Object Permissions](https://github.com/djangoadvent/djangoadvent-articles/blob/master/1.2/06_object-permissions.rst)</sup>
<sup id="footnote2">[2] Even `django-trusts` is incepted to support multiple organizations in a single project, it does not define or restrict oraganization model design. One natural approach is to model an organization as a special user. With this arrangment, an organization can be the `settlor` of `trusts`. Alternative approach is to create another model for organization. With this arrangment, the `settlor` of `trust`s can simple be the creating user and one might or might not have all permissions of organization's content.</sup>
---
### Usages
##### Installation
1. Put `django-trusts` in PYTHONPATH.
2. Replace `AUTHENTICATION_BACKENDS` in `settings.py`
```python
AUTHENTICATION_BACKENDS = (
'trusts.backends.TrustModelBackend',
)
```
##### Implementation
Alternative 1, use `Content`
```python
# app/models.py
from django.db import models
from trusts.models import Content
class Receipt(Content, models.Model):
account = models.ForeignKey(Account, null=True)
merchant = models.ForeignKey(Merchant, null=True)
# ... other field
```
Alternative 2, use `Junction`
```python
# app/models.py
from django.db import models
from django.contrib.auth.models import Group
from trusts.models import Junction
# New Junction to model that is not under your control
class GroupJunction(Junction, models.Model):
# field name must be named as `content` and unique=True, null=False, blank=False
content = models.ForeignKey(django.contrib.auth.models.Group, unique=True, null=False, blank=False)
```
##### Permission Assignments
```python
from django.contrib.auth.models import User, Group, Permission
from trusts.models import Trust
# Helper function
def grant_user_group_permssion_to_model(user, group_name, model_name, code='change', app='app'):
# Django's auth permission mechanism, nothing specific to `django-trust`
# get perm by name
perm = Permission.objects.get_by_natural_key('change_%s' % model_name, app, model_name)
group = Group.objects.get(name=group_name)
# connect them
user.groups.add(group)
perm.group_set.add(group)
# user.has_perm('%s.change_%s' % (app, model_name)) ==> True
# user.has_perm('%s.change_%s' % (app, model_name), obj) ==> False
# View
def create_receipt_object_for_user(request, title, details):
trust = Trust.objects.get_or_create_settlor_default(settlor=request.user)
content = Receipt(trust=trust, title=title, details=details)
content.save()
model_name = receipt.__class__.__name__.lower()
perm = Permission.objects.get_by_natural_key('%_%' % ('change', model_name), 'app', model_name)
tup = TrustUserPermission(trust=trust, entity=request.user, permission=perm)
tup.save()
# request.user.has_perm('%s.change_%s' % ('app', model_name), content) ==> True
# View
def give_user_change_permission_on_existing_group(request, user, group_name):
grant_user_permssion_to_model(request.user, group_name, code='change', app='auth')
group = Group.objects.get(name=group_name)
junction = GroupJunction(trust=trust, content=group)
junction.save()
# request.user.has_perm('auth.change_group', group) ==> True
```
##### Inheritance
Dependent model can inherit Trust from a related model. Such class need to be registered manually
with `fieldlookup` specified.
Consider ReceiptImage is a dependent model of Receipt, and ReceiptImageMeta is a dependent
model of ReceiptImage. The following code makes both model available for permission checking.
```python
Content.register_content(ReceiptImage, '%s__image' % Content.get_content_fieldlookup('app.Receipt'))
Content.register_content(ReceiptImageMeta, '%s__image' % Content.get_content_fieldlookup(ReceiptImage))
```
##### Permissions Checking
```python
def check_permission_to_a_specific_receipt(request, receipt_id):
return request.user.has_perm('app.change_receipt', Receipt.objects.get(id=receipt_id))
def check_permission_to_a_specific_group(request, group_id):
return request.user.has_perm('app.change_group', Group.objects.get(id=group_id))
```
##### Decorators
```python
from trusts.decorators import permission_required
from app.models import Xyz
@permission_required('app.change_xyz', fieldlookups_kwargs={'pk': 'xyz_id'})
def edit_xyz_view(request, xyz_id):
# ...
pass
@permission_required('app.change_xyz', fieldlookups_kwargs={'pk': 'xyz_id'})
@permission_required('app.read_project', fieldlookups_kwargs={'pk': 'project_id'})
def move_xyz_to_project_view(request, xyz_id, project_id):
# ...
pass
```
##### Permission Conditions
In additional `Group` and `Permission` based check, object level condition can be used.
For example, a user should be let to modify a `Receipt` if he was the creation owner. In
this case, a condition code should be registered.
```python
Content.register_permission_condition(Receipt, 'own', lambda u, p, o: u == o.user)
```
To check `own` permission, a colon and the condition name should be added after the condition name:
```python
def check_permission_to_a_specific_receipt(request, receipt_id):
return request.user.has_perm('app.change_receipt:own', Receipt.objects.get(id=receipt_id))
```
Condition can also be used with decorator as follow:
```python
@permission_required('app.change_receipt:own', fieldlookups_kwargs={'pk': 'pk'})
def edit_receipt_view(request, pk):
# ...
pass
```
##### Customization
The folllowing settings (django.conf) allow for customization and adaptation.
###### Initial options
Warning: Changing the options below affects the construction of foreign keys and many-to-many
relationships. If you intend to set these options, you should set it before creating any
migrations or running `manage.py migrate` for the first time, and should not be changed afterward.
Changing this setting after you have tables created is not supported by `makemigrations` and
will result in you having to manually fix your schema, port your data from the old user table,
and possibly manually reapply some migrations.
* TRUSTS_ENTITY_MODEL -- The model name for `settlors` and `trustees` field. Must be specified in contenttypes format, ie, 'app_label.model_name'. (default: `settings.AUTH_USER_MODEL`.)
* TRUSTS_GROUP_MODEL -- The model name for `groups` field. (default: `auth.Group`)
* TRUSTS_PERMISSION_MODEL -- The model name for `Permission`. (default: `auth.Permission`)
* TRUSTS_CREATE_ROOT -- A boolean set to True indicates root Trust model object to be created during the initial migration. (default: True)
* TRUSTS_ROOT_PK -- The `pk` of the root trust model object. (default: 1)
* TRUSTS_ROOT_SETTLOR -- The `pk` of settlor of the root trust object. (default: None)
* TRUSTS_DEFAULT_SETTLOR -- The default value for `settlor` field on Trust model. (default: None)
* TRUSTS_ROOT_TITLE -- The title of root rust object. (default: "In Trust We Trust")
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
django-trusts-0.9.3.tar.gz
(13.5 kB
view hashes)