Projet

Général

Profil

0002-rbac-handle-inheritance-between-model-in-get_all_per.patch

Benjamin Dauvergne, 18 octobre 2022 10:50

Télécharger (9,01 ko)

Voir les différences:

Subject: [PATCH 2/2] rbac: handle inheritance between model in
 get_all_permissions (#70152)

For global and ou scoped permissions, equivalent permissions on the child
classes are added, i.e. if you have authentic2.admin_service
permission then you also have authentic2_idp_oidc.admin_oidcclient
permission (globally or scoped by an organizational unit).

For instance scoped permissions, equivalent permissions on the parent
classes are added, i.e. if you have permission
authentic2_idp_oidc.admin_oidcclient on OIDCClient(pk=1), you also have
authentic2.admin_service on the same object.
 src/authentic2/custom_user/backends.py | 64 +++++++++++++++++++++++++-
 tests/test_rbac.py                     | 41 +++++++++++++++++
 2 files changed, 104 insertions(+), 1 deletion(-)
src/authentic2/custom_user/backends.py
1 1
import copy
2 2
import functools
3 3

  
4
from django.apps import apps
4 5
from django.conf import settings
5 6
from django.contrib.contenttypes.models import ContentType
6 7
from django.core.exceptions import FieldDoesNotExist
8
from django.db import models
7 9
from django.db.models.query import Q
8 10

  
9 11
from django_rbac import utils
......
20 22
        return field.related_model
21 23

  
22 24

  
25
_MODEL_CHILDREN = None
26
_MODEL_PARENTS = None
27

  
28

  
29
def get_model_inheritance():
30
    global _MODEL_CHILDREN  # pylint: disable=global-statement
31
    global _MODEL_PARENTS  # pylint: disable=global-statement
32

  
33
    if _MODEL_CHILDREN is None or _MODEL_PARENTS is None:
34
        _MODEL_CHILDREN = {}
35
        _MODEL_PARENTS = {}
36
        for app in apps.get_app_configs():
37
            for child in app.get_models():
38
                for parent in child.__bases__:
39
                    if (
40
                        issubclass(parent, models.Model)
41
                        and hasattr(parent, '_meta')
42
                        and not parent._meta.abstract
43
                    ):
44
                        _MODEL_CHILDREN.setdefault(parent, set()).add(child)
45
                        _MODEL_PARENTS.setdefault(child, set()).add(parent)
46
    return _MODEL_CHILDREN, _MODEL_PARENTS
47

  
48

  
49
def get_model_child_classes(model):
50
    return get_model_inheritance()[0].get(model) or ()
51

  
52

  
53
def get_model_parent_classes(model):
54
    return get_model_inheritance()[1].get(model) or ()
55

  
56

  
23 57
class DjangoRBACBackend:
24 58
    _DEFAULT_DJANGO_RBAC_PERMISSIONS_HIERARCHY = {
25 59
        'view': ['search'],
......
59 93
                    target = ContentType.objects.get_for_id(permission.target_id)
60 94
                    app_label = target.app_label
61 95
                    model = target.model
96
                    model_child_classes = get_model_child_classes(target.model_class())
62 97
                    if permission.ou_id:
63 98
                        key = 'ou.%s' % permission.ou_id
64 99
                    else:
......
66 101
                else:
67 102
                    app_label = target_ct.app_label
68 103
                    model = target_ct.model
104
                    model_child_classes = get_model_child_classes(target_ct.model_class)
69 105
                    key = '%s.%s' % (permission.target_ct_id, permission.target_id)
70 106
                slug = permission.operation.slug
71
                perms = [str('%s.%s_%s' % (app_label, slug, model))]
107
                perms = ['%s.%s_%s' % (app_label, slug, model)]
108
                for model_child_class in model_child_classes:
109
                    perms.append(
110
                        f'{model_child_class._meta.app_label}.{slug}_{model_child_class._meta.model_name}'
111
                    )
72 112
                perm_hierarchy = getattr(
73 113
                    settings,
74 114
                    'DJANGO_RBAC_PERMISSIONS_HIERARCHY',
......
77 117
                if slug in perm_hierarchy:
78 118
                    for other_perm in perm_hierarchy[slug]:
79 119
                        perms.append(str('%s.%s_%s' % (app_label, other_perm, model)))
120
                    for model_child_class in model_child_classes:
121
                        for other_perm in perm_hierarchy[slug]:
122
                            perms.append(
123
                                f'{model_child_class._meta.app_label}.{other_perm}_{model_child_class._meta.model_name}'
124
                            )
80 125
                permissions = perms_cache.setdefault(key, set())
81 126
                permissions.update(perms)
82 127
                # optimization for has_module_perms
......
93 138
            ct = ContentType.objects.get_for_model(obj)
94 139
            key = '%s.%s' % (ct.id, obj.pk)
95 140
            if key in perms_cache:
141
                object_permissions = perms_cache[key]
96 142
                permissions.update(perms_cache[key])
143
                # add equivalent permissions with parent app_label.model_name
144
                for parent in get_model_parent_classes(ct.model_class()):
145
                    for permission in object_permissions:
146
                        _, rest = permission.split('.')
147
                        operation, _ = rest.rsplit('_', 1)
148
                        permissions.add(f'{parent._meta.app_label}.{operation}_{parent._meta.model_name}')
97 149
            for permission in perms_cache.get('__all__', set()):
98 150
                if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
99 151
                    permissions.add(permission)
152
                for parent in get_model_parent_classes(ct.model_class()):
153
                    if permission.startswith(parent._meta.app_label) and permission.endswith(
154
                        f'_{parent._meta.model_name}'
155
                    ):
156
                        permissions.add(permission)
100 157
            if hasattr(obj, 'ou_id') and obj.ou_id:
101 158
                key = 'ou.%s' % obj.ou_id
102 159
                for permission in perms_cache.get(key, ()):
103 160
                    if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
104 161
                        permissions.add(permission)
162
                    for parent in get_model_parent_classes(ct.model_class()):
163
                        if permission.startswith(parent._meta.app_label) and permission.endswith(
164
                            f'_{parent._meta.model_name}'
165
                        ):
166
                            permissions.add(permission)
105 167
            return permissions
106 168
        else:
107 169
            return perms_cache.get('__all__', [])
tests/test_rbac.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import pytest
17 18
from django.contrib.auth import get_user_model
18 19
from django.contrib.contenttypes.models import ContentType
19 20
from django.db import connection
......
347 348
            m[a][b] = False
348 349
        print('duration', time() - t)
349 350
        check(i)
351

  
352

  
353
class TestInheritance:
354
    @pytest.fixture
355
    def role(self, db):
356
        return Role.objects.create(name='role')
357

  
358
    @pytest.fixture
359
    def user(self, simple_user, role):
360
        simple_user.roles.add(role)
361
        return simple_user
362

  
363
    @pytest.fixture
364
    def backend(self):
365
        return backends.DjangoRBACBackend()
366

  
367
    @pytest.fixture
368
    def oidc_client_ou1(self, ou1):
369
        from authentic2_idp_oidc.models import OIDCClient
370

  
371
        return OIDCClient.objects.create(ou=ou1, slug='oidclient')
372

  
373
    def test_global(self, role, user, backend):
374
        role.permissions.add(Permission.from_str('authentic2.admin_service'))
375
        assert user.has_perm('authentic2_idp_oidc.admin_oidcclient')
376
        assert user.has_perm('authentic2_idp_oidc.search_oidcclient')
377

  
378
    def test_ou_scoped(self, role, user, backend, ou1, oidc_client_ou1):
379
        role.permissions.add(Permission.from_str('ou1 authentic2.admin_service'))
380
        assert user.has_perm('authentic2_idp_oidc.admin_oidcclient', oidc_client_ou1)
381
        assert user.has_perm('authentic2_idp_oidc.search_oidcclient', oidc_client_ou1)
382
        assert user.has_perm('authentic2.admin_service', oidc_client_ou1)
383
        assert user.has_perm('authentic2.search_service', oidc_client_ou1)
384

  
385
    def test_instance_scoped(self, role, user, backend, oidc_client_ou1):
386
        role.permissions.add(Permission.from_str('authentic2.admin_service', instance=oidc_client_ou1))
387
        assert user.has_perm('authentic2_idp_oidc.admin_oidcclient', oidc_client_ou1)
388
        assert user.has_perm('authentic2_idp_oidc.search_oidcclient', oidc_client_ou1)
389
        assert user.has_perm('authentic2.admin_service', oidc_client_ou1)
390
        assert user.has_perm('authentic2.search_service', oidc_client_ou1)
350
-