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_field
value of that object.- Only users with “edit_secret” permission on MyModel object may modify
secret_field
value 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_auth
returns True for all objects for as long as user is authenticated.require_owner
returns True if user owns the object.require_superuser
returns 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.