This post first appeared on Gevin’s blog

Implementation of Djangos permission mechanism

Reproduced without permission from Gevin

Djangos permission mechanism implementation

The techniques mentioned in this article have been applied to my blog system MayBlog, which is based on Django 1.8+.

1. Overview of Django permissions

Permissions can constrain user behavior, control what is displayed on a page, and make apis more secure and flexible. With good authority mechanism, can make the system more powerful and robust. Therefore, it is necessary to clarify the Django permissions mechanism based on Django development.

1.1 Django Permission Control

Djangos implements the permission mechanism with user, group, and permission. The permission mechanism grants permission belonging to A model to A user or group, which can be interpreted as global permission. If user A has writable permission on model B, Then A can modify all the objects of Model B. The same is true for group permissions. If group C is given writable permissions to Model B, all users belonging to group C can modify all instances of Model B.

This permission mechanism only solves some simple application requirements. However, in most application scenarios, a more detailed permission mechanism is required. Taking the blog system as an example, the users of the blog system can be divided into four user groups: “administrator”, “editor”, “author” and “reader”. Blog system administrators and editors have the authority to view, modify and delete all articles, authors can only modify and delete their own articles, and readers only have the authority to read. Administrator, editor and reader permissions, we can use global permissions to do control, and for the author, global permissions can not meet the needs, only through global permissions, or allow the author to edit does not belong to their own articles, or let the author can not modify their own articles.

In this case, Djangos built-in permission mechanism doesn’t suffice, so you need to introduce another, more subtle permission mechanism: object Permission.

Object Permission is an Object granularity Permission mechanism that allows authorization for each specific Object. If model B has three instances B1, B2, and B3, and if we grant user A writable access to B1, user A can modify the B1 object, but not user B2 and B3. The same is true for group C. If the writable permission of B2 is granted to group C, all users belonging to group C can modify B2, but not B1 and B3. With Django’s own permission mechanism and Object permission, author permission is easily controlled in the blog system. The system does not allow authors to edit articles globally.

Django actually contains the framework of Object Permission, but there is no concrete implementation. The implementation of object Permission requires the help of the third-party app Django-Guardian. In development, we can simply call the method encapsulated by Django Guradian.

1.2 Django Permissions

Django stores permission items with permission objects. Each model has three permissions by default: Add Model, Change Model, and Delete Model. For example, if you define a model named “Car”, after defining the Car, three permissions are automatically created: add_car, change_car, and delete_car. Django also allows custom permissions. For example, we can create new permission items for Car: drive_car, clean_car, fix_car, and so on

Note that permission always corresponds to Model, and we cannot create/assign permissions to an object if it is not an instance of Model.

2. Use Django’s built-in permissions mechanism

2.1 the Permission

As mentioned above, Django adds add, change, and delete permissions to each model by default. Custom permissions can be added manually when we define a model:

