Permissions et autorisations (Permission, PermissionManager)

La nouvelle permission view avec django 2.1

Description

Django contient un système simple de permissions.

Il permet d’attribuer des permissions à des utilisateurs ou des groupes d’utilisateurs particuliers .

Ce système est utilisé par le site d’administration de Django, mais vous avez tout loisir de l’utiliser dans votre propre code .

Le site d’administration de Django utilise les permissions ainsi :

  • L’accès à l’affichage d’un objet est limité aux utilisateurs possédant la permission view ou change pour ce type d’objet.

  • L’accès au formulaire d’ajout et l’ajout d’objet sont limités aux utilisateurs possédant la permission add pour ce type d’objet.

  • L’accès à la liste des objets pour modification, au formulaire de modification et la modification d’un objet sont limités aux utilisateurs possédant la permission change pour ce type d’objet.

  • L’accès à la suppression d’un objet est limité aux utilisateurs possédant la permission delete pour ce type d’objet.

Opérations de base sur les permissions

In [4]: u2=User.objects.get(username='test1')

Liste de toutes les permissions

In [5]: u2.get_all_permissions()
Out[5]:
{'projets.add_projet',
 'projets.change_projet',
 'projets.delete_projet',
 'projets.view_projet'}

L’utilisateur a-t-il toutes les permissions sur une application ?

In [8]: u2.has_module_perms('projets')
Out[8]: True
In [9]: u2.has_module_perms('logs')
Out[9]: False

L’utilisateur peut-il modifier un projet ?

In [6]: u2.has_perm('projets.change_projet')
Out[6]: True

Créer une permission sur un modèle

Par exemple, vous pouvez créer la permission can_publish d’un modèle Article dans articles:

from articles.models import Article
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(Article)
permission = Permission.objects.create(
    codename='can_publish',
    name='Can Publish Articles',
    content_type=content_type,
)

La permission peut ensuite être attribuée à un User par son attribut user_permissions ou à un Group par son attribut permissions.

In [21]: content_type = ContentType.objects.get_for_model(Article)

In [22]:     permission = Permission.objects.create(
    ...:         codename='can_publish',
    ...:         name='Can Publish Articles',
    ...:         content_type=content_type,
    ...:     )

In [23]: u2.user_permissions.add(permission)
In [24]: u2.user_permissions.all()
Out[24]: <QuerySet [<Permission: articles | article | Can Publish Articles>]>

Permissions par défaut

Quand l’application django.contrib.auth figure dans le réglage INSTALLED_APPS, elle s’assure que les quatre permissions par défaut (« add », « change », « delete » et « view ») soient créées pour chaque modèle Django défini dans toutes les applications installées.

Ces permissions seront créées lorsque vous lancez manage.py migrate; au premier lancement de migrate après l’ajout de django.contrib.auth à INSTALLED_APPS, les permissions par défaut seront créées pour tous les modèles précédemment installés de même que pour tout nouveau modèle installé à ce moment-là.

Par la suite, chaque nouvelle exécution de manage.py migrate crée les permissions par défaut pour les nouveaux modèles (la fonction qui crée les permissions est connectée au signal post_migrate).

En supposant qu’une application ait l’attribut app_label foo et qu’un modèle soit nommé Bar, voici comment il faut tester les permissions de base:

ajout :        user.has_perm('foo.add_bar')
modification : user.has_perm('foo.change_bar')
suppression :  user.has_perm('foo.delete_bar')
affichage  :   user.has_perm('foo.view_bar')

Le modèle Permission est rarement accédé directement.

Utilisation des permissions dans les gabarits (templates)

Variable {{ perms }}

Les permissions de l’utilisateur actuellement connecté sont stockés dans la variable de gabarit {{ perms }}.

C’est une instance de django.contrib.auth.context_processors.PermWrapper, un objet mandataire de permissions adapté aux gabarits.

