Developer Reference¶
Permissions System¶
Overview¶
Permissions system is implemented by security.permissions module. It is designed to be as
low-ceremony as possible, but some boilerplate is unavoidable.
Package django-guardian is used to handle per-object permissions, so it’s possible to grant
user extra permissions for some specific objects instead of all objects of particular type.
Support for the concept of object ownership is provided. If a Django model defines
is_owner(self, user_or_group) method, any user or group that passes this method (i.e. True
or equivalent value is returned) will be considered that object’s owner. Object owners always have
full permissions for the object, including the permission to manage other users’ permissions.
Essentially they’re equivalent to superusers, but their authority only covers the objects they own.
Also, owner status does not grant add permission (which is considered model-level permission,
not object-level).
Here’s inventory.models.InventoryObject.is_owner() method as example:
def is_owner(self, applicant): if isinstance(applicant, Group): return applicant == self.assigned_group if isinstance(applicant, User): return (applicant == self.assigned_user) or \ self.assigned_group and (applicant in self.assigned_group.user_set.all()) return False
Essentially, user owns an InventoryObject if he’s recorded as InventoryObject.assigned_user,
or alternatively, if he’s a member of a group that’s recorded as InventoryObject.assigned_group.
A group must match InventoryObject.assigned_group to be considered it’s owner.
Object’s Permissions Management¶
Permissions management consists of two components: object permissions overview table and object permissions form.
To display object permissions in HTML format, use the following boilerplate in your Django template:
{% include 'common/permissions_view.html' with object=your_object %}
This template expects that object is available in template context. If your object is already
available in context with this name, with object=your_object part can be skipped.
Permissions overview table can be placed anywhere, though it’s advised to put it on a separate tab.
Permissions management form is provided as extension of racks.crud package. Normally, it is
automatically accessible for any model which explicitly defines manage_yourmodelname permission:
class InventoryObject(models.Model): class Meta: permissions = ( ('view_ipmi', 'View IPMI'), ('edit_ipmi', 'Edit IPMI'), ('manage_inventoryobject', 'Manage Access'), )
Alternatively, you can add 'manage' to the model’s list of default permissions and let Django
handle permission name generation:
class InventoryObject(models.Model): class Meta: default_permissions = ('add', 'change', 'delete', 'manage')
For as long as there’s a manage permission defined for a model, CRUD viewset for that model will
automatically enable access page, and “Manage Permissions” button will be displayed below the
permissions overview table (disabled unless authenticated user is superuser, current object’s owner
or has manage permission set for current object).
For models which do not define their own manage permission, permissions management form can
still be enabled by explicitly setting access_view attribute of a viewset to
racks.crud.AccessView:
from racks import crud class MyModelViewSet(crud.ViewSet): access_view = crud.AccessView
To disable permissions form for a model which does define it’s own manage permission, set
viewset’s disable_access_view attribute to True:
class MyModelViewSet(crud.ViewSet): disable_access_view = True
Protected Objects and Protected Fields¶
Module security.permissions provides functionality to restrict access to model’s fields based
on user’s permissions for specific model instance. That functionality is not fully transparent and
must be used explicitly. Essentially, you’re creating a security wrapper object for your model’s
instance, and use that wrapper like you would use the wrapped object itself.
To create a security-wrapped object, use the following syntax:
from security.permissions import wrap protected_obj = wrap(obj, user)
And here’s an example of a model with protected fields:
from security.permissions import require_owner class MyModel(models.Model): public_field = CharField(max_length=50) readonly_field = CharField(max_length=50) secret_field = CharField(max_length=50) owner_field = CharField(max_length=50) class Meta: permissions = ( ('manage_mymodel', 'Manage Access'), ('view_secret', 'Read Secret Field'), ('edit_secret', 'Write Secret Field'), ) protected_fields = { 'readonly_field': (True, False), 'secret_field': ('read_secret', 'write_secret'), 'owner_field': (True, require_owner), }
This model defines four fields, standard manage permission (so permissions management form will
be enabled), two custom permissions and a special protected_fields dictionary which contains
the list of all protected fields and their read/write prerequisites.
First field in this model is public_field. It is not mentioned in protected_fields, and as
such receives no protection. Any user will be able to access this field’s value through the
security wrapper, and any user with change permission for the object will be able to edit this
field’s value (unless it’s protected by other means, like declaring it read-only in viewset or
form).
Second field is readonly_field. It is mentioned in protected_fields and has quite simple
prerequisites: read is always True and write is always False.
Third field is secret_field and it defines string read/write prerequisites. These are
interpreted as follows:
- Only users with “read_secret” permission on MyModel object may read
secret_fieldvalue of that object.- Only users with “edit_secret” permission on MyModel object may modify
secret_fieldvalue of that object.
Finally, the owner_field provides an example of a function callback being used as access
validator. It’s read permission is always True and thus available to any user. However to modify
this field, user must pass require_owner(obj, user) check.
security.permissions module provides three predefined callbacks that can be used in field
permissions:
require_authreturns True for all objects for as long as user is authenticated.require_ownerreturns True if user owns the object.require_superuserreturns True for all objects for if user is a superuser.
Custom callback functions or callable objects can be used if necessary.
Managing Permissions Programmatically¶
Permissions for specific object can be set with shortcut functions provided by django-guardian:
from django.contrib.auth.models import User from myapp.models import MyModel from guardian.shortcuts import assign_perm, remove_perm me = User.objects.get(username='myname') obj = MyModel.objects.get(pk=11) assign_perm('myapp.delete_mymodel', obj) remove_perm('myapp.manage_mymodel', obj)
To check user’s permissions on particular object, it is recommended to use get_permissions()
shortcut function from security.permissions module:
from django.contrib.auth.models import User from myapp.models import MyModel from security.permissions import wrap, get_permissions me = User.objects.get(username='myname') obj = MyModel.objects.get(pk=11) perms = get_permissions(obj, me) if perms.change: log.info('I can modify this object!') if perms.secret_field.view: log.info('I can view secret field value of this object!') # Note that permissions are also available from security-wrapped object: protected_obj = wrap(obj, me) if protected_obj.permissions.change: log.info('I can still modify this object!')
Finally, user’s permissions for specific object can be checked with standard Django syntax,
monkey-patched by django-guardian package:
me = User.objects.get(username='myname') obj = MyModel.objects.get(pk=11) if me.has_perm('change_mymodel', obj): # (...)
The last method is not recommended though, as it doesn’t handle object ownership. Also, with
get_permissions() you can check manage permission on all models, even those that do not
define their own manage_modelname permission.