Projet

Général

Profil

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

Benjamin Dauvergne, 18 octobre 2022 10:31

Télécharger (8,9 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 | 60 +++++++++++++++++++++++++-
 tests/test_rbac.py                     | 41 ++++++++++++++++++
 2 files changed, 100 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 issubclass(parent, models.Model) and hasattr(parent, '_meta'):
40
                        _MODEL_CHILDREN.setdefault(parent, set()).add(child)
41
                        _MODEL_PARENTS.setdefault(child, set()).add(parent)
42
    return _MODEL_CHILDREN, _MODEL_PARENTS
43

  
44

  
45
def get_model_child_classes(model):
46
    return get_model_inheritance()[0].get(model) or ()
47

  
48

  
49
def get_model_parent_classes(model):
50
    return get_model_inheritance()[1].get(model) or ()
51

  
52

  
23 53
class DjangoRBACBackend:
24 54
    _DEFAULT_DJANGO_RBAC_PERMISSIONS_HIERARCHY = {
25 55
        'view': ['search'],
......
59 89
                    target = ContentType.objects.get_for_id(permission.target_id)
60 90
                    app_label = target.app_label
61 91
                    model = target.model
92
                    model_child_classes = get_model_child_classes(target.model_class())
62 93
                    if permission.ou_id:
63 94
                        key = 'ou.%s' % permission.ou_id
64 95
                    else:
......
66 97
                else:
67 98
                    app_label = target_ct.app_label
68 99
                    model = target_ct.model
100
                    model_child_classes = get_model_child_classes(target_ct.model_class)
69 101
                    key = '%s.%s' % (permission.target_ct_id, permission.target_id)
70 102
                slug = permission.operation.slug
71
                perms = [str('%s.%s_%s' % (app_label, slug, model))]
103
                perms = ['%s.%s_%s' % (app_label, slug, model)]
104
                for model_child_class in model_child_classes:
105
                    perms.append(
106
                        f'{model_child_class._meta.app_label}.{slug}_{model_child_class._meta.model_name}'
107
                    )
72 108
                perm_hierarchy = getattr(
73 109
                    settings,
74 110
                    'DJANGO_RBAC_PERMISSIONS_HIERARCHY',
......
77 113
                if slug in perm_hierarchy:
78 114
                    for other_perm in perm_hierarchy[slug]:
79 115
                        perms.append(str('%s.%s_%s' % (app_label, other_perm, model)))
116
                    for model_child_class in model_child_classes:
117
                        for other_perm in perm_hierarchy[slug]:
118
                            perms.append(
119
                                f'{model_child_class._meta.app_label}.{other_perm}_{model_child_class._meta.model_name}'
120
                            )
80 121
                permissions = perms_cache.setdefault(key, set())
81 122
                permissions.update(perms)
82 123
                # optimization for has_module_perms
......
93 134
            ct = ContentType.objects.get_for_model(obj)
94 135
            key = '%s.%s' % (ct.id, obj.pk)
95 136
            if key in perms_cache:
137
                object_permissions = perms_cache[key]
96 138
                permissions.update(perms_cache[key])
139
                # add equivalent permissions with parent app_label.model_name
140
                for parent in get_model_parent_classes(ct.model_class()):
141
                    for permission in object_permissions:
142
                        app_label, rest = permission.split('.')
143
                        operation, model_name = rest.rsplit('_', 1)
144
                        permissions.add(f'{parent._meta.app_label}.{operation}_{parent._meta.model_name}')
97 145
            for permission in perms_cache.get('__all__', set()):
98 146
                if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
99 147
                    permissions.add(permission)
148
                for parent in get_model_parent_classes(ct.model_class()):
149
                    if permission.startswith(parent._meta.app_label) and permission.endswith(
150
                        f'_{parent._meta.model_name}'
151
                    ):
152
                        permissions.add(permission)
100 153
            if hasattr(obj, 'ou_id') and obj.ou_id:
101 154
                key = 'ou.%s' % obj.ou_id
102 155
                for permission in perms_cache.get(key, ()):
103 156
                    if permission.startswith('%s.' % ct.app_label) and permission.endswith('_%s' % ct.model):
104 157
                        permissions.add(permission)
158
                    for parent in get_model_parent_classes(ct.model_class()):
159
                        if permission.startswith(parent._meta.app_label) and permission.endswith(
160
                            f'_{parent._meta.model_name}'
161
                        ):
162
                            permissions.add(permission)
105 163
            return permissions
106 164
        else:
107 165
            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
-