La consultation d’attributs simples de {{ perms }} sous forme de booléen est une méthode mandataire de User.has_module_perms().

Par exemple, pour vérifier si l’utilisateur connecté a au moins une permission dans l’application foo:

{% if perms.foo %}

La consultation d’attributs à deux niveaux de {{ perms }} sous forme de booléen est une méthode mandataire de User.has_perm().

Par exemple, pour vérifier si l’utilisateur connecté a la permission foo.can_vote:

{% if perms.foo.can_vote %}

Voici un exemple plus complet de contrôle de permissions dans un gabarit.

 1 {% if perms.foo %}
 2     <p>You have permission to do something in the foo app.</p>
 3     {% if perms.foo.can_vote %}
 4         <p>You can vote!</p>
 5     {% endif %}
 6     {% if perms.foo.can_drive %}
 7         <p>You can drive!</p>
 8     {% endif %}
 9 {% else %}
10     <p>You don't have permission to do anything in the foo app.</p>
11 {% endif %}

Il est aussi possible de consulter les permissions par des instructions {% if in %}.

Par exemple:

{% if 'foo' in perms %}
    {% if 'foo.can_vote' in perms %}
        <p>In lookup works, too.</p>
    {% endif %}
{% endif %}

class contrib.auth.models.PermissionManager

1 class PermissionManager(models.Manager):
2     use_in_migrations = True
3
4     def get_by_natural_key(self, codename, app_label, model):
5         return self.get(
6             codename=codename,
7             content_type=ContentType.objects.db_manager(self.db).get_by_natural_key(app_label, model),
8         )

class contrib.auth.models.Permission

 1 class Permission(models.Model):
 2     """
 3     The permissions system provides a way to assign permissions to specific
 4     users and groups of users.
 5
 6     The permission system is used by the Django admin site, but may also be
 7     useful in your own code. The Django admin site uses permissions as follows:
 8
 9         - The "add" permission limits the user's ability to view the "add" form
10           and add an object.
11         - The "change" permission limits a user's ability to view the change
12           list, view the "change" form and change an object.
13         - The "delete" permission limits the ability to delete an object.
14         - The "view" permission limits the ability to view an object.
15
16     Permissions are set globally per type of object, not per specific object
17     instance. It is possible to say "Mary may change news stories," but it's
18     not currently possible to say "Mary may change news stories, but only the
19     ones she created herself" or "Mary may only change news stories that have a
20     certain status or publication date."
21
22     The permissions listed above are automatically created for each model.
23     """
24     name = models.CharField(_('name'), max_length=255)
25     content_type = models.ForeignKey(
26         ContentType,
27         models.CASCADE,
28         verbose_name=_('content type'),
29     )
30     codename = models.CharField(_('codename'), max_length=100)
31
32     objects = PermissionManager()
33
34     class Meta:
35         verbose_name = _('permission')
36         verbose_name_plural = _('permissions')
37         unique_together = (('content_type', 'codename'),)
38         ordering = ('content_type__app_label', 'content_type__model',
39                     'codename')
40
41     def __str__(self):
42         return '%s | %s' % (self.content_type, self.name)
43
44     def natural_key(self):
45         return (self.codename,) + self.content_type.natural_key()
46     natural_key.dependencies = ['contenttypes.contenttype']

