/
roles.py
255 lines (188 loc) · 7.93 KB
/
roles.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
from __future__ import unicode_literals
import inspect
from django.contrib.auth.models import Group, Permission
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from rolepermissions.utils import camelToSnake, camel_or_snake_to_title
from rolepermissions.exceptions import RoleDoesNotExist
registered_roles = {}
class RolesManager(object):
def __iter__(cls):
return iter(registered_roles)
@classmethod
def retrieve_role(cls, role_name):
if role_name in registered_roles:
return registered_roles[role_name]
@classmethod
def get_roles_names(cls):
return registered_roles.keys()
@classmethod
def get_roles(cls):
return registered_roles.values()
class RolesClassRegister(type):
@staticmethod
def is_abstract(meta):
if meta is not None:
return getattr(meta, 'abstract', False)
else:
return False
def __new__(cls, name, parents, dct):
meta = dct.get("Meta", None)
role_class = super(RolesClassRegister, cls).__new__(cls, name, parents, dct)
if not cls.is_abstract(meta):
registered_roles[role_class.get_name()] = role_class
return role_class
class AbstractUserRole(metaclass=RolesClassRegister):
class Meta:
abstract = True
@classmethod
def get_name(cls):
if hasattr(cls, 'role_name'):
return cls.role_name
return camelToSnake(cls.__name__)
@classmethod
def assign_role_to_user(cls, user):
"""
Assign this role to a user.
:returns: :py:class:`django.contrib.auth.models.Group` The group for the
new role.
"""
group, _created = Group.objects.get_or_create(name=cls.get_name())
user.groups.add(group)
permissions_to_add = cls.get_default_true_permissions()
user.user_permissions.add(*permissions_to_add)
return group
@classmethod
def _get_adjusted_true_permissions(cls, user):
"""
Get all true permissions for a user excluding ones that
have been explicitly revoked.
"""
from rolepermissions.permissions import available_perm_status
default_true_permissions = set()
user_permission_states = available_perm_status(user)
adjusted_true_permissions = set()
# Grab the default true permissions from each of the user's roles
for role in get_user_roles(user):
default_true_permissions.update(role.get_default_true_permissions())
# For each of those default true permissions, only keep ones
# that haven't been explicitly revoked
for permission in default_true_permissions:
if user_permission_states[permission.codename]:
adjusted_true_permissions.add(permission)
return adjusted_true_permissions
@classmethod
def remove_role_from_user(cls, user):
"""
Remove this role from a user.
WARNING: Any permissions that were explicitly granted to the user
that are also defined to be granted by this role will be revoked
when this role is revoked.
Example:
>>> class Doctor(AbstractUserRole):
... available_permissions = {
... "operate": False,
... }
>>>
>>> class Surgeon(AbstractUserRole):
... available_permissions = {
... "operate": True,
... }
>>>
>>> grant_permission(user, "operate")
>>> remove_role(user, Surgeon)
>>>
>>> has_permission(user, "operate")
False
>>>
In the example, the user no longer has the ``"operate"`` permission,
even though it was set explicitly before the ``Surgeon`` role was removed.
"""
# Grab the adjusted true permissions before the removal
current_adjusted_true_permissions = cls._get_adjusted_true_permissions(user)
group, _created = cls.get_or_create_group()
user.groups.remove(group)
# Grab the adjusted true permissions after the removal
new_adjusted_true_permissions = cls._get_adjusted_true_permissions(user)
# Remove true permissions that were default granted only by the removed role
permissions_to_remove = (current_adjusted_true_permissions
.difference(new_adjusted_true_permissions))
user.user_permissions.remove(*permissions_to_remove)
return group
@classmethod
def permission_names_list(cls):
available_permissions = getattr(cls, 'available_permissions', {})
return available_permissions.keys()
@classmethod
def get_all_permissions(cls):
permission_names = list(cls.permission_names_list())
if permission_names:
return cls.get_or_create_permissions(permission_names)
return []
@classmethod
def get_default_true_permissions(cls):
if hasattr(cls, 'available_permissions'):
permission_names = [
key for (key, default) in
cls.available_permissions.items() if default]
return cls.get_or_create_permissions(permission_names)
return []
@classmethod
def get_or_create_permissions(cls, permission_names):
user_ct = ContentType.objects.get_for_model(get_user_model())
permissions = list(Permission.objects.filter(
content_type=user_ct, codename__in=permission_names).all())
missing_permissions = set(permission_names) - set((p.codename for p in permissions))
if len(missing_permissions) > 0:
for permission_name in missing_permissions:
permission, created = get_or_create_permission(permission_name)
if created: # assert created is True
permissions.append(permission)
return permissions
@classmethod
def get_default(cls, permission_name):
return cls.available_permissions[permission_name]
@classmethod
def get_or_create_group(cls):
return Group.objects.get_or_create(name=cls.get_name())
def get_or_create_permission(codename, name=camel_or_snake_to_title):
"""
Get a Permission object from a permission name.
@:param codename: permission code name
@:param name: human-readable permissions name (str) or callable that takes codename as
argument and returns str
"""
user_ct = ContentType.objects.get_for_model(get_user_model())
return Permission.objects.get_or_create(content_type=user_ct, codename=codename,
defaults={'name': name(codename) if callable(name) else name})
def retrieve_role(role_name):
"""Get a Role object from a role name."""
return RolesManager.retrieve_role(role_name)
def get_user_roles(user):
"""Get a list of a users's roles."""
if user:
groups = user.groups.all() # Important! all() query may be cached on User with prefetch_related.
roles = (RolesManager.retrieve_role(group.name) for group in groups if group.name in RolesManager.get_roles_names())
return sorted(roles, key=lambda r: r.get_name() )
else:
return []
def _assign_or_remove_role(user, role, method_name):
role_cls = role
if not inspect.isclass(role):
role_cls = retrieve_role(role)
if not role_cls:
raise RoleDoesNotExist
getattr(role_cls, method_name)(user)
return role_cls
def assign_role(user, role):
"""Assign a role to a user."""
return _assign_or_remove_role(user, role, "assign_role_to_user")
def remove_role(user, role):
"""Remove a role from a user."""
return _assign_or_remove_role(user, role, "remove_role_from_user")
def clear_roles(user):
"""Remove all roles from a user."""
roles = get_user_roles(user)
for role in roles:
role.remove_role_from_user(user)
return roles