From b01204d5d2c4cbe8468d396ffc23f064c4ca86d4 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 23 May 2019 15:08:53 +0200 Subject: [PATCH 2/3] django_rbac: annotate roles with actual auth level given context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example when this is useful : A user has role R1 with an auth level of 2. R1 inherits from R2 (ie R1 is child, R2 is parent). When looking for the roles of the user (Roles.objects.for_user(user)), we will get R1 and R2. Then we might want to know which authentication level the user has to get in order to be allowed to use R2. The good answer is 2, although R2.auth_level is 1. This commit adds a way to find out the good answer. Note : il y a un cas limite où ça devrait ne pas marcher, pourtant si, et je sais pas pourquoi. Si un utilisateur a R1 et R2, deux rôles de niv 2. R1 hérite de R3 (niv 1) et R2 hérite de R4 (niv 1). Jusque là la solution fonctionne. Et maintenant si R3 hérite de R4 ? Pour déterminer le needed_auth_level de R4, je pensais qu'on se retrouverait à faire : min(niv(fils_de_R4)) = min(niv(R2, R3)) = min(2, 1) = 1 Ce qui serait bien sûr faux. Ben dans mes tests set_needed_auth_level lui met un needed_auth_level de 2, mais je sais pas ce qui le fait marcher exactement ! À confirmer par un test très clair, quand on en sera à écrire des tests. --- src/django_rbac/managers.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/django_rbac/managers.py b/src/django_rbac/managers.py index 0378b2e4..46ada3b9 100644 --- a/src/django_rbac/managers.py +++ b/src/django_rbac/managers.py @@ -2,7 +2,7 @@ import contextlib import threading from django.db import models -from django.db.models import query +from django.db.models import query, Min, Case, When from django.contrib.contenttypes.models import ContentType from django.db.models.query import Q, Prefetch from django.contrib.auth import get_user_model @@ -103,14 +103,17 @@ class IntCast(models.Func): class RoleQuerySet(query.QuerySet): - def for_user(self, user, max_auth_level=None): + def for_user(self, user, max_auth_level=None, annotate=False): qs = self.filter(members=user) if max_auth_level: qs = qs.filter(auth_level__lte=max_auth_level) qs = qs.parents() if max_auth_level: qs = qs.filter(auth_level__lte=max_auth_level) - return qs.distinct() + qs = qs.distinct() + if annotate: + qs = qs.set_needed_auth_levels(user) + return qs def parents(self, include_self=True, annotate=False): qs = self.model.objects.filter(child_relation__child__in=self) @@ -121,6 +124,12 @@ class RoleQuerySet(query.QuerySet): qs = qs.annotate(direct=models.Max(IntCast('child_relation__direct'))) return qs + def set_needed_auth_levels(self, user): + return self.annotate(needed_auth_level=Case( + When(~Q(members=user), then=Min('child_relation__child__auth_level')), + default='auth_level' + )) + def children(self, include_self=True, annotate=False): qs = self.model.objects.filter(parent_relation__parent__in=self) if include_self: -- 2.20.1