Projet

Général

Profil

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

Paul Marillonnet, 18 juillet 2018 17:55

Télécharger (11 ko)

Voir les différences:

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

 src/authentic2/a2_rbac/apps.py            |  15 +-
 src/authentic2/a2_rbac/models.py          | 165 +++++++++++-----------
 src/authentic2/a2_rbac/signal_handlers.py |  18 +++
 tests/test_a2_rbac.py                     |  41 ++++++
 4 files changed, 156 insertions(+), 83 deletions(-)
src/authentic2/a2_rbac/apps.py
8 8
    def ready(self):
9 9
        from . import signal_handlers, models
10 10
        from django.db.models.signals import post_save, post_migrate, pre_save, \
11
            post_delete
11
            post_delete, m2m_changed
12 12
        from django.contrib.contenttypes.models import ContentType
13
        from django.contrib.auth import get_user_model
13 14
        from authentic2.models import Service
15
        from authentic2.a2_rbac.models import OrganizationalUnit
14 16

  
17
        User = get_user_model()
18

  
19
        #xxx missing user pre save here
20
        # update ou roles on user post save
21
        post_save.connect(
22
            signal_handlers.update_roles_on_user_post_save,
23
            sender=User)
24
        # update ou roles on OU automatic roles changes
25
        m2m_changed.connect(
26
            signal_handlers.update_roles_on_ou_automatic_roles_changed,
27
            sender=OrganizationalUnit.automatic_roles.through)
15 28
        # update rbac on save to contenttype, ou and roles
16 29
        post_save.connect(
17 30
            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/a2_rbac/signal_handlers.py
63 63
    if get_ou_model().objects.count() < 2:
64 64
        update_ous_admin_roles()
65 65

  
66
#xxx missing pre save here
67
def update_roles_on_user_post_save(sender, instance, created, raw, **kwargs):
68
    if instance.ou:
69
        for role in instance.ou.automatic_roles:
70
            if role not in instance.roles:
71
                instance.roles.add(role)
72

  
73
def update_roles_on_ou_automatic_roles_changed(sender, instance, action, reverse, pk_set, **kwargs):
74
    from django.contrib.auth import get_user_model
75
    if not reverse:
76
        if action == 'post_add':
77
            set_change = 'add'
78
        elif action in ('post_removed', 'post_clear'):
79
            set_change = 'delete'
80
        users = get_user_model().objects.filter(ou=instance)
81
        for user in users:
82
            set_op = gettattr(user.roles, set_change)
83
            set_op(pk_set)
66 84

  
67 85
def update_service_role_ou(sender, instance, created, raw, **kwargs):
68 86
    get_role_model().objects.filter(service=instance).update(ou=instance.ou)
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
-