Projet

Général

Profil

0002-custom_user-move-permission-mixin-code-from-django_r.patch

Valentin Deniaud, 11 octobre 2022 18:14

Télécharger (16,9 ko)

Voir les différences:

Subject: [PATCH 2/2] custom_user: move permission mixin code from django_rbac
 (#69902)

 .../custom_user}/backends.py                  |   2 +-
 src/authentic2/custom_user/models.py          | 136 +++++++++++++++-
 src/authentic2/manager/forms.py               |   2 +-
 src/authentic2/models.py                      |   2 +-
 src/authentic2/settings.py                    |   2 +-
 src/django_rbac/models.py                     | 146 +-----------------
 tests/test_rbac.py                            |   3 +-
 7 files changed, 140 insertions(+), 153 deletions(-)
 rename src/{django_rbac => authentic2/custom_user}/backends.py (99%)
src/django_rbac/backends.py → src/authentic2/custom_user/backends.py
6 6
from django.core.exceptions import FieldDoesNotExist
7 7
from django.db.models.query import Q
8 8

  
9
from . import utils
9
from django_rbac import utils
10 10

  
11 11

  
12 12
def get_fk_model(model, fieldname):
src/authentic2/custom_user/models.py
17 17

  
18 18
import base64
19 19
import datetime
20
import functools
21
import operator
20 22
import os
21 23
import uuid
22 24

  
23
from django.contrib.auth.models import AbstractBaseUser
25
from django.contrib import auth
26
from django.contrib.auth.models import AbstractBaseUser, Group
27
from django.contrib.auth.models import Permission as AuthPermission
28
from django.contrib.auth.models import _user_has_module_perms, _user_has_perm
24 29
from django.contrib.contenttypes.fields import GenericRelation
25 30
from django.contrib.postgres.fields import JSONField
26 31
from django.core.exceptions import MultipleObjectsReturned, ValidationError
......
30 35
from django.utils import timezone
31 36
from django.utils.translation import gettext_lazy as _
32 37

  
38
try:
39
    from django.contrib.auth.models import _user_get_all_permissions
40
except ImportError:
41
    from django.contrib.auth.models import _user_get_permissions
42

  
43
    def _user_get_all_permissions(user, obj):
44
        return _user_get_permissions(user, obj, 'all')
45

  
46

  
33 47
from authentic2 import app_settings
34 48
from authentic2.a2_rbac.models import RoleParenting
35 49
from authentic2.decorators import errorcollector
......
38 52
from authentic2.utils.cache import RequestCache
39 53
from authentic2.utils.models import generate_slug
40 54
from authentic2.validators import email_validator
41
from django_rbac.models import PermissionMixin
42 55

  
56
from .backends import DjangoRBACBackend
43 57
from .managers import UserManager, UserQuerySet
44 58

  
45 59

  
......
132 146
        return IsVerified(obj)
133 147

  
134 148

  
135
class User(AbstractBaseUser, PermissionMixin):
149
class User(AbstractBaseUser):
136 150
    """
137 151
    An abstract base class implementing a fully featured User model with
138 152
    admin-compliant permissions.
......
151 165
    email_verified_date = models.DateTimeField(
152 166
        default=None, blank=True, null=True, verbose_name=_('email verified date')
153 167
    )
168
    is_superuser = models.BooleanField(
169
        _('superuser status'),
170
        default=False,
171
        help_text=_('Designates that this user has all permissions without explicitly assigning them.'),
172
    )
154 173
    is_staff = models.BooleanField(
155 174
        _('staff status'),
156 175
        default=False,
......
172 191
        swappable=False,
173 192
        on_delete=models.CASCADE,
174 193
    )
194
    groups = models.ManyToManyField(
195
        to=Group,
196
        verbose_name=_('groups'),
197
        blank=True,
198
        help_text=_(
199
            'The groups this user belongs to. A user will get all permissions granted to each of his/her'
200
            ' group.'
201
        ),
202
        related_name="user_set",
203
        related_query_name="user",
204
    )
205
    user_permissions = models.ManyToManyField(
206
        to=AuthPermission,
207
        verbose_name=_('user permissions'),
208
        blank=True,
209
        help_text=_('Specific permissions for this user.'),
210
        related_name="user_set",
211
        related_query_name="user",
212
    )
175 213

  
176 214
    # events dates
177 215
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
......
198 236
        verbose_name_plural = _('users')
199 237
        ordering = ('last_name', 'first_name', 'email', 'username')
200 238

  
239
    def get_group_permissions(self, obj=None):
240
        """