class contrib.auth.models.PermissionsMixin

 1 class PermissionsMixin(models.Model):
 2     """
 3     Add the fields and methods necessary to support the Group and Permission
 4     models using the ModelBackend.
 5     """
 6     is_superuser = models.BooleanField(
 7         _('superuser status'),
 8         default=False,
 9         help_text=_(
10             'Designates that this user has all permissions without '
11             'explicitly assigning them.'
12         ),
13     )
14     groups = models.ManyToManyField(
15         Group,
16         verbose_name=_('groups'),
17         blank=True,
18         help_text=_(
19             'The groups this user belongs to. A user will get all permissions '
20             'granted to each of their groups.'
21         ),
22         related_name="user_set",
23         related_query_name="user",
24     )
25     user_permissions = models.ManyToManyField(
26         Permission,
27         verbose_name=_('user permissions'),
28         blank=True,
29         help_text=_('Specific permissions for this user.'),
30         related_name="user_set",
31         related_query_name="user",
32     )
33
34     class Meta:
35         abstract = True
36
37     def get_group_permissions(self, obj=None):
38         """
39         Return a list of permission strings that this user has through their
40         groups. Query all available auth backends. If an object is passed in,
41         return only permissions matching this object.
42         """
43         permissions = set()
44         for backend in auth.get_backends():
45             if hasattr(backend, "get_group_permissions"):
46                 permissions.update(backend.get_group_permissions(self, obj))
47         return permissions
48
49     def get_all_permissions(self, obj=None):
50         return _user_get_all_permissions(self, obj)
51
52     def has_perm(self, perm, obj=None):
53         """
54         Return True if the user has the specified permission. Query all
55         available auth backends, but return immediately if any backend returns
56         True. Thus, a user who has permission from a single auth backend is
57         assumed to have permission in general. If an object is provided, check
58         permissions for that object.
59         """
60         # Active superusers have all permissions.
61         if self.is_active and self.is_superuser:
62             return True
63
64         # Otherwise we need to check the backends.
65         return _user_has_perm(self, perm, obj)
66
67     def has_perms(self, perm_list, obj=None):
68         """
69         Return True if the user has each of the specified permissions. If
70         object is passed, check if the user has all required perms for it.
71         """
72         return all(self.has_perm(perm, obj) for perm in perm_list)
73
74     def has_module_perms(self, app_label):
75         """
76         Return True if the user has any permissions in the given app label.
77         Use similar logic as has_perm(), above.
78         """
79         # Active superusers have all permissions.
80         if self.is_active and self.is_superuser:
81             return True
82
83         return _user_has_module_perms(self, app_label)

class contrib.auth.mixins.PermissionRequiredMixin

 1 class PermissionRequiredMixin(AccessMixin):
 2     """Verify that the current user has all specified permissions."""
 3     permission_required = None
 4
 5     def get_permission_required(self):
 6         """
 7         Override this method to override the permission_required attribute.
 8         Must return an iterable.
 9         """
10         if self.permission_required is None:
11             raise ImproperlyConfigured(
12                 '{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
13                 '{0}.get_permission_required().'.format(self.__class__.__name__)
14             )
15         if isinstance(self.permission_required, str):
16             perms = (self.permission_required,)
17         else:
18             perms = self.permission_required
19         return perms
20
21     def has_permission(self):
22         """
23         Override this method to customize the way permissions are checked.
24         """
25         perms = self.get_permission_required()
26         return self.request.user.has_perms(perms)
27
28     def dispatch(self, request, *args, **kwargs):
29         if not self.has_permission():
30             return self.handle_no_permission()
31         return super().dispatch(request, *args, **kwargs)

class contrib.auth.decorators.permission_required

 1 from django.core.exceptions import PermissionDenied
 2
 3 ...
 4
 5
 6 def permission_required(perm, login_url=None, raise_exception=False):
 7     """
 8     Decorator for views that checks whether a user has a particular permission
 9     enabled, redirecting to the log-in page if necessary.
10     If the raise_exception parameter is given the PermissionDenied exception
11     is raised.
12     """
13     def check_perms(user):
14         if isinstance(perm, str):
15             perms = (perm,)
16         else:
17             perms = perm
18         # First check if the user has the permission (even anon users)
19         if user.has_perms(perms):
20             return True
21         # In case the 403 handler should be called raise the exception
22         if raise_exception:
23             raise PermissionDenied
24         # As the last resort, show the login form
25         return False
26     return user_passes_test(check_perms, login_url=login_url)

Permissions personnalisées