mirror of
https://github.com/ae-utbm/sith.git
synced 2025-01-21 06:21:12 +00:00
Improve permission documentation
This commit is contained in:
parent
d0b1a49300
commit
9b5f08e13c
@ -157,7 +157,9 @@ il est automatiquement ajouté au groupe des membres
|
|||||||
du club.
|
du club.
|
||||||
Lorsqu'il quitte le club, il est retiré du groupe.
|
Lorsqu'il quitte le club, il est retiré du groupe.
|
||||||
|
|
||||||
## Les principaux groupes utilisés
|
## Les groupes utilisés
|
||||||
|
|
||||||
|
### Groupes principaux
|
||||||
|
|
||||||
Les groupes les plus notables gérables par les administrateurs du site sont :
|
Les groupes les plus notables gérables par les administrateurs du site sont :
|
||||||
|
|
||||||
@ -168,15 +170,45 @@ Les groupes les plus notables gérables par les administrateurs du site sont :
|
|||||||
- `SAS admin` : les administrateurs du SAS
|
- `SAS admin` : les administrateurs du SAS
|
||||||
- `Forum admin` : les administrateurs du forum
|
- `Forum admin` : les administrateurs du forum
|
||||||
- `Pedagogy admin` : les administrateurs de la pédagogie (guide des UVs)
|
- `Pedagogy admin` : les administrateurs de la pédagogie (guide des UVs)
|
||||||
- `Banned from buying alcohol` : les utilisateurs interdits de vente d'alcool (non mineurs)
|
|
||||||
- `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs
|
|
||||||
- `Banned to subscribe` : les utilisateurs interdits de cotisation
|
|
||||||
|
|
||||||
|
|
||||||
En plus de ces groupes, on peut noter :
|
En plus de ces groupes, on peut noter :
|
||||||
|
|
||||||
- `Public` : tous les utilisateurs du site
|
- `Public` : tous les utilisateurs du site.
|
||||||
- `Subscribers` : tous les cotisants du site
|
Un utilisateur est automatiquement ajouté à ce group
|
||||||
- `Old subscribers` : tous les anciens cotisants
|
lors de la création de son compte.
|
||||||
|
- `Subscribers` : tous les cotisants du site.
|
||||||
|
Les utilisateurs ne sont pas réellement ajoutés ce groupe ;
|
||||||
|
cependant, les utilisateurs cotisants sont implicitement
|
||||||
|
considérés comme membres du groupe lors de l'appel
|
||||||
|
à la méthode `User.has_perm`.
|
||||||
|
- `Old subscribers` : tous les anciens cotisants.
|
||||||
|
Un utilisateur est automatiquement ajouté à ce groupe
|
||||||
|
lors de sa première cotisation
|
||||||
|
|
||||||
|
### Groupes de club
|
||||||
|
|
||||||
|
Chaque club est associé à deux groupes :
|
||||||
|
le groupe des membres et le groupe du bureau.
|
||||||
|
|
||||||
|
Lorsqu'un utilisateur rejoint un club, il est automatiquement
|
||||||
|
ajouté au groupe des membres.
|
||||||
|
S'il rejoint le club en tant que membre du bureau,
|
||||||
|
il est également ajouté au groupe du bureau.
|
||||||
|
|
||||||
|
Lorsqu'un utilisateur quitte le club, il est automatiquement
|
||||||
|
retiré des groupes liés au club.
|
||||||
|
S'il quitte le bureau, mais reste dans le club,
|
||||||
|
il est retiré du groupe du bureau, mais reste dans le groupe des membres.
|
||||||
|
|
||||||
|
### Groupes de ban
|
||||||
|
|
||||||
|
Les groupes de ban sont une catégorie de groupes à part,
|
||||||
|
qui ne sont pas stockés dans la même table
|
||||||
|
et qui ne sont pas gérés sur la même interface
|
||||||
|
que les autres groupes.
|
||||||
|
|
||||||
|
Les groupes de ban existants sont les suivants :
|
||||||
|
|
||||||
|
- `Banned from buying alcohol` : les utilisateurs interdits de vente d'alcool (non mineurs)
|
||||||
|
- `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs
|
||||||
|
- `Banned to subscribe` : les utilisateurs interdits de cotisation
|
||||||
|
@ -1,15 +1,292 @@
|
|||||||
|
|
||||||
## Les permissions
|
## Objectifs du système de permissions
|
||||||
|
|
||||||
Le fonctionnement de l'AE ne permet pas d'utiliser le système de permissions
|
Les permissions attendues sur le site sont relativement spécifiques.
|
||||||
intégré à Django tel quel. Lors de la conception du Sith, ce qui paraissait le
|
L'accès à une ressource peut se faire selon un certain nombre
|
||||||
plus simple à l'époque était de concevoir un système maison afin de se calquer
|
de paramètres différents :
|
||||||
sur ce que faisait l'ancien site.
|
|
||||||
|
|
||||||
### Protéger un modèle
|
`L'état de la ressource`
|
||||||
|
: Certaines ressources
|
||||||
|
sont visibles par tous les cotisants (voire tous les utilisateurs),
|
||||||
|
à condition qu'elles aient passé une étape de modération.
|
||||||
|
La visibilité des ressources non-modérées nécessite des permissions
|
||||||
|
supplémentaires.
|
||||||
|
|
||||||
La gestion des permissions se fait directement par modèle.
|
`L'appartenance à un groupe`
|
||||||
Il existe trois niveaux de permission :
|
: Les groupes Root, Admin Com, Admin SAS, etc.
|
||||||
|
sont associés à des jeux de permissions.
|
||||||
|
Par exemple, les membres du groupe Admin SAS ont tous les droits sur
|
||||||
|
les ressources liées au SAS : ils peuvent voir,
|
||||||
|
créer, éditer, supprimer et éventuellement modérer
|
||||||
|
des images, des albums, des identifications de personnes...
|
||||||
|
Il en va de même avec les admins Com pour la communication,
|
||||||
|
les admins pédagogie pour le guide des UEs et ainsi de suite.
|
||||||
|
Quant aux membres du groupe Root, ils ont tous les droits
|
||||||
|
sur toutes les ressources du site.
|
||||||
|
|
||||||
|
`Le statut de la cotisation`
|
||||||
|
: Les non-cotisants n'ont presque aucun
|
||||||
|
droit sur les ressources du site (ils peuvent seulement en voir une poignée),
|
||||||
|
les anciens cotisants peuvent voir un grand nombre de ressources
|
||||||
|
et les cotisants actuels ont la plupart des droits qui ne sont
|
||||||
|
pas liés à un club ou à l'administration du site.
|
||||||
|
|
||||||
|
`L'appartenance à un club`
|
||||||
|
: Être dans un club donne le droit
|
||||||
|
de voir la plupart des ressources liées au club dans lequel ils
|
||||||
|
sont ; être dans le bureau du club donne en outre des droits
|
||||||
|
d'édition et de création sur ces ressources.
|
||||||
|
|
||||||
|
`Être l'auteur ou le possesseur d'une ressource`
|
||||||
|
: Certaines ressources, comme les nouvelles,
|
||||||
|
enregistrent l'utilisateur qui les a créées ;
|
||||||
|
ce dernier a les droits de voir, de modifier et éventuellement
|
||||||
|
de supprimer ses ressources, quand bien même
|
||||||
|
elles ne seraient pas visibles pour les utilisateurs normaux
|
||||||
|
(par exemple, parce qu'elles ne sont pas encore modérées.)
|
||||||
|
|
||||||
|
|
||||||
|
Le système de permissions inclus par défaut dans django
|
||||||
|
permet de modéliser aisément l'accès à des ressources au niveau
|
||||||
|
de la table.
|
||||||
|
Ainsi, il n'est pas compliqué de gérer les permissions liées
|
||||||
|
aux groupes d'administration.
|
||||||
|
|
||||||
|
Cependant, une surcouche est nécessaire dès lors que l'on veut
|
||||||
|
gérer les droits liés à une ligne en particulier
|
||||||
|
d'une table de la base de données.
|
||||||
|
|
||||||
|
Nous essayons le plus possible de nous tenir aux fonctionnalités
|
||||||
|
de django, sans pour autant hésiter à nous rabattre sur notre
|
||||||
|
propre surcouche dès lors que les permissions attendues
|
||||||
|
deviennent trop spécifiques pour être gérées avec juste django.
|
||||||
|
|
||||||
|
!!!info "Un peu d'histoire"
|
||||||
|
|
||||||
|
Les permissions du site n'ont pas toujours été gérées
|
||||||
|
avec un mélange de fonctionnalités de django et de notre
|
||||||
|
propre code.
|
||||||
|
Pendant très longtemps, seule la surcouche était utilisée,
|
||||||
|
ce qui menait souvent à des vérifications de droits
|
||||||
|
inefficaces et à une gestion complexe de certaines
|
||||||
|
parties qui auraient pu être manipulées beaucoup plus simplement.
|
||||||
|
|
||||||
|
En plus de ça, les permissions liées à la plupart
|
||||||
|
des groupes se faisait de manière hardcodée :
|
||||||
|
plutôt que d'associer un groupe à un jeu de permission
|
||||||
|
et de faire une jointure en db sur les groupes de l'utilisateur
|
||||||
|
ayant cette permissions,
|
||||||
|
on conservait la clef primaire du groupe dans la config
|
||||||
|
et on vérifiait en dur dans le code que l'utilisateur
|
||||||
|
était un des groupes voulus.
|
||||||
|
|
||||||
|
Ce système possédait le triple désavantage de prendre énormément
|
||||||
|
de temps, d'être extrêmement limité (de fait, si tout est hardcodé,
|
||||||
|
on est obligé d'avoir le moins de groupes possibles pour que ça reste
|
||||||
|
gérable) et d'être désespérément dangereux (par exemple : fin novembre 2024,
|
||||||
|
une erreur dans le code a donné les accès à la création des cotisations
|
||||||
|
à tout le monde ; mi-octobre 2019, le calcul des permissions des etickets
|
||||||
|
pouvait faire tomber le site, cf.
|
||||||
|
[ce topic du forum](https://ae.utbm.fr/forum/topic/17943/?page=1msg2277272))
|
||||||
|
|
||||||
|
## Accès à toutes les ressources d'une table
|
||||||
|
|
||||||
|
Gérer ce genre d'accès (par exemple : voir toutes les nouvelles
|
||||||
|
ou pouvoir supprimer n'importe quelle photo)
|
||||||
|
est exactement le problème que le système de permissions de django résout.
|
||||||
|
Nous utilisons donc ce système dans ce genre de situations.
|
||||||
|
|
||||||
|
!!!note
|
||||||
|
|
||||||
|
Nous décrivons ci-dessous l'usage que nous faisons du système
|
||||||
|
de permissions de django,
|
||||||
|
mais la seule source d'information complète et pleinement fiable
|
||||||
|
sur le fonctionnement réel de ce système est
|
||||||
|
[la documentation de django](https://docs.djangoproject.com/fr/stable/topics/auth/default/).
|
||||||
|
|
||||||
|
### Permissions d'un modèle
|
||||||
|
|
||||||
|
Par défaut, django crée quatre permissions pour chaque table de la base de données :
|
||||||
|
|
||||||
|
- `add_<nom de la table>` : créer un objet dans cette table
|
||||||
|
- `view_<nom de la table>` : voir le contenu de la table
|
||||||
|
- `change_<nom de la table>` : éditer des objets de la table
|
||||||
|
- `delete_<nom de la table>` : supprimer des objets de la table
|
||||||
|
|
||||||
|
Ces permissions sont créées au même moment que le modèle.
|
||||||
|
Si la table existe en base de données, ces permissions existent aussi.
|
||||||
|
|
||||||
|
Il est également possible de rajouter nos propres permissions,
|
||||||
|
directement dans les options Meta du modèle.
|
||||||
|
Par exemple, prenons le modèle suivant :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class News(models.Model):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = [
|
||||||
|
("moderate_news", "Can moderate news"),
|
||||||
|
("view_unmoderated_news", "Can view non-moderated news"),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce dernier aura les permissions : `view_news`, `add_news`, `change_news`,
|
||||||
|
`delete_news`, `moderate_news` et `view_unmoderated_news`.
|
||||||
|
|
||||||
|
### Utilisation des permissions d'un modèle
|
||||||
|
|
||||||
|
Pour vérifier qu'un utilisateur a une permission,
|
||||||
|
on utilise les fonctions suivantes :
|
||||||
|
|
||||||
|
- `User.has_perm(perm)` : retourne `True` si l'utilisateur
|
||||||
|
a la permission voulue, sinon `False`
|
||||||
|
- `User.has_perms([perm_a, perm_b, perm_c])` : retourne `True` si l'utilisateur
|
||||||
|
a toutes les permissions voulues, sinon `False`.
|
||||||
|
|
||||||
|
Ces fonctions attendent un string suivant le format :
|
||||||
|
`<nom de l'application>.<nom de la permission>`.
|
||||||
|
Par exemple, la permission pour vérifier qu'un utilisateur
|
||||||
|
peut modérer une nouvelle sera : `com.moderate_news`.
|
||||||
|
|
||||||
|
Ces fonctions sont utilisables aussi bien dans les templates Jinja
|
||||||
|
que dans le code Python :
|
||||||
|
|
||||||
|
=== "Jinja"
|
||||||
|
|
||||||
|
```jinja
|
||||||
|
{% if user.has_perm("com.moderate_news") %}
|
||||||
|
<form method="post" action="{{ url("com:news_moderate", news_id=387) }}">
|
||||||
|
<input type="submit" value="Modérer" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python
|
||||||
|
from com.models import News
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
|
user = User.objects.get(username="bibou")
|
||||||
|
news = News.objects.get(id=387)
|
||||||
|
if user.has_perm("com.moderate_news"):
|
||||||
|
news.is_moderated = True
|
||||||
|
news.save()
|
||||||
|
else:
|
||||||
|
raise PermissionDenied
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour utiliser ce système de permissions dans une class-based view
|
||||||
|
(c'est-à-dire la plus grande partie de nos vues),
|
||||||
|
Django met à disposition `PermissionRequiredMixin`,
|
||||||
|
qui restreint l'accès à la vue aux utilisateurs ayant
|
||||||
|
la ou les permissions requises.
|
||||||
|
Pour les vues sous forme de fonction, il y a le décorateur
|
||||||
|
`permission_required`.
|
||||||
|
|
||||||
|
=== "Class-Based View"
|
||||||
|
|
||||||
|
```python
|
||||||
|
from com.models import News
|
||||||
|
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views import View
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
|
class NewsModerateView(PermissionRequiredMixin, SingleObjectMixin, View):
|
||||||
|
model = News
|
||||||
|
pk_url_kwarg = "news_id"
|
||||||
|
permission_required = "com.moderate_news"
|
||||||
|
# On peut aussi fournir plusieurs permissions, par exemple :
|
||||||
|
# permission_required = ["com.moderate_news", "com.delete_news"]
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# Si nous sommes ici, nous pouvons être certains que l'utilisateur
|
||||||
|
# a la permission requise
|
||||||
|
obj = self.get_object()
|
||||||
|
obj.is_moderated = True
|
||||||
|
obj.save()
|
||||||
|
return redirect(reverse("com:news_list"))
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Function-based view"
|
||||||
|
|
||||||
|
```python
|
||||||
|
from com.models import News
|
||||||
|
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
@permission_required("com.moderate_news")
|
||||||
|
@require_POST
|
||||||
|
def moderate_news(request, news_id: int):
|
||||||
|
# Si nous sommes ici, nous pouvons être certains que l'utilisateur
|
||||||
|
# a la permission requise
|
||||||
|
news = get_object_or_404(News, id=news_id)
|
||||||
|
news.is_moderated = True
|
||||||
|
news.save()
|
||||||
|
return redirect(reverse("com:news_list"))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accès à des éléments en particulier
|
||||||
|
|
||||||
|
### Accès à l'auteur de la ressource
|
||||||
|
|
||||||
|
Dans ce genre de cas, on peut identifier trois acteurs possibles :
|
||||||
|
|
||||||
|
- les administrateurs peuvent accéder à toutes les ressources,
|
||||||
|
y compris non-modérées
|
||||||
|
- l'auteur d'une ressource non-modérée peut y accéder
|
||||||
|
- Les autres utilisateurs ne peuvent pas voir les ressources
|
||||||
|
non-modérées dont ils ne sont pas l'auteur
|
||||||
|
|
||||||
|
Dans ce genre de cas, on souhaite donc accorder l'accès aux
|
||||||
|
utilisateurs qui ont la permission globale, selon le système
|
||||||
|
décrit plus haut, ou bien à l'auteur de la ressource.
|
||||||
|
|
||||||
|
Pour cela, nous avons le mixin `PermissionOrAuthorRequired`.
|
||||||
|
Ce dernier va effectuer les mêmes vérifications que `PermissionRequiredMixin`
|
||||||
|
puis, si l'utilisateur n'a pas la permission requise, vérifier
|
||||||
|
s'il est l'auteur de la ressource.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from com.models import News
|
||||||
|
from core.auth.mixins import PermissionOrAuthorRequiredMixin
|
||||||
|
|
||||||
|
from django.views.generic import UpdateView
|
||||||
|
|
||||||
|
class NewsUpdateView(PermissionOrAuthorRequiredMixin, UpdateView):
|
||||||
|
model = News
|
||||||
|
pk_url_kwarg = "news_id"
|
||||||
|
permission_required = "com.change_news"
|
||||||
|
author_field = "author" # (1)!
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Nom du champ du modèle utilisé comme clef étrangère vers l'auteur.
|
||||||
|
Par exemple, ici, la permission sera accordée si
|
||||||
|
l'utilisateur connecté correspond à l'utilisateur
|
||||||
|
désigné par `News.author`.
|
||||||
|
|
||||||
|
### Accès en fonction de règles plus complexes
|
||||||
|
|
||||||
|
Tout ce que nous avons décrit précédemment permet de couvrir
|
||||||
|
la plupart des cas simples.
|
||||||
|
Cependant, il arrivera souvent que les permissions attendues soient
|
||||||
|
plus complexes.
|
||||||
|
Dans ce genre de cas, on rentre entièrement dans notre surcouche.
|
||||||
|
|
||||||
|
#### Implémentation dans les modèles
|
||||||
|
|
||||||
|
La gestion de ce type de permissions se fait directement par modèle.
|
||||||
|
Il en existe trois niveaux :
|
||||||
|
|
||||||
- Éditer des propriétés de l'objet
|
- Éditer des propriétés de l'objet
|
||||||
- Éditer certaines valeurs l'objet
|
- Éditer certaines valeurs l'objet
|
||||||
@ -47,28 +324,43 @@ Voici un exemple d'implémentation de ce système :
|
|||||||
|
|
||||||
from core.models import User, Group
|
from core.models import User, Group
|
||||||
|
|
||||||
# Utilisation de la protection par fonctions
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
|
|
||||||
title = models.CharField(_("title"), max_length=100)
|
title = models.CharField(_("title"), max_length=100)
|
||||||
content = models.TextField(_("content"))
|
content = models.TextField(_("content"))
|
||||||
|
|
||||||
# Donne ou non les droits d'édition des propriétés de l'objet
|
def is_owned_by(self, user): # (1)!
|
||||||
# Un utilisateur dans le bureau AE aura tous les droits sur cet objet
|
|
||||||
def is_owned_by(self, user):
|
|
||||||
return user.is_board_member
|
return user.is_board_member
|
||||||
|
|
||||||
# Donne ou non les droits d'édition de l'objet
|
def can_be_edited_by(self, user): # (2)!
|
||||||
# L'objet ne sera modifiable que par un utilisateur cotisant
|
|
||||||
def can_be_edited_by(self, user):
|
|
||||||
return user.is_subscribed
|
return user.is_subscribed
|
||||||
|
|
||||||
# Donne ou non les droits de vue de l'objet
|
def can_be_viewed_by(self, user): # (3)!
|
||||||
# Ici, l'objet n'est visible que par un utilisateur connecté
|
|
||||||
def can_be_viewed_by(self, user):
|
|
||||||
return not user.is_anonymous
|
return not user.is_anonymous
|
||||||
```
|
```
|
||||||
|
|
||||||
|
1. Donne ou non les droits d'édition des propriétés de l'objet.
|
||||||
|
Ici, un utilisateur dans le bureau AE aura tous les droits sur cet objet
|
||||||
|
2. Donne ou non les droits d'édition de l'objet
|
||||||
|
Ici, l'objet ne sera modifiable que par un utilisateur cotisant
|
||||||
|
3. Donne ou non les droits de vue de l'objet
|
||||||
|
Ici, l'objet n'est visible que par un utilisateur connecté
|
||||||
|
|
||||||
|
!!!note
|
||||||
|
|
||||||
|
Dans cet exemple, nous utilisons des permissions très simples
|
||||||
|
pour que vous puissiez constater le squelette de ce système,
|
||||||
|
plutôt que la logique de validation dans ce cas particulier.
|
||||||
|
|
||||||
|
En réalité, il serait ici beaucoup plus approprié de
|
||||||
|
donner les permissions `com.delete_article` et
|
||||||
|
`com.change_article_properties` (en créant ce dernier
|
||||||
|
s'il n'existe pas encore) au groupe du bureau AE,
|
||||||
|
de donner également la permission `com.change_article`
|
||||||
|
au groupe `Cotisants` et enfin de restreindre l'accès
|
||||||
|
aux vues d'accès aux articles avec `LoginRequiredMixin`.
|
||||||
|
|
||||||
|
|
||||||
=== "Avec les groupes de permission"
|
=== "Avec les groupes de permission"
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -83,15 +375,12 @@ Voici un exemple d'implémentation de ce système :
|
|||||||
content = models.TextField(_("content"))
|
content = models.TextField(_("content"))
|
||||||
|
|
||||||
# relation one-to-many
|
# relation one-to-many
|
||||||
# Groupe possédant l'objet
|
owner_group = models.ForeignKey( # (1)!
|
||||||
# Donne les droits d'édition des propriétés de l'objet
|
|
||||||
owner_group = models.ForeignKey(
|
|
||||||
Group, related_name="owned_articles", default=settings.SITH_GROUP_ROOT_ID
|
Group, related_name="owned_articles", default=settings.SITH_GROUP_ROOT_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
# relation many-to-many
|
# relation many-to-many
|
||||||
# Tous les groupes qui seront ajouté dans ce champ auront les droits d'édition de l'objet
|
edit_groups = models.ManyToManyField( # (2)!
|
||||||
edit_groups = models.ManyToManyField(
|
|
||||||
Group,
|
Group,
|
||||||
related_name="editable_articles",
|
related_name="editable_articles",
|
||||||
verbose_name=_("edit groups"),
|
verbose_name=_("edit groups"),
|
||||||
@ -99,8 +388,7 @@ Voici un exemple d'implémentation de ce système :
|
|||||||
)
|
)
|
||||||
|
|
||||||
# relation many-to-many
|
# relation many-to-many
|
||||||
# Tous les groupes qui seront ajouté dans ce champ auront les droits de vue de l'objet
|
view_groups = models.ManyToManyField( # (3)!
|
||||||
view_groups = models.ManyToManyField(
|
|
||||||
Group,
|
Group,
|
||||||
related_name="viewable_articles",
|
related_name="viewable_articles",
|
||||||
verbose_name=_("view groups"),
|
verbose_name=_("view groups"),
|
||||||
@ -108,9 +396,16 @@ Voici un exemple d'implémentation de ce système :
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Appliquer les permissions
|
1. Groupe possédant l'objet
|
||||||
|
Donne les droits d'édition des propriétés de l'objet.
|
||||||
|
Il ne peut y avoir qu'un seul groupe `owner` par objet.
|
||||||
|
2. Tous les groupes ayant droit d'édition sur l'objet.
|
||||||
|
Il peut y avoir autant de groupes d'édition que l'on veut par objet.
|
||||||
|
3. Tous les groupes ayant droit de voir l'objet.
|
||||||
|
Il peut y avoir autant de groupes de vue que l'on veut par objet.
|
||||||
|
|
||||||
|
|
||||||
#### Dans un template
|
#### Application dans les templates
|
||||||
|
|
||||||
Il existe trois fonctions de base sur lesquelles
|
Il existe trois fonctions de base sur lesquelles
|
||||||
reposent les vérifications de permission.
|
reposent les vérifications de permission.
|
||||||
@ -130,7 +425,7 @@ Voici un exemple d'utilisation dans un template :
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Dans une vue
|
#### Application dans les vues
|
||||||
|
|
||||||
Généralement, les vérifications de droits dans les templates
|
Généralement, les vérifications de droits dans les templates
|
||||||
se limitent aux urls à afficher puisqu'il
|
se limitent aux urls à afficher puisqu'il
|
||||||
@ -138,7 +433,7 @@ ne faut normalement pas mettre de logique autre que d'affichage à l'intérieur
|
|||||||
(en réalité, c'est un principe qu'on a beaucoup violé, mais promis on le fera plus).
|
(en réalité, c'est un principe qu'on a beaucoup violé, mais promis on le fera plus).
|
||||||
C'est donc habituellement au niveau des vues que cela a lieu.
|
C'est donc habituellement au niveau des vues que cela a lieu.
|
||||||
|
|
||||||
Notre système s'appuie sur un système de mixin
|
Pour cela, nous avons rajouté des mixins
|
||||||
à hériter lors de la création d'une vue basée sur une classe.
|
à hériter lors de la création d'une vue basée sur une classe.
|
||||||
Ces mixins ne sont compatibles qu'avec les classes récupérant
|
Ces mixins ne sont compatibles qu'avec les classes récupérant
|
||||||
un objet ou une liste d'objet.
|
un objet ou une liste d'objet.
|
||||||
@ -152,22 +447,23 @@ l'utilisateur recevra une liste vide d'objet.
|
|||||||
Voici un exemple d'utilisation en reprenant l'objet Article crée précédemment :
|
Voici un exemple d'utilisation en reprenant l'objet Article crée précédemment :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django.views.generic import CreateView, ListView
|
from django.views.generic import CreateView, DetailView
|
||||||
|
|
||||||
from core.auth.mixins import CanViewMixin, CanCreateMixin
|
from core.auth.mixins import CanViewMixin, CanCreateMixin
|
||||||
|
|
||||||
from com.models import WeekmailArticle
|
from com.models import WeekmailArticle
|
||||||
|
|
||||||
|
|
||||||
# Il est important de mettre le mixin avant la classe héritée de Django
|
# Il est important de mettre le mixin avant la classe héritée de Django
|
||||||
# L'héritage multiple se fait de droite à gauche et les mixins ont besoin
|
# L'héritage multiple se fait de droite à gauche et les mixins ont besoin
|
||||||
# d'une classe de base pour fonctionner correctement.
|
# d'une classe de base pour fonctionner correctement.
|
||||||
class ArticlesListView(CanViewMixin, ListView):
|
class ArticlesDetailView(CanViewMixin, DetailView):
|
||||||
model = WeekmailArticle
|
model = WeekmailArticle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Même chose pour une vue de création de l'objet Article
|
# Même chose pour une vue de création de l'objet Article
|
||||||
class ArticlesCreateView(CanCreateMixin, CreateView):
|
class ArticlesCreateView(CanCreateMixin, CreateView):
|
||||||
model = WeekmailArticle
|
model = WeekmailArticle
|
||||||
```
|
```
|
||||||
|
|
||||||
Les mixins suivants sont implémentés :
|
Les mixins suivants sont implémentés :
|
||||||
@ -221,6 +517,76 @@ Les mixins suivants sont implémentés :
|
|||||||
Mais sur les `ListView`, on peut arriver à des temps
|
Mais sur les `ListView`, on peut arriver à des temps
|
||||||
de réponse extrêmement élevés.
|
de réponse extrêmement élevés.
|
||||||
|
|
||||||
|
### Filtrage des querysets
|
||||||
|
|
||||||
|
Récupérer tous les objets d'un queryset et vérifier pour chacun que
|
||||||
|
l'utilisateur a le droit de les voir peut-être excessivement
|
||||||
|
coûteux en ressources
|
||||||
|
(cf. l'encart ci-dessus).
|
||||||
|
|
||||||
|
Lorsqu'il est nécessaire de récupérer un certain nombre
|
||||||
|
d'objets depuis la base de données, il est donc préférable
|
||||||
|
de filtrer directement depuis le queryset.
|
||||||
|
|
||||||
|
Pour cela, certains modèles, tels que [Picture][sas.models.Picture]
|
||||||
|
peuvent être filtrés avec la méthode de queryset `viewable_by`.
|
||||||
|
Cette dernière s'utilise comme n'importe quelle autre méthode
|
||||||
|
de queryset :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sas.models import Picture
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
user = User.objects.get(username="bibou")
|
||||||
|
pictures = Picture.objects.viewable_by(user)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le résultat de la requête contiendra uniquement des éléments
|
||||||
|
que l'utilisateur sélectionné a effectivement le droit de voir.
|
||||||
|
|
||||||
|
Si vous désirez utiliser cette méthode sur un modèle
|
||||||
|
qui ne la possède pas, il est relativement facile de l'écrire :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class NewsQuerySet(models.QuerySet): # (1)!
|
||||||
|
def viewable_by(self, user: User) -> Self:
|
||||||
|
if user.has_perm("com.view_unmoderated_news"):
|
||||||
|
# si l'utilisateur peut tout voir, on retourne tout
|
||||||
|
return self
|
||||||
|
# sinon, on retourne les nouvelles modérées ou dont l'utilisateur
|
||||||
|
# est l'auteur
|
||||||
|
return self.filter(
|
||||||
|
models.Q(is_moderated=True)
|
||||||
|
| models.Q(author=user)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class News(models.Model):
|
||||||
|
is_moderated = models.BooleanField(default=False)
|
||||||
|
author = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||||
|
# ...
|
||||||
|
|
||||||
|
objects = NewsQuerySet.as_manager() # (2)!
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = [("view_unmoderated_news", "Can view non moderated news")]
|
||||||
|
```
|
||||||
|
|
||||||
|
1. On crée un `QuerySet` maison, dans lequel on définit la méthode `viewable_by`
|
||||||
|
2. Puis, on attache ce `QuerySet` à notre modèle
|
||||||
|
|
||||||
|
!!!note
|
||||||
|
|
||||||
|
Pour plus d'informations sur la création de `QuerySet` personnalisés, voir
|
||||||
|
[la documentation de django](https://docs.djangoproject.com/fr/stable/topics/db/managers/)
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
L'API utilise son propre système de permissions.
|
L'API utilise son propre système de permissions.
|
||||||
|
Loading…
Reference in New Issue
Block a user