Projet

Général

Profil

0001-WIP-support-OU-automatic-roles-20690.patch

Paul Marillonnet, 19 juillet 2018 10:30

Télécharger (12,7 ko)

Voir les différences:

Subject: [PATCH] WIP support OU automatic roles (#20690)

 src/authentic2/a2_rbac/apps.py       |   1 -
 src/authentic2/a2_rbac/models.py     | 165 ++++++++++++++-------------
 src/authentic2/custom_user/models.py |   3 +-
 src/authentic2/manager/user_views.py |  17 ++-
 src/django_rbac/managers.py          |   3 +-
 tests/test_a2_rbac.py                |  41 +++++++
 6 files changed, 140 insertions(+), 90 deletions(-)
src/authentic2/a2_rbac/apps.py
11 11
            post_delete
12 12
        from django.contrib.contenttypes.models import ContentType
13 13
        from authentic2.models import Service
14

  
15 14
        # update rbac on save to contenttype, ou and roles
16 15
        post_save.connect(
17 16
            signal_handlers.update_rbac_on_ou_post_save,
src/authentic2/a2_rbac/models.py
23 23
from . import managers, fields
24 24

  
25 25

  
26
class OrganizationalUnit(OrganizationalUnitAbstractBase):
27
    username_is_unique = models.BooleanField(
28
        blank=True,
29
        default=False,
30
        verbose_name=_('Username is unique'))
31
    email_is_unique = models.BooleanField(
32
        blank=True,
33
        default=False,
34
        verbose_name=_('Email is unique'))
35
    default = fields.UniqueBooleanField(
36
        verbose_name=_('Default organizational unit'))
37

  
38
    validate_emails = models.BooleanField(
39
        blank=True,
40
        default=False,
41
        verbose_name=_('Validate emails'))
42

  
43
    admin_perms = GenericRelation(rbac_utils.get_permission_model_name(),
44
                                  content_type_field='target_ct',
45
                                  object_id_field='target_id')
46

  
47
    objects = managers.OrganizationalUnitManager()
48

  
49
    class Meta:
50
        verbose_name = _('organizational unit')
51
        verbose_name_plural = _('organizational units')
52
        ordering = ('name',)
53
        unique_together = (
54
            ('name',),
55
            ('slug',),
56
        )
57

  
58
    def clean(self):
59
        # if we set this ou as the default one, we must unset the other one if
60
        # there is
61
        if self.default:
62
            qs = self.__class__.objects.filter(default=True)
63
            if self.pk:
64
                qs = qs.exclude(pk=self.pk)
65
            qs.update(default=None)
66
        if self.pk and not self.default \
67
           and self.__class__.objects.get(pk=self.pk).default:
68
            raise ValidationError(_('You cannot unset this organizational '
69
                                    'unit as the default, but you can set '
70
                                    'another one as the default.'))
71
        super(OrganizationalUnit, self).clean()
72

  
73
    def get_admin_role(self):
74
        '''Get or create the generic admin role for this organizational
75
           unit.
76
        '''
77
        name = _('Managers of "{ou}"').format(ou=self)
78
        slug = '_a2-managers-of-{ou.slug}'.format(ou=self)
79
        return Role.objects.get_admin_role(
80
            instance=self, name=name, slug=slug, operation=VIEW_OP,
81
            update_name=True, update_slug=True)
82

  
83
    def delete(self, *args, **kwargs):
84
        Permission.objects.filter(ou=self).delete()
85
        return super(OrganizationalUnitAbstractBase, self).delete(*args, **kwargs)
86

  
87
    def natural_key(self):
88
        return [self.slug]
89

  
90
    @classmethod
91
    @GlobalCache(timeout=5)
92
    def cached(cls):
93
        return cls.objects.all()
94

  
95
    def export_json(self):
96
        return {
97
            'uuid': self.uuid, 'slug': self.slug, 'name': self.name,
98
            'description': self.description, 'default': self.default,
99
            'email_is_unique': self.email_is_unique,
100
            'username_is_unique': self.username_is_unique,
101
            'validate_emails': self.validate_emails
102
        }
103

  
104

  
105
OrganizationalUnit._meta.natural_key = [['uuid'], ['slug'], ['name']]
106

  
107

  
108 26
class Permission(PermissionAbstractBase):
109 27
    class Meta:
110 28
        verbose_name = _('permission')
......
260 178
]
261 179

  
262 180

  
181
class OrganizationalUnit(OrganizationalUnitAbstractBase):
182
    username_is_unique = models.BooleanField(
183
        blank=True,
184
        default=False,
185
        verbose_name=_('Username is unique'))
186
    email_is_unique = models.BooleanField(
187
        blank=True,
188
        default=False,
189
        verbose_name=_('Email is unique'))
190
    default = fields.UniqueBooleanField(
191
        verbose_name=_('Default organizational unit'))
192

  
193
    validate_emails = models.BooleanField(
194
        blank=True,
195
        default=False,
196
        verbose_name=_('Validate emails'))
197

  
198
    automatic_roles = models.ManyToManyField(Role)
199

  
200
    admin_perms = GenericRelation(rbac_utils.get_permission_model_name(),
201
                                  content_type_field='target_ct',
202
                                  object_id_field='target_id')
203

  
204
    objects = managers.OrganizationalUnitManager()
205

  
206
    class Meta:
207
        verbose_name = _('organizational unit')
208
        verbose_name_plural = _('organizational units')
209
        ordering = ('name',)
210
        unique_together = (
211
            ('name',),
212
            ('slug',),
213
        )
214

  
215
    def clean(self):
216
        # if we set this ou as the default one, we must unset the other one if
217
        # there is
218
        if self.default:
219
            qs = self.__class__.objects.filter(default=True)
220
            if self.pk:
221
                qs = qs.exclude(pk=self.pk)
222
            qs.update(default=None)
223
        if self.pk and not self.default \
224
           and self.__class__.objects.get(pk=self.pk).default:
225
            raise ValidationError(_('You cannot unset this organizational '
226
                                    'unit as the default, but you can set '
227
                                    'another one as the default.'))
228
        super(OrganizationalUnit, self).clean()
229

  
230
    def get_admin_role(self):
231
        '''Get or create the generic admin role for this organizational
232
           unit.
233
        '''
234
        name = _('Managers of "{ou}"').format(ou=self)
235
        slug = '_a2-managers-of-{ou.slug}'.format(ou=self)
236
        return Role.objects.get_admin_role(
237
            instance=self, name=name, slug=slug, operation=VIEW_OP,
238
            update_name=True, update_slug=True)
239

  
240
    def delete(self, *args, **kwargs):
241
        Permission.objects.filter(ou=self).delete()
242
        return super(OrganizationalUnitAbstractBase, self).delete(*args, **kwargs)
243

  
244
    def natural_key(self):
245
        return [self.slug]
246

  
247
    @classmethod
248
    @GlobalCache(timeout=5)
249
    def cached(cls):
250
        return cls.objects.all()
251

  
252
    def export_json(self):
253
        return {
254
            'uuid': self.uuid, 'slug': self.slug, 'name': self.name,
255
            'description': self.description, 'default': self.default,
256
            'email_is_unique': self.email_is_unique,
257
            'username_is_unique': self.username_is_unique,
258
            'validate_emails': self.validate_emails
259
        }
260

  
261

  
262
OrganizationalUnit._meta.natural_key = [['uuid'], ['slug'], ['name']]
263

  
263 264
class RoleParenting(RoleParentingAbstractBase):
264 265
    class Meta(RoleParentingAbstractBase.Meta):
265 266
        verbose_name = _('role parenting relation')
src/authentic2/custom_user/models.py
150 150
    def roles_and_parents(self):
151 151
        qs1 = self.roles.all()
152 152
        qs2 = qs1.model.objects.filter(child_relation__child=qs1)
153
        qs = (qs1 | qs2).order_by('name').distinct()
153
        qs3 = qs1.model.objects.for_user(user=self)
154
        qs = (qs1 | qs2 | qs3).order_by('name').distinct()
154 155
        RoleParenting = get_role_parenting_model()
155 156
        rp_qs = RoleParenting.objects.filter(child=qs1)
156 157
        qs = qs.prefetch_related(models.Prefetch(
src/authentic2/manager/user_views.py
415 415

  
416 416
    def get_table_queryset(self):
417 417
        if self.is_ou_specified():
418
            roles = self.object.roles.all()
419
            User = get_user_model()
420 418
            Role = get_role_model()
419
            roles = Role.object.for_user(self.object)
420
            User = get_user_model()
421 421
            RoleParenting = get_role_parenting_model()
422 422
            rp_qs = RoleParenting.objects.filter(child=roles)
423 423
            qs = Role.objects.all()
......
448 448
            return redirect(request, 'a2-manager-user-detail', kwargs={'pk': self.object.pk})
449 449

  
450 450
    def form_valid(self, form):
451
        Role = get_role_model()
451 452
        user = self.object
452 453
        role = form.cleaned_data['role']
453 454
        action = form.cleaned_data['action']
......
463 464
                    hooks.call_hooks('event', name='manager-add-role-member',
464 465
                                     user=self.request.user, role=role, member=user)
465 466
            elif action == 'remove':
466
                user.roles.remove(role)
467
                hooks.call_hooks('event', name='manager-remove-role-member', user=self.request.user,
468
                                 role=role, member=user)
467
                if user.roles.filter(pk=role.pk):
468
                    user.roles.remove(role)
469
                    hooks.call_hooks('event', name='manager-remove-role-member', user=self.request.user,
470
                                     role=role, member=user)
471
                elif role in Role.objects.for_user(user):
472
                    messages.warning(
473
                        self.request,
474
                        _('User {user} has role {role} automatically derived from ou {ou}.')
475
                        .format(user=user, role=role, ou=user.ou))
469 476
        else:
470 477
            messages.warning(self.request, _('You are not authorized'))
471 478
        return super(UserRolesView, self).form_valid(form)
src/django_rbac/managers.py
78 78
        return self.by_target_ct(target).filter(target_id=target.pk)
79 79

  
80 80
    def for_user(self, user):
81
        '''Retrieve all permissions hold by an user through its role and
81
        '''Retrieve all permissions held by a user through its role and
82 82
           inherited roles.
83 83
        '''
84 84
        Role = utils.get_role_model()
85 85
        roles = Role.objects.for_user(user=user)
86
        roles.append(user.ou.automatic_roles)
86 87
        return self.filter(roles=roles)
87 88

  
88 89
    def cleanup(self):
tests/test_a2_rbac.py
180 180
    assert ou_dict['email_is_unique'] == ou.email_is_unique
181 181
    assert ou_dict['default'] == ou.default
182 182
    assert ou_dict['validate_emails'] == ou.validate_emails
183

  
184

  
185
def test_ou_automatic_roles_m2m_changed(db, ou_rando, role_ou1, role_ou2, simple_user):
186
    simple_user.ou = ou_rando
187
    simple_user.save()
188

  
189
    assert role_ou1 not in simple_user.roles
190
    assert role_ou2 not in simple_user.roles
191

  
192
    ou_rando.automatic_roles.add(role_ou1)
193
    ou_rando.automatic_roles.add(role_ou2)
194

  
195
    assert role_ou1 in simple_user.roles
196
    assert role_ou2 in simple_user.roles
197

  
198
    ou_rando.automatic_roles.remove(role_ou1)
199

  
200
    assert role_ou1 not in simple_user.roles
201

  
202
    ou_rando.automatic_roles.clear()
203

  
204
    assert role_ou2 not in simple_user.roles
205

  
206
def test_ou_automatic_roles_user_changed(db, user_ou1, user_ou2, role_ou1, role_ou2, ou1, ou2):
207
    assert ou1.automatic_roles in user_ou1.roles
208

  
209
    user_ou1.ou = ou2
210
    user_ou1.save()
211

  
212
    assert ou2.automatic_roles in user_ou1.roles
213

  
214

  
215
def test_ou_automatic_roles_no_sidefx(db, user_ou1, user_ou2, role_ou1, role_ou2, ou1, ou2):
216
    assert role_ou1 in user_ou1.roles
217

  
218
    ou1.automatic_roles.add(role1)
219
    ou1.save()
220
    ou1.automatic_roles.remove(role1)
221
    ou1.save()
222

  
223
    assert role_ou1 in user_ou1.roles
183
-