How to avoid typos in django's permission strings
Asked Answered
P

2

6

According to the docs custom permissions can be created and used like this:

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
        )

Using the permission:

user.has_perm('app.view_task')

Source: https://docs.djangoproject.com/en/1.10/topics/auth/customizing/#custom-permissions

If there is a typo in the permission string. For example: I use user.has_perm('app.view_tasks'), then this typo won't be noticed.

Goal 1

I would like to get an exception or warning if not existing permissions get used.

Goal 2

To avoid typos in the first place, I would like to have constants: user.PERM_APP_VIEW_TASKS or something like this.

Parrot answered 10/10, 2016 at 8:46 Comment(2)
So what is actual question? For goal 1 you can change has_perm method on user so it will check if permission exist in db with Permission.objects.get(...) and for goal 2 you can create class variablesSwag
@SardorbekImomaliev the question is "how to solve this in the most pythonic way?".Parrot
M
2

Goal 1

Override the has_perm method of the ModelBackend class, from my backends.py file:

import logging
from difflib import get_close_matches
from django.conf import settings
from django.contrib.auth.backends import ModelBackend


class ModelBackendHelper(ModelBackend):
    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        else:
            obj_perms = self.get_all_permissions(user_obj, obj)
            allowed = perm in obj_perms

            if not allowed:
                if settings.DEBUG:
                    similar = get_close_matches(perm, obj_perms)

                    if similar:
                        logging.warn("{0} not found, but is similar to: {1}".format(perm, ','.join(similar)))

            return allowed

How it works:

Same logic of has_perm but if settings.DEBUG is True and similar versions of perm are found then output a warning log message of level WARN:

WARNING:root:myapp.view_tasks not found, but is similar to: myapp.view_task

Change the value of AUTHENTICATION_BACKENDS in settings.py:

AUTHENTICATION_BACKENDS = ['myapp.backends.ModelBackendHelper']

This can be used both in production and development environments but personally I wouldn't include this in a production site, I hope that when everything goes to production permissions are consolidated.

Goal 2

Permissions belong to models and to keep this DRY I'm reusing the permissions defined in Meta:

from django.db import models


class Task(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField()

    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
        )


def _get_perm(model, perm):
    for p in model._meta.permissions:
        if p[0] == perm:
            return p[0]
    err = "Permission '{0}' not found in model {1}".format(perm, model)
    raise Exception(err)


def format_perm(perm):
    return '{0}.{1}'.format(__package__, perm)


def get_perm(model, type):
    return format_perm(_get_perm(model, type))


PERM_APP_VIEW_TASK = get_perm(Task, "view_task")

Permissions can be accessed with get_perm or with a shortcut PERM_APP_VIEW_TASK:

models.PERM_APP_VIEW_TASK
# or
get_perm(Task, "view_task")
# or
format_perm(_get_perm(Task, "view_task"))

In case of searching for a missing permission get_perm will raise an Exception:

PERM_APP_VIEW_TASK = get_perm(Task, "add_task")

Message:

Exception: Permission 'add_task' not found in model <class 'myapp.models.Task'>
Melina answered 2/11, 2016 at 22:1 Comment(2)
Doesn't this show a warning in all cases when a user doesn't have a permission, even if the permission is correctly spelled?Glissando
How does this work if you are using multiple AUTHENTICATION_BACKENDS, for example, with django-allauth? Wouldn't you have override allauths implementation too?Glissando
D
0

Goal 1

Create a custom decorator:

Warning! untested code!!!!!

@permission_required
def assert_permission(permission):
    def real_decorator(original_function):
        def wrapper(request, *args, **kwargs):
            permission_object = Permission.objects.filter(codename=permission)
            if not permission_object.exists():
                raise ValueError("This permission does not exist!")
            original_function(request, *args, **kwargs)
        return wrapper
    return real_decorator

Goal 2

Add constants to your model classes and refer to those whenever asking for specific permissions. example:

class Task(models.Model):
MY_PERMISSION_CONSTANT = 'app.view_task'
...
class Meta:
    permissions = (
        (MY_PERMISSION_CONSTANT, "Can see available tasks"),
    )

@permission_required(Task.MY_PERMISSION_CONSTANT)
def some_view(request):
  pass
Dusen answered 28/10, 2016 at 11:45 Comment(2)
Are you sure that Goal1 cannot be achieved? For me all permissions in the db are valid. Every string which does not correspond to a db row is invalid.Parrot
@Parrot haven't been able to test this, but i assume that should work, or at least get you in the right direction, if something doesn't work and you fix it, please edit this answer ;)Dusen

© 2022 - 2024 — McMap. All rights reserved.