class Task(models.Model):.class Meta:
        permissions = (
            ("view_task"."Can see available tasks"),
            ("change_task_status"."Can change the status of tasks"),
            ("close_task"."Can remove a task by setting its status as closed"),Copy the code

Each permission is django. Contrib. Auth. Permission type instance, the type contains three fields name, codename and content_type, Where content_type reflects which model permission belongs to, codename is used as view_task above, and name is the description of permission. The name is displayed by default when permission is printed to the screen or page

Creating custom permissions in model, from the perspective of system development, can be understood as creating the built-in permissions of the system. If the requirements involve users creating custom permissions when using the system, the following methods should be used:

from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(codename='can_publish',
                                       name='Can Publish Posts',
                                       content_type=content_type)Copy the code

2.2 User Permission Management

The user_Permission field of the User object manages User permissions:

myuser.user_permissions = [permission_list]
myuser.user_permissions.add(permission, permission, ...) # add permission
myuser.user_permissions.remove(permission, permission, ...) # delete permission
myuser.user_permissions.clear() # delete permission

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# note: the above permission to django contrib. Auth. Permission type instance
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #Copy the code

Check user permissions with has_perm() :

myuser.has_perm('myapp.fix_car')Copy the code

The argument to the has_perm() method, the codename of permission, is passed prefixed by the app to which the model belongs, in the format

. .

The has_perm() method applies whether permission is granted to a user or a group

Note:

The user.get_all_permissions() method lists all the permissions of the user and returns permission name. The list user.get_group_permissions() method lists the permissions of the user’s group. A list of permission names is returned

2.3 Group Permission Management

Group permission management logic is the same as user permission management. The permissions field is used for permission management in the group:

group.permissions = [permission_list]
group.permissions.add(permission, permission, ...)
group.permissions.remove(permission, permission, ...)
group.permissions.clear()Copy the code

Permission check:

The user.has_perm() method is still used.

2.4 permission_requiredA decorator

Permissions constrain user behavior, and when permission checking is involved in business logic, decorators can separate permission validation from the core business logic, resulting in cleaner code and clearer logic. The permission decorator is permission_required:

from django.contrib.auth.decorators import permission_required

@permission_required('car.drive_car')
def my_view(request):.Copy the code

Check permissions in 2.5 Template

Template uses the global variable perms to store all permissions of the current user.

{% if perms.main.add_page %} <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Pages <span class="caret"></span></a> <ul class="dropdown-menu" role="menu"> <li><a href="{% url 'main:admin_pages' %}">All Pages</a></li> <li><a href="{% url 'main:admin_page' %}">New Page</a></li> <li><a href="{% url 'main:admin_pages' %}? draft=true">Drafts</a></li> </ul> </li> {% endif %}Copy the code

3. Application of Object Permission based on Django-Guardian

Django-guardian extends Django’s permissions based on django-native logic. After using Django-Guardian, you can check global permissions using the methods provided by Django-Guardian and Django’s native methods. Django-guardian’s object Permission mechanism complements django’s permission mechanism.

Please refer to the official documentation for detailed usage of Django-Guardian. Common methods of its object Permission are as follows:

from guardian.shortcuts import assign_perm, get_perms
from guardian.core import ObjectPermissionChecker
from guardian.decorators import permission_requiredCopy the code

3.1 Adding Object Permission

Use assign_perm() to add drive_car permissions to mycar objects, for example:

assign_perm('myapp.drive_car', request.user, mycar)Copy the code

The assign_perm() method can also be used for groups

assign_perm('myapp.drive_car', mygroup, mycar)Copy the code

3.2 Permission Check

3.2.1 Global permission

The get_perms() method is used to check the user’s “global permission”, as in user.has_perm() :

# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# It works! 
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 if not 'main.change_post' in get_perms(request.user, post):
     raise HttpResponse('Forbidden')

# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# It works, too!
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if not request.user.has_perm('main.change_post')
    return HttpResponse('Forbidden')Copy the code

In this example, although the post object is passed to the get_perms() method, it only checks whether the user has main.change_POST in its global permission. In many cases, it can be replaced by the native user.has_perm. But both user and group can be passed as arguments to get_perms(), which in some cases makes the code cleaner.

3.2.2 Object permission

Django-guardian uses ObjectPermissionChecker to check a user’s object permission as shown in the following example:

checker = ObjectPermissionChecker(request.user)
print checker.has_perm('main.change_post', post)Copy the code

3.3 Permission_required decorator

Guardian. Decorators. Permission_required django – the guardian permission check decorators, can check global permissions, and can check the object permissions (object permission), among them, The accept_global_perms parameter indicates whether the user’s global permission is checked, as in:

from guardian.decorators import permission_required

class DeletePost(View):
    @method_decorator(permission_required('main.delete_post', 
                            (models.Post, 'id'.'pk'), 
                            accept_global_perms=True))
    def get(self, request, pk):
        try:
            pk = int(pk)
            cur_post = models.Post.objects.get(pk=pk)
            is_draft = cur_post.is_draft

            url = reverse('main:admin_posts')
            if is_draft:
                url = '{0}? draft=true'.format(url)    
            cur_post.delete()
        except models.Post.DoesNotExist:
            raise Http404

        return redirect(url)Copy the code

Note:

In the decorator (models.Post, ‘id’, ‘pk’) part, which specifies the object instance. If this parameter is ignored, only global permissions are checked regardless of accept_global_perms values.

4. Conclusion

Djangos native provides a simple global permission control mechanism, but object permissions are more useful in many applications. Django-guardian is one of the most active Django extensions that provides an effective object permission control mechanism. It is recommended to use django-Guardian in the same vein as django’s native mechanism.