241
        Returns a list of permission strings that this user has through their
242
        groups. This method queries all available auth backends. If an object
243
        is passed in, only permissions matching this object are returned.
244
        """
245
        permissions = set()
246
        for backend in auth.get_backends():
247
            if hasattr(backend, "get_group_permissions"):
248
                permissions.update(backend.get_group_permissions(self, obj))
249
        return permissions
250

  
251
    def get_all_permissions(self, obj=None):
252
        return _user_get_all_permissions(self, obj)
253

  
254
    def has_perm(self, perm, obj=None):
255
        """
256
        Returns True if the user has the specified permission. This method
257
        queries all available auth backends, but returns immediately if any
258
        backend returns True. Thus, a user who has permission from a single
259
        auth backend is assumed to have permission in general. If an object is
260
        provided, permissions for this specific object are checked.
261
        """
262

  
263
        # Active superusers have all permissions.
264
        if self.is_active and self.is_superuser:
265
            return True
266

  
267
        # Otherwise we need to check the backends.
268
        return _user_has_perm(self, perm, obj)
269

  
270
    def has_perms(self, perm_list, obj=None):
271
        """
272
        Returns True if the user has each of the specified permissions. If
273
        object is passed, it checks if the user has all required perms for this
274
        object.
275
        """
276
        # Active superusers have all permissions.
277
        if self.is_active and self.is_superuser:
278
            return True
279

  
280
        for perm in perm_list:
281
            if not self.has_perm(perm, obj):
282
                return False
283
        return True
284

  
285
    def has_module_perms(self, app_label):
286
        """
287
        Returns True if the user has any permissions in the given app label.
288
        Uses pretty much the same logic as has_perm, above.
289
        """
290
        # Active superusers have all permissions.
291
        if self.is_active and self.is_superuser:
292
            return True
293

  
294
        return _user_has_module_perms(self, app_label)
295

  
296
    def filter_by_perm(self, perm_or_perms, qs):
297
        results = []
298
        for backend in auth.get_backends():
299
            if hasattr(backend, "filter_by_perm"):
300
                results.append(backend.filter_by_perm(self, perm_or_perms, qs))
301
        if results:
302
            return functools.reduce(operator.__or__, results)
303
        else:
304
            return qs
305

  
306
    def has_perm_any(self, perm_or_perms):
307
        # Active superusers have all permissions.
308
        if self.is_active and self.is_superuser:
309
            return True
310

  
311
        for backend in auth.get_backends():
312
            if hasattr(backend, "has_perm_any"):
313
                if backend.has_perm_any(self, perm_or_perms):
314
                    return True
315
        return False
316

  
317
    def has_ou_perm(self, perm, ou):
318
        # Active superusers have all permissions.
319
        if self.is_active and self.is_superuser:
320
            return True
321

  
322
        for backend in auth.get_backends():
323
            if hasattr(backend, "has_ou_perm"):
324
                if backend.has_ou_perm(self, perm, ou):
325
                    return True
326
        return False
327

  
328
    def ous_with_perm(self, perm, queryset=None):
329
        return DjangoRBACBackend().ous_with_perm(self, perm, queryset=queryset)
330

  
201 331
    def get_full_name(self):
202 332
        """
203 333
        Returns the first_name plus the last_name, with a space in between.
