0001-WIP-support-OU-automatic-roles-20690.patch
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 |
- |