Permissioning models¶
The primary purpose of the model-related parts of django-tutelary is
to associate django-tutelary actions and objects with Django model
classes. To do this, we use a metadata class member within our models
called TutelaryMeta and a class decorator called
permissioned_model, which can also be used as a normal function to
retrospectively add django-tutelary metadata to pre-existing Django
models. As well as actions associated with Django models, it is also
possible to register “free-floating” actions, which are a lot like
normal Django permissions.
Model metadata¶
In a similar way to how core Django uses the Meta class within
model classes to store model metadata, django-tutelary uses a
TutelaryMeta class to hold permissions-related metadata. The
TutelaryMeta class should have the following attributes:
perm_type- A string giving the type tag to use for building django-tutelary
object names for instances of the model. Used in conjunction with
the
path_fieldsattribute. path_fields- A sequence of model field names (as strings) to use for building
django-tutelary object names for instances of the model. Used in
conjunction with the
perm_typeattribute: the object name for a model instance is a sequence of items separated by forward slashes, starting with theperm_typelabel, then including the values of each of the model fields listed inpath_fields. Any entry inpath_fieldsreferring to a foreign key field in the model causes thepath_fieldsof the foreign key’s target model to be inserted into the object name. For instance, if we have aUsermodel withperm_type = 'user'andpath_fields = ('username',), then user objects would be represented in JSON policy documents asuser/iross,user/bjenkins, etc. (Apath_fieldsentry ofpkcan be used to refer to the model’s primary key.) actions- A sequence of action-label or (action-label, action-options)
pairs, where action-label is a string giving the name of an action
(e.g.
parcel.list,board.solder,page.create) and action-options is a dictionary of action options. Possible options are:description,permissions_objectanderror_message. Thedescriptionoption gives a human-readable free-text description of the action. Thepermissions_objectoption is eitherNone(indicating the the action is “free-floating” and not associated with any particular object) or is a string giving the name of a foreign key field in the model being configured. The result of setting thepermissions_objectoption is that permissions for the relevant action are not checked on the object of the class theactionslist is attached to, but to the alternative object referred to by the appropriate foreign key field. This capability is intended for use with “collective” actions. For example, if all “board” entities belong to a “project” entity, a “board.create” action should really be referred to theproject, not to any individual board – is the user allowed to create new boards for this project? Theerror_messageoption can be used to provide a custom error message to be passed in aPermissionDeniedexception if access to a view fails because of permissioning on an action.
Here’s an example of a model definition set up for use with django-tutelary:
@permissioned_model
class Party(models.Model):
project = models.ForeignKey(Project)
name = models.CharField(max_length=100)
class Meta:
ordering = ('project', 'name')
class TutelaryMeta:
perm_type = 'party'
path_fields = ('project', 'pk')
actions = [
('party.list',
{'description': "List existing parties",
'permissions_object': 'project'}),
('party.create',
{'description': "Create parties",
'permissions_object': 'project'}),
('party.detail',
{'description': "View details of a party",
'error_message': "Detail view is not allowed"}),
('party.edit',
{'description': "Update existing parties"}),
('party.delete',
{'description': "Delete parties"})
]
In this case, as well as the normal Django Meta class member, we
also set up a TutelaryMeta class member. This gives the
permission type of the model as party and the path_fields as
project and name – together these mean that django-tutelary
will refer to objects of class Party as party/.../<pk>, where
the ... will be filled based on the path_fields of class
Project (since project is a foreign key field here).
The actions list here defines five actions, two of which
(party.list and party.create) are “collective” actions,
meaning that they are permissioned on the project field of the
Party model.
The permissioned_model function¶
The permissioned_model function, defined in
tutelary.decorators can be used either as a class decorator for a
Django model class containing a TutelaryMeta metadata class, or
can be used as a normal function to add django-tutelary metadata to an
existing model class.
As a class decorator, permissioned_model is used as follows:
from tutelary.decorators import permissioned_model
from django.db import models
...
@permissioned_model
class AModel(models.Model):
# Field definitions
...
class TutelaryMeta:
perm_type = ...
path_fields = ...
actions = ...
As a normal function, permissioned_model must be passed a Django
model class and keyword arguments giving the TutelaryMeta
attributes perm_type, path_fields and actions:
permissioned_model(AnExistingModel,
perm_type=..., path_fields=..., actions=...)
Action registration¶
Actions listed in the TutelaryMeta metadata or passed in the
actions argument to the permissioned_model function are
automatically associated with a Django model. In some cases, it may
be useful also to have “free-floating actions” that are not associated
with a particular model. These actions are more like what the default
Django permissioning system called “permissions” and are useful for
controlling access to views for summary pages or other resources that
aren’t directly tied to Django models.
To register a free-floating action, use the Action.register class
method. For example:
Action.register('statistics')
After this call, the action name statistics can be used in
permissions queries and in the permission_required attribute for
PermissionsRequiredMixin.
Examples¶
Suppose that we have a pair of related models, Organization and
Project, with Project instances belonging to an
Organization so that Project has a foreign key to
Organization. We can set up these models with django-tutelary
permissions as follows:
@permissioned_model
class Organization(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
class TutelaryMeta:
perm_type = 'organization'
path_fields = ('name',)
actions = [
('org.list', {'permissions_object': None}),
('org.create', {'permissions_object': None}),
'org.delete'
]
@permissioned_model
class Project(models.Model):
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization)
class Meta:
ordering = ('organization', 'name')
class TutelaryMeta:
perm_type = 'project'
path_fields = ('organization', 'name')
actions = [
('project.list',
{'permissions_object': 'organization'}),
('project.create',
{'permissions_object': 'organization'}),
'project.delete'
]
In policies, Organization objects are then represented as
organization/<org-name> and projects as
project/<org-name>/<project-name>. Using the organization
foreign key field in the path_fields metadata attribute of the
Project model causes the path_fields from the Organization
model to be spliced into the object names used for Project
instances.
To add django-tutelary permissioning metadata to an existing Django
model, such as the User model, we can do something like this:
permissioned_model(
User, perm_type='user', path_fields=['username'],
actions=[
('user.list',
{'description': "Can list existing users",
'permissions_object': None}),
('user.detail',
{'description': "Can view details of a user"}),
('user.create',
{'description': "Can create users",
'permissions_object': None,
'allow_get': True}),
('user.edit',
{'description': "Can update existing users",
'allow_get': True}),
('user.delete',
{'description': "Can delete users",
'allow_get': True})
]
)