src/authentic2/manager/forms.py
33 33

  
34 34
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role, RoleAttribute
35 35
from authentic2.a2_rbac.utils import generate_slug, get_default_ou
36
from authentic2.custom_user.backends import DjangoRBACBackend
36 37
from authentic2.forms.fields import (
37 38
    CheckPasswordField,
38 39
    CommaSeparatedCharField,
......
45 46
from authentic2.passwords import generate_password, get_min_password_strength
46 47
from authentic2.utils.misc import send_email_change_email, send_password_reset_mail, send_templated_mail
47 48
from authentic2.validators import EmailValidator
48
from django_rbac.backends import DjangoRBACBackend
49 49
from django_rbac.models import Operation
50 50

  
51 51
from . import app_settings, fields, utils
src/authentic2/models.py
41 41

  
42 42
from authentic2.a2_rbac.models import Role
43 43
from authentic2.a2_rbac.utils import get_default_ou_pk
44
from authentic2.custom_user.backends import DjangoRBACBackend
44 45
from authentic2.utils.crypto import base64url_decode, base64url_encode
45 46
from authentic2.validators import HexaColourValidator
46
from django_rbac.backends import DjangoRBACBackend
47 47

  
48 48
# install our natural_key implementation
49 49
from . import managers
src/authentic2/settings.py
165 165
    'authentic2.backends.ldap_backend.LDAPBackend',
166 166
    'authentic2.backends.ldap_backend.LDAPBackendPasswordLost',
167 167
    'authentic2.backends.models_backend.DummyModelBackend',
168
    'django_rbac.backends.DjangoRBACBackend',
168
    'authentic2.custom_user.backends.DjangoRBACBackend',
169 169
    'authentic2_auth_saml.backends.SAMLBackend',
170 170
    'authentic2_auth_oidc.backends.OIDCBackend',
171 171
    'authentic2_auth_fc.backends.FcBackend',
src/django_rbac/models.py
1
import functools
2
import operator
3

  
4
from django.contrib import auth
5
from django.contrib.auth.models import Group
6
from django.contrib.auth.models import Permission as AuthPermission
7
from django.contrib.auth.models import _user_has_module_perms, _user_has_perm
8

  
9
try:
10
    from django.contrib.auth.models import _user_get_all_permissions
11
except ImportError:
12
    from django.contrib.auth.models import _user_get_permissions
13

  
14
    def _user_get_all_permissions(user, obj):
15
        return _user_get_permissions(user, obj, 'all')
16

  
17

  
18 1
from django.db import models
19 2
from django.utils.translation import gettext_lazy as _
20 3
from django.utils.translation import pgettext_lazy
21 4

  
22
from . import backends, managers
5
from . import managers
23 6

  
24 7

  
25 8
class Operation(models.Model):
......
51 34
Operation._meta.natural_key = ['slug']
52 35

  
53 36

  
54
class PermissionMixin(models.Model):
55
    """
56
    A mixin class that adds the fields and methods necessary to support
57
    Django's Group and Permission model using the ModelBackend.
58
    """
59

  
60
    is_superuser = models.BooleanField(
61
        _('superuser status'),
62
        default=False,
63
        help_text=_('Designates that this user has all permissions without explicitly assigning them.'),
64
    )
65
    groups = models.ManyToManyField(
66
        to=Group,
67
        verbose_name=_('groups'),
68
        blank=True,
69
        help_text=_(
70
            'The groups this user belongs to. A user will get all permissions granted to each of his/her'
71
            ' group.'
72
        ),
73
        related_name="user_set",
74
        related_query_name="user",
75
    )
76
    user_permissions = models.ManyToManyField(
77
        to=AuthPermission,
78
        verbose_name=_('user permissions'),
79
        blank=True,
80
        help_text=_('Specific permissions for this user.'),
81
        related_name="user_set",
82
        related_query_name="user",
83
    )
84

  
85
    class Meta:
86
        abstract = True
87

  
88
    def get_group_permissions(self, obj=None):
89
        """
90
        Returns a list of permission strings that this user has through their
91
        groups. This method queries all available auth backends. If an object
92
        is passed in, only permissions matching this object are returned.
93
        """
94
        permissions = set()
95
        for backend in auth.get_backends():
96
            if hasattr(backend, "get_group_permissions"):
97
                permissions.update(backend.get_group_permissions(self, obj))
98
        return permissions
99

  
100
    def get_all_permissions(self, obj=None):
101
        return _user_get_all_permissions(self, obj)
102

  
103
    def has_perm(self, perm, obj=None):
104
        """
105
        Returns True if the user has the specified permission. This method
106
        queries all available auth backends, but returns immediately if any
107
        backend returns True. Thus, a user who has permission from a single
108
        auth backend is assumed to have permission in general. If an object is
109
        provided, permissions for this specific object are checked.
110
        """
111

  
112
        # Active superusers have all permissions.
113
        if self.is_active and self.is_superuser:
114
            return True
115

  
116
        # Otherwise we need to check the backends.
117
        return _user_has_perm(self, perm, obj)
118

  
119
    def has_perms(self, perm_list, obj=None):
120
        """
121
        Returns True if the user has each of the specified permissions. If
122
        object is passed, it checks if the user has all required perms for this
123
        object.
124
        """
125
        # Active superusers have all permissions.
126
        if self.is_active and self.is_superuser:
127
            return True
128

  
129
        for perm in perm_list:
130
            if not self.has_perm(perm, obj):
131
                return False
132
        return True
133

  
134
    def has_module_perms(self, app_label):
135
        """
136
        Returns True if the user has any permissions in the given app label.
137
        Uses pretty much the same logic as has_perm, above.
138
        """
139
        # Active superusers have all permissions.
140
        if self.is_active and self.is_superuser:
141
            return True
142

  
143
        return _user_has_module_perms(self, app_label)
144

  
145
    def filter_by_perm(self, perm_or_perms, qs):
146
        results = []
147
        for backend in auth.get_backends():
148
            if hasattr(backend, "filter_by_perm"):
149
                results.append(backend.filter_by_perm(self, perm_or_perms, qs))
150
        if results:
151
            return functools.reduce(operator.__or__, results)
152
        else:
153
            return qs
154

  
155
    def has_perm_any(self, perm_or_perms):
156
        # Active superusers have all permissions.
157
        if self.is_active and self.is_superuser:
158
            return True
159

  
160
        for backend in auth.get_backends():
161
            if hasattr(backend, "has_perm_any"):
162
                if backend.has_perm_any(self, perm_or_perms):
163
                    return True
164
        return False
165

  
166
    def has_ou_perm(self, perm, ou):
167
        # Active superusers have all permissions.
168
        if self.is_active and self.is_superuser:
169
            return True
170

  
171
        for backend in auth.get_backends():
172
            if hasattr(backend, "has_ou_perm"):
173
                if backend.has_ou_perm(self, perm, ou):
174
                    return True
175
        return False
176

  
177
    def ous_with_perm(self, perm, queryset=None):
178
        return backends.DjangoRBACBackend().ous_with_perm(self, perm, queryset=queryset)
179

  
180

  
181 37
ADMIN_OP = Operation.register(name=pgettext_lazy('permission', 'Management'), slug='admin')
182 38
CHANGE_OP = Operation.register(name=pgettext_lazy('permission', 'Change'), slug='change')
183 39
DELETE_OP = Operation.register(name=pgettext_lazy('permission', 'Delete'), slug='delete')
tests/test_rbac.py
20 20
from django.db.models import Q
21 21
from django.test.utils import CaptureQueriesContext
22 22

  
23
from django_rbac import backends, models, utils
23
from authentic2.custom_user import backends
24
from django_rbac import models, utils
24 25

  
25 26
OU = OrganizationalUnit = utils.get_ou_model()
26 27
Permission = utils.get_permission_model()
27
-