0003-misc-integration-of-journal-in-manager-47155.patch
src/authentic2/custom_user/managers.py | ||
---|---|---|
78 | 78 |
self = self.distinct() |
79 | 79 |
return self |
80 | 80 | |
81 |
def find_duplicates(self, first_name, last_name, birthdate=None):
|
|
81 |
def find_duplicates(self, first_name=None, last_name=None, fullname=None, birthdate=None):
|
|
82 | 82 |
with connection.cursor() as cursor: |
83 | 83 |
cursor.execute( |
84 | 84 |
"SET pg_trgm.similarity_threshold = %f" % app_settings.A2_DUPLICATES_THRESHOLD |
85 | 85 |
) |
86 | 86 | |
87 |
name = '%s %s' % (first_name, last_name) |
|
87 |
if fullname is not None: |
|
88 |
name = fullname |
|
89 |
else: |
|
90 |
assert first_name is not None and last_name is not None |
|
91 |
name = '%s %s' % (first_name, last_name) |
|
88 | 92 |
name = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore').decode('ascii').lower() |
89 | 93 | |
90 | 94 |
qs = self.filter(deleted__isnull=True) |
src/authentic2/manager/forms.py | ||
---|---|---|
1 | 1 |
# authentic2 - versatile identity manager |
2 |
# Copyright (C) 2010-2019 Entr'ouvert
|
|
2 |
# Copyright (C) 2010-2020 Entr'ouvert
|
|
3 | 3 |
# |
4 | 4 |
# This program is free software: you can redistribute it and/or modify it |
5 | 5 |
# under the terms of the GNU Affero General Public License as published |
src/authentic2/manager/journal_event_types.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.contrib.auth import get_user_model |
|
18 |
from django.utils.translation import ugettext_lazy as _ |
|
19 | ||
20 |
from authentic2.journal_event_types import get_attributes_label, EventTypeWithService |
|
21 |
from authentic2.apps.journal.models import EventTypeDefinition |
|
22 |
from authentic2.apps.journal.utils import form_to_old_new |
|
23 | ||
24 | ||
25 |
from django_rbac.utils import get_role_model |
|
26 | ||
27 |
User = get_user_model() |
|
28 |
Role = get_role_model() |
|
29 | ||
30 | ||
31 |
class ManagerUserCreation(EventTypeDefinition): |
|
32 |
name = 'manager.user.creation' |
|
33 |
label = _('user creation') |
|
34 | ||
35 |
@classmethod |
|
36 |
def record(cls, user, session, form): |
|
37 |
super().record(user=user, session=session, references=[form.instance]) |
|
38 | ||
39 |
@classmethod |
|
40 |
def get_message(cls, event, context): |
|
41 |
(user,) = event.get_typed_references(User) |
|
42 |
# user journal page |
|
43 |
if context and context == user: |
|
44 |
return _('creation by administrator') |
|
45 |
elif user: |
|
46 |
# manager gloabal journal page |
|
47 |
return _('creation of user "%s"') % user.get_full_name() |
|
48 |
return super().get_message(event, context) |
|
49 | ||
50 | ||
51 |
class ManagerUserProfileEdit(EventTypeDefinition): |
|
52 |
name = 'manager.user.profile.edit' |
|
53 |
label = _('user profile edit') |
|
54 | ||
55 |
@classmethod |
|
56 |
def record(cls, user, session, form): |
|
57 |
super().record(user=user, session=session, references=[form.instance], data=form_to_old_new(form)) |
|
58 | ||
59 |
@classmethod |
|
60 |
def get_message(cls, event, context): |
|
61 |
(user,) = event.get_typed_references(User) |
|
62 |
new = event.get_data('new') or {} |
|
63 |
edited_attributes = ', '.join(get_attributes_label(new)) or '' |
|
64 |
if context and context == user: |
|
65 |
return _('edit by administrator (%s)') % edited_attributes |
|
66 |
elif user: |
|
67 |
user_full_name = user.get_full_name() |
|
68 |
return _('edit of user "{0}" ({1})').format(user_full_name, edited_attributes) |
|
69 |
return super().get_message(event, context) |
|
70 | ||
71 | ||
72 |
class ManagerUserEmailChangeRequest(EventTypeDefinition): |
|
73 |
name = 'manager.user.email.change.request' |
|
74 |
label = _('email change request') |
|
75 | ||
76 |
@classmethod |
|
77 |
def record(cls, user, session, form): |
|
78 |
data = { |
|
79 |
'old_email': form.instance.email, |
|
80 |
'email': form.cleaned_data.get('new_email'), |
|
81 |
} |
|
82 |
super().record(user=user, session=session, references=[form.instance], data=data) |
|
83 | ||
84 |
@classmethod |
|
85 |
def get_message(cls, event, context): |
|
86 |
(user,) = event.get_typed_references(User) |
|
87 |
new_email = event.get_data('email') |
|
88 |
if context and context == user: |
|
89 |
return _('email change for email address "%s" requested by administrator') % new_email |
|
90 |
elif user: |
|
91 |
user_full_name = user.get_full_name() |
|
92 |
return _('email change of user "{0}" for email address "{1}"').format(user_full_name, new_email) |
|
93 |
return super().get_message(event, context) |
|
94 | ||
95 | ||
96 |
class ManagerUserPasswordChange(EventTypeDefinition): |
|
97 |
name = 'manager.user.password.change' |
|
98 |
label = _('user password change') |
|
99 | ||
100 |
@classmethod |
|
101 |
def record(cls, user, session, form): |
|
102 |
data = { |
|
103 |
'generate_password': form.cleaned_data['generate_password'], |
|
104 |
'send_mail': form.cleaned_data['send_mail'], |
|
105 |
} |
|
106 |
super().record(user=user, session=session, references=[form.instance], data=data) |
|
107 | ||
108 |
@classmethod |
|
109 |
def get_message(cls, event, context): |
|
110 |
(user,) = event.get_typed_references(User) |
|
111 |
send_mail = event.get_data('send_mail') |
|
112 |
if context and context == user: |
|
113 |
if send_mail: |
|
114 |
return _('password change by administrator and notification by mail') |
|
115 |
else: |
|
116 |
return _('password change by administrator') |
|
117 |
elif user: |
|
118 |
user_full_name = user.get_full_name() |
|
119 |
if send_mail: |
|
120 |
return _('password change of user "%s" and notification by mail') % user_full_name |
|
121 |
else: |
|
122 |
return _('password change of user "%s"') % user_full_name |
|
123 |
return super().get_message(event, context) |
|
124 | ||
125 | ||
126 |
class ManagerUserPasswordResetRequest(EventTypeDefinition): |
|
127 |
name = 'manager.user.password.reset.request' |
|
128 |
label = _('user password reset request') |
|
129 | ||
130 |
@classmethod |
|
131 |
def record(cls, user, session, target_user): |
|
132 |
super().record( |
|
133 |
user=user, session=session, references=[target_user], data={'email': target_user.email} |
|
134 |
) |
|
135 | ||
136 |
@classmethod |
|
137 |
def get_message(cls, event, context): |
|
138 |
(user,) = event.get_typed_references(User) |
|
139 |
email = event.get_data('email') |
|
140 |
if context and context == user: |
|
141 |
return _('password reset request by administrator sent to "%s"') % email |
|
142 |
elif user: |
|
143 |
return _('password reset request of "{0}" sent to "{1}"').format(user.get_full_name(), email) |
|
144 |
return super().get_message(event, context) |
|
145 | ||
146 | ||
147 |
class ManagerUserPasswordChangeForce(EventTypeDefinition): |
|
148 |
name = 'manager.user.password.change.force' |
|
149 |
label = _('mandatory password change at next login set') |
|
150 | ||
151 |
@classmethod |
|
152 |
def record(cls, user, session, target_user): |
|
153 |
super().record(user=user, session=session, references=[target_user]) |
|
154 | ||
155 |
@classmethod |
|
156 |
def get_message(cls, event, context): |
|
157 |
(user,) = event.get_typed_references(User) |
|
158 |
if context and context == user: |
|
159 |
return _('mandatory password change at next login set by administrator') |
|
160 |
elif user: |
|
161 |
return _('mandatory password change at next login set for user "%s"') % user.get_full_name() |
|
162 |
return super().get_message(event, context) |
|
163 | ||
164 | ||
165 |
class ManagerUserPasswordChangeUnforce(EventTypeDefinition): |
|
166 |
name = 'manager.user.password.change.unforce' |
|
167 |
label = _('mandatory password change at next login unset') |
|
168 | ||
169 |
@classmethod |
|
170 |
def record(cls, user, session, target_user): |
|
171 |
super().record(user=user, session=session, references=[target_user]) |
|
172 | ||
173 |
@classmethod |
|
174 |
def get_message(cls, event, context): |
|
175 |
(user,) = event.get_typed_references(User) |
|
176 |
if context and context == user: |
|
177 |
return _('mandatory password change at next login unset by administrator') |
|
178 |
elif user: |
|
179 |
return _('mandatory password change at next login unset for user "%s"') % user.get_full_name() |
|
180 |
return super().get_message(event, context) |
|
181 | ||
182 | ||
183 |
class ManagerUserActivation(EventTypeDefinition): |
|
184 |
name = 'manager.user.activation' |
|
185 |
label = _('user activation') |
|
186 | ||
187 |
@classmethod |
|
188 |
def record(cls, user, session, target_user): |
|
189 |
super().record(user=user, session=session, references=[target_user]) |
|
190 | ||
191 |
@classmethod |
|
192 |
def get_message(cls, event, context): |
|
193 |
(user,) = event.get_typed_references(User) |
|
194 |
if context and context == user: |
|
195 |
return _('activation by administrator') |
|
196 |
elif user: |
|
197 |
return _('activation of user "%s"') % user.get_full_name() |
|
198 |
return super().get_message(event, context) |
|
199 | ||
200 | ||
201 |
class ManagerUserDeactivation(EventTypeDefinition): |
|
202 |
name = 'manager.user.deactivation' |
|
203 |
label = _('user deactivation') |
|
204 | ||
205 |
@classmethod |
|
206 |
def record(cls, user, session, target_user): |
|
207 |
super().record(user=user, session=session, references=[target_user]) |
|
208 | ||
209 |
@classmethod |
|
210 |
def get_message(cls, event, context): |
|
211 |
(user,) = event.get_typed_references(User) |
|
212 |
if context and context == user: |
|
213 |
return _('deactivation by administrator') |
|
214 |
elif user: |
|
215 |
return _('deactivation of user "%s"') % user.get_full_name() |
|
216 |
return super().get_message(event, context) |
|
217 | ||
218 | ||
219 |
class ManagerUserDeletion(EventTypeDefinition): |
|
220 |
name = 'manager.user.deletion' |
|
221 |
label = _('user deletion') |
|
222 | ||
223 |
@classmethod |
|
224 |
def record(cls, user, session, target_user): |
|
225 |
super().record(user=user, session=session, references=[target_user]) |
|
226 | ||
227 |
@classmethod |
|
228 |
def get_message(cls, event, context): |
|
229 |
(user,) = event.get_typed_references(User) |
|
230 |
if context and context == user: |
|
231 |
return _('deletion by administrator') |
|
232 |
elif user: |
|
233 |
return _('deletion of user "%s"') % user.get_full_name() |
|
234 |
return super().get_message(event, context) |
|
235 | ||
236 | ||
237 |
class ManagerUserSSOAuthorizationDeletion(EventTypeWithService): |
|
238 |
name = 'manager.user.sso.authorization.deletion' |
|
239 |
label = _('delete authorization') |
|
240 | ||
241 |
@classmethod |
|
242 |
def record(cls, user, session, service, target_user): |
|
243 |
super().record(user=user, session=session, service=service, references=[target_user]) |
|
244 | ||
245 |
@classmethod |
|
246 |
def get_message(cls, event, context): |
|
247 |
# first reference is to the service |
|
248 |
__, user = event.get_typed_references(None, User) |
|
249 |
service_name = cls.get_service_name(event) |
|
250 |
if context and context == user: |
|
251 |
return _('deletion of authorization of single sign on with "%s" by administrator') % service_name |
|
252 |
elif user: |
|
253 |
return _('deletion of authorization of single sign on with "%s" of user "%s"') % ( |
|
254 |
service_name, |
|
255 |
user.get_full_name(), |
|
256 |
) |
|
257 |
return super().get_message(event, context) |
|
258 | ||
259 | ||
260 |
class RoleEventsMixin(EventTypeDefinition): |
|
261 |
@classmethod |
|
262 |
def record(self, user, session, role, references=None, data=None): |
|
263 |
references = references or [] |
|
264 |
references = [role] + references |
|
265 |
data = data or {} |
|
266 |
data.update( |
|
267 |
{'role_name': str(role), 'role_uuid': role.uuid,} |
|
268 |
) |
|
269 |
super().record( |
|
270 |
user=user, session=session, references=references, data=data, |
|
271 |
) |
|
272 | ||
273 | ||
274 |
class ManagerRoleCreation(RoleEventsMixin): |
|
275 |
name = 'manager.role.creation' |
|
276 |
label = _('role creation') |
|
277 | ||
278 |
@classmethod |
|
279 |
def get_message(cls, event, context): |
|
280 |
(role,) = event.get_typed_references(Role) |
|
281 |
role = role or event.get_data('role_name') |
|
282 |
if context != role: |
|
283 |
return _('creation of role "%s"') % role |
|
284 |
else: |
|
285 |
return _('creation') |
|
286 | ||
287 | ||
288 |
class ManagerRoleEdit(RoleEventsMixin): |
|
289 |
name = 'manager.role.edit' |
|
290 |
label = _('role edit') |
|
291 | ||
292 |
@classmethod |
|
293 |
def record(cls, user, session, role, form): |
|
294 |
super().record(user=user, session=session, role=role, data=form_to_old_new(form)) |
|
295 | ||
296 |
@classmethod |
|
297 |
def get_message(cls, event, context): |
|
298 |
(role,) = event.get_typed_references(Role) |
|
299 |
role = role or event.get_data('role_name') |
|
300 |
new = event.get_data('new') |
|
301 |
edited_attributes = ', '.join(get_attributes_label(new)) or '' |
|
302 |
if context != role: |
|
303 |
return _('edit of role "%s" (%s)') % (role, edited_attributes) |
|
304 |
else: |
|
305 |
return _('edit (%s)') % edited_attributes |
|
306 | ||
307 | ||
308 |
class ManagerRoleDeletion(RoleEventsMixin): |
|
309 |
name = 'manager.role.deletion' |
|
310 |
label = _('role deletion') |
|
311 | ||
312 |
@classmethod |
|
313 |
def get_message(cls, event, context): |
|
314 |
(role,) = event.get_typed_references(Role) |
|
315 |
role = role or event.get_data('role_name') |
|
316 |
if context != role: |
|
317 |
return _('deletion of role "%s"') % role |
|
318 |
else: |
|
319 |
return _('deletion') |
|
320 | ||
321 | ||
322 |
class ManagerRoleMembershipGrant(RoleEventsMixin): |
|
323 |
name = 'manager.role.membership.grant' |
|
324 |
label = _('role membership grant') |
|
325 | ||
326 |
@classmethod |
|
327 |
def record(cls, user, session, role, member): |
|
328 |
data = {'member_name': member.get_full_name()} |
|
329 |
super().record(user=user, session=session, role=role, references=[member], data=data) |
|
330 | ||
331 |
@classmethod |
|
332 |
def get_message(cls, event, context): |
|
333 |
role, member = event.get_typed_references(Role, User) |
|
334 |
role = role or event.get_data('role_name') |
|
335 |
member = member or event.get_data('member_name') |
|
336 |
if context == member: |
|
337 |
return _('membership grant in role "%s"') % role |
|
338 |
elif context == role: |
|
339 |
return _('membership grant to user "%s"') % member |
|
340 |
else: |
|
341 |
return _('membership grant to user "{member}" in role "{role}"').format(member=member, role=role) |
|
342 | ||
343 | ||
344 |
class ManagerRoleMembershipRemoval(RoleEventsMixin): |
|
345 |
name = 'manager.role.membership.removal' |
|
346 |
label = _('role membership removal') |
|
347 | ||
348 |
@classmethod |
|
349 |
def record(cls, user, session, role, member): |
|
350 |
data = {'member_name': member.get_full_name()} |
|
351 |
super().record(user=user, session=session, role=role, references=[member], data=data) |
|
352 | ||
353 |
@classmethod |
|
354 |
def get_message(cls, event, context): |
|
355 |
role, member = event.get_typed_references(Role, User) |
|
356 |
role = role or event.get_data('role_name') |
|
357 |
member = member or event.get_data('member_name') |
|
358 |
if context == member: |
|
359 |
return _('membership removal from role "%s"') % role |
|
360 |
elif context == role: |
|
361 |
return _('membership removal of user "%s"') % member |
|
362 |
else: |
|
363 |
return _('membership removal of user "{member}" from role "{role}"').format( |
|
364 |
member=member, role=role |
|
365 |
) |
|
366 | ||
367 | ||
368 |
class ManagerRoleInheritanceAddition(RoleEventsMixin): |
|
369 |
name = 'manager.role.inheritance.addition' |
|
370 |
label = _('role inheritance addition') |
|
371 | ||
372 |
@classmethod |
|
373 |
def record(cls, user, session, parent, child): |
|
374 |
data = { |
|
375 |
'child_name': str(child), |
|
376 |
'child_uuid': child.uuid, |
|
377 |
} |
|
378 |
super().record(user=user, session=session, role=parent, references=[child], data=data) |
|
379 | ||
380 |
@classmethod |
|
381 |
def get_message(cls, event, context): |
|
382 |
parent, child = event.get_typed_references(Role, Role) |
|
383 |
parent = parent or event.get_data('role_name') |
|
384 |
child = child or event.get_data('child_name') |
|
385 |
if context == child: |
|
386 |
return _('inheritance addition from parent role "%s"') % parent |
|
387 |
elif context == parent: |
|
388 |
return _('inheritance addition to child role "%s"') % child |
|
389 |
else: |
|
390 |
return _('inheritance addition from parent role "{parent}" to child role "{child}"').format( |
|
391 |
parent=parent, child=child |
|
392 |
) |
|
393 | ||
394 | ||
395 |
class ManagerRoleInheritanceRemoval(ManagerRoleInheritanceAddition): |
|
396 |
name = 'manager.role.inheritance.removal' |
|
397 |
label = _('role inheritance removal') |
|
398 | ||
399 |
@classmethod |
|
400 |
def get_message(cls, event, context): |
|
401 |
parent, child = event.get_typed_references(Role, Role) |
|
402 |
parent = parent or event.get_data('role_name') |
|
403 |
child = child or event.get_data('child_name') |
|
404 |
if context == child: |
|
405 |
return _('inheritance removal from parent role "%s"') % parent |
|
406 |
elif context == parent: |
|
407 |
return _('inheritance removal to child role "%s"') % child |
|
408 |
else: |
|
409 |
return _('inheritance removal from parent role "{parent}" to child role "{child}"').format( |
|
410 |
parent=parent, child=child |
|
411 |
) |
|
412 | ||
413 | ||
414 |
class ManagerRoleAdministratorRoleAddition(RoleEventsMixin): |
|
415 |
name = 'manager.role.administrator.role.addition' |
|
416 |
label = _('role administrator role addition') |
|
417 | ||
418 |
@classmethod |
|
419 |
def record(cls, user, session, role, admin_role): |
|
420 |
data = { |
|
421 |
'admin_role_name': str(admin_role), |
|
422 |
'admin_role_uuid': admin_role.uuid, |
|
423 |
} |
|
424 |
super().record(user=user, session=session, role=role, references=[admin_role], data=data) |
|
425 | ||
426 |
@classmethod |
|
427 |
def get_message(cls, event, context): |
|
428 |
role, admin_role = event.get_typed_references(Role, Role) |
|
429 |
role = role or event.get_data('role_name') |
|
430 |
admin_role = admin_role or event.get('admin_role_name') |
|
431 |
if context == role: |
|
432 |
return _('addition of role "%s" as administrator') % admin_role |
|
433 |
elif context == admin_role: |
|
434 |
return _('addition as administrator of role "%s"') % role |
|
435 |
else: |
|
436 |
return _('addition of role "{admin_role}" as administrator of role "{role}"').format( |
|
437 |
admin_role=admin_role, role=role |
|
438 |
) |
|
439 | ||
440 | ||
441 |
class ManagerRoleAdministratorRoleRemoval(ManagerRoleAdministratorRoleAddition): |
|
442 |
name = 'manager.role.administrator.role.removal' |
|
443 |
label = _('role administrator role removal') |
|
444 | ||
445 |
@classmethod |
|
446 |
def get_message(cls, event, context): |
|
447 |
role, admin_role = event.get_typed_references(Role, Role) |
|
448 |
role = role or event.get_data('role_name') |
|
449 |
admin_role = admin_role or event.get('admin_role_name') |
|
450 |
if context == role: |
|
451 |
return _('removal of role "%s" as administrator') % admin_role |
|
452 |
elif context == admin_role: |
|
453 |
return _('removal as administrator of role "%s"') % role |
|
454 |
else: |
|
455 |
return _('removal of role "{admin_role}" as administrator of role "{role}"').format( |
|
456 |
admin_role=admin_role, role=role |
|
457 |
) |
|
458 | ||
459 | ||
460 |
class ManagerRoleAdministratorUserAddition(RoleEventsMixin): |
|
461 |
name = 'manager.role.administrator.user.addition' |
|
462 |
label = _('role administrator user addition') |
|
463 | ||
464 |
@classmethod |
|
465 |
def record(cls, user, session, role, admin_user): |
|
466 |
data = { |
|
467 |
'admin_user_name': admin_user.get_full_name(), |
|
468 |
'admin_user_uuid': admin_user.uuid, |
|
469 |
} |
|
470 |
super().record(user=user, session=session, role=role, references=[admin_user], data=data) |
|
471 | ||
472 |
@classmethod |
|
473 |
def get_message(cls, event, context): |
|
474 |
role, admin_user = event.get_typed_references(Role, User) |
|
475 |
role = role or event.get_data('role_name') |
|
476 |
admin_user = admin_user or event.get_data('admin_user_name') |
|
477 |
if context == role: |
|
478 |
return _('addition of user "%s" as administrator') % admin_user |
|
479 |
elif context == admin_user: |
|
480 |
return _('addition as administrator of role "%s"') % role |
|
481 |
else: |
|
482 |
return _('addition of user "{admin_user}" as administrator of role "{role}"').format( |
|
483 |
admin_user=admin_user, role=role |
|
484 |
) |
|
485 | ||
486 | ||
487 |
class ManagerRoleAdministratorUserRemoval(ManagerRoleAdministratorUserAddition): |
|
488 |
name = 'manager.role.administrator.user.removal' |
|
489 |
label = _('role administrator user removal') |
|
490 | ||
491 |
@classmethod |
|
492 |
def get_message(cls, event, context): |
|
493 |
role, admin_user = event.get_typed_references(Role, User) |
|
494 |
role = role or event.get_data('role_name') |
|
495 |
admin_user = admin_user or event.get_data('admin_user_name') |
|
496 |
if context == role: |
|
497 |
return _('removal of user "%s" as administrator') % admin_user |
|
498 |
elif context == admin_user: |
|
499 |
return _('removal as administrator of role "%s"') % role |
|
500 |
else: |
|
501 |
return _('removal of user "{admin_user}" as administrator of role "{role}"').format( |
|
502 |
admin_user=admin_user, role=role |
|
503 |
) |
src/authentic2/manager/journal_views.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import uuid |
|
18 | ||
19 |
from django.contrib.auth import get_user_model |
|
20 |
from django.core.exceptions import PermissionDenied, ValidationError |
|
21 |
from django.core.validators import EmailValidator |
|
22 |
from django.db.models import Q |
|
23 |
from django.utils.translation import ugettext_lazy as _ |
|
24 | ||
25 |
from authentic2.apps.journal.forms import JournalForm |
|
26 |
from authentic2.apps.journal.search_engine import JournalSearchEngine |
|
27 |
from authentic2.apps.journal.views import JournalView |
|
28 | ||
29 |
from . import views |
|
30 | ||
31 |
User = get_user_model() |
|
32 | ||
33 | ||
34 |
class JournalSearchEngine(JournalSearchEngine): |
|
35 |
def search_by_uuid(self, lexem): |
|
36 |
# by user uuid |
|
37 |
try: |
|
38 |
user_uuid = uuid.UUID(lexem) |
|
39 |
except ValueError: |
|
40 |
yield self.q_false |
|
41 |
else: |
|
42 |
yield Q(user__uuid=user_uuid.hex) |
|
43 |
search_by_uuid.documentation = _( |
|
44 |
'''\ |
|
45 |
You can use <tt>uuid:1234</tt> to find all events related \ |
|
46 |
to user whose UUID is <tt>1234</tt>.''' |
|
47 |
) |
|
48 |
unmatched = None |
|
49 | ||
50 |
def lexem_queries(self, lexem): |
|
51 |
queries = list(super().lexem_queries(lexem)) |
|
52 |
if queries: |
|
53 |
yield from queries |
|
54 |
elif '@' in lexem: |
|
55 |
# fallback for raw email |
|
56 |
try: |
|
57 |
EmailValidator(lexem) |
|
58 |
except ValidationError: |
|
59 |
pass |
|
60 |
else: |
|
61 |
yield from super().lexem_queries('email:' + lexem) |
|
62 |
yield from super().lexem_queries('username:' + lexem) |
|
63 | ||
64 |
def unmatched_lexems_query(self, lexems): |
|
65 |
fullname = ' '.join(lexem.strip() for lexem in lexems if lexem.strip()) |
|
66 |
if fullname: |
|
67 |
users = User.objects.find_duplicates(fullname=fullname) |
|
68 |
return self.query_for_users(users) |
|
69 | ||
70 | ||
71 |
class JournalForm(JournalForm): |
|
72 |
search_engine_class = JournalSearchEngine |
|
73 | ||
74 | ||
75 |
class BaseJournalView(views.TitleMixin, views.MediaMixin, JournalView): |
|
76 |
template_name = 'authentic2/manager/journal.html' |
|
77 |
title = _('Journal') |
|
78 |
form_class = JournalForm |
|
79 | ||
80 |
def get_context_data(self, **kwargs): |
|
81 |
ctx = super().get_context_data(**kwargs) |
|
82 |
date_hierarchy = ctx['date_hierarchy'] |
|
83 |
if date_hierarchy.title: |
|
84 |
ctx['title'] = _('Journal of %s') % date_hierarchy.title |
|
85 |
return ctx |
|
86 | ||
87 | ||
88 |
class GlobalJournalView(BaseJournalView): |
|
89 |
template_name = 'authentic2/manager/journal.html' |
|
90 | ||
91 |
def dispatch(self, request, *args, **kwargs): |
|
92 |
if not request.user.is_superuser: |
|
93 |
raise PermissionDenied |
|
94 |
return super().dispatch(request, *args, **kwargs) |
|
95 | ||
96 | ||
97 |
journal = GlobalJournalView.as_view() |
src/authentic2/manager/role_views.py | ||
---|---|---|
17 | 17 |
import json |
18 | 18 | |
19 | 19 |
from django.core.exceptions import PermissionDenied, ValidationError |
20 |
from django.utils.functional import cached_property |
|
20 | 21 |
from django.utils.translation import ugettext_lazy as _ |
21 | 22 |
from django.urls import reverse |
22 | 23 |
from django.views.generic import FormView, TemplateView |
... | ... | |
27 | 28 |
from django.db.models.query import Q, Prefetch |
28 | 29 |
from django.db.models import Count, F |
29 | 30 |
from django.contrib.auth import get_user_model |
31 |
from django.shortcuts import get_object_or_404 |
|
30 | 32 | |
31 | 33 |
from django_rbac.utils import get_role_model, get_permission_model, get_ou_model |
32 | 34 | |
33 | 35 |
from authentic2.forms.profile import modelform_factory |
34 | 36 |
from authentic2.utils import redirect |
35 | 37 |
from authentic2 import hooks, data_transfer |
38 |
from authentic2.apps.journal.views import JournalViewWithContext |
|
36 | 39 | |
37 | 40 |
from . import tables, views, resources, forms, app_settings |
38 | 41 |
from .utils import has_show_username |
42 |
from .journal_views import BaseJournalView |
|
43 | ||
44 |
OU = get_ou_model() |
|
39 | 45 | |
40 | 46 | |
41 | 47 |
class RolesMixin(object): |
... | ... | |
48 | 54 |
Permission = get_permission_model() |
49 | 55 |
permission_ct = ContentType.objects.get_for_model(Permission) |
50 | 56 |
ct_ct = ContentType.objects.get_for_model(ContentType) |
51 |
ou_ct = ContentType.objects.get_for_model(get_ou_model())
|
|
57 |
ou_ct = ContentType.objects.get_for_model(OU)
|
|
52 | 58 |
permission_qs = Permission.objects.filter(target_ct_id__in=[ct_ct.id, ou_ct.id]) \ |
53 | 59 |
.values_list('id', flat=True) |
54 | 60 |
# only non role-admin roles, they are accessed through the |
... | ... | |
61 | 67 |
return qs |
62 | 68 | |
63 | 69 | |
64 |
class RolesView(views.HideOUColumnMixin, RolesMixin, views.BaseTableView): |
|
70 |
class SearchOUMixin: |
|
71 |
@cached_property |
|
72 |
def ou(self): |
|
73 |
try: |
|
74 |
ou_id = int(self.request.GET['search-ou']) |
|
75 |
except (ValueError, KeyError): |
|
76 |
return None |
|
77 |
else: |
|
78 |
return OU.objects.filter(pk=ou_id).first() |
|
79 | ||
80 |
def get_context_data(self, **kwargs): |
|
81 |
return super().get_context_data(ou=self.ou, **kwargs) |
|
82 | ||
83 | ||
84 |
class RolesView(SearchOUMixin, views.HideOUColumnMixin, RolesMixin, |
|
85 |
views.BaseTableView): |
|
65 | 86 |
template_name = 'authentic2/manager/roles.html' |
66 | 87 |
model = get_role_model() |
67 | 88 |
table_class = tables.RoleTable |
... | ... | |
98 | 119 |
response = super(RoleAddView, self).form_valid(form) |
99 | 120 |
hooks.call_hooks('event', name='manager-add-role', user=self.request.user, |
100 | 121 |
instance=form.instance, form=form) |
122 |
self.request.journal.record('manager.role.creation', role=form.instance) |
|
101 | 123 |
return response |
102 | 124 | |
103 | 125 | |
... | ... | |
138 | 160 |
response = super(RoleEditView, self).form_valid(form) |
139 | 161 |
hooks.call_hooks('event', name='manager-edit-role', user=self.request.user, |
140 | 162 |
instance=form.instance, form=form) |
163 |
self.request.journal.record('manager.role.edit', role=form.instance, form=form) |
|
141 | 164 |
return response |
142 | 165 | |
143 | 166 |
edit = RoleEditView.as_view() |
... | ... | |
179 | 202 |
self.object.members.add(user) |
180 | 203 |
hooks.call_hooks('event', name='manager-add-role-member', |
181 | 204 |
user=self.request.user, role=self.object, member=user) |
205 |
self.request.journal.record('manager.role.membership.grant', role=self.object, member=user) |
|
182 | 206 |
elif action == 'remove': |
183 | 207 |
if not self.object.members.filter(pk=user.pk).exists(): |
184 | 208 |
messages.warning(self.request, _('User was not in this role.')) |
... | ... | |
186 | 210 |
self.object.members.remove(user) |
187 | 211 |
hooks.call_hooks('event', name='manager-remove-role-member', |
188 | 212 |
user=self.request.user, role=self.object, member=user) |
213 |
self.request.journal.record('manager.role.membership.removal', role=self.object, member=user) |
|
189 | 214 |
else: |
190 | 215 |
messages.warning(self.request, _('You are not authorized')) |
191 | 216 |
return super(RoleMembersView, self).form_valid(form) |
... | ... | |
203 | 228 |
self.object.children(include_self=False, annotate=True)) |
204 | 229 |
ctx['parents'] = views.filter_view(self.request, self.object.parents( |
205 | 230 |
include_self=False, annotate=True).order_by(F('ou').asc(nulls_first=True), 'name')) |
206 |
ctx['has_multiple_ou'] = get_ou_model().objects.count() > 1
|
|
231 |
ctx['has_multiple_ou'] = OU.objects.count() > 1
|
|
207 | 232 |
ctx['admin_roles'] = views.filter_view(self.request, |
208 | 233 |
self.object.get_admin_role().children(include_self=False, |
209 | 234 |
annotate=True)) |
... | ... | |
324 | 349 |
parent.add_child(role) |
325 | 350 |
hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user, |
326 | 351 |
parent=parent, child=role) |
352 |
self.request.journal.record('manager.role.inheritance.addition', parent=parent, child=role) |
|
327 | 353 |
return super(RoleAddChildView, self).form_valid(form) |
328 | 354 | |
329 | 355 |
add_child = RoleAddChildView.as_view() |
... | ... | |
349 | 375 |
child.add_parent(role) |
350 | 376 |
hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user, |
351 | 377 |
parent=role, child=child) |
378 |
self.request.journal.record('manager.role.inheritance.addition', parent=role, child=child) |
|
352 | 379 |
return super(RoleAddParentView, self).form_valid(form) |
353 | 380 | |
354 | 381 |
add_parent = RoleAddParentView.as_view() |
... | ... | |
376 | 403 |
self.object.remove_child(self.child) |
377 | 404 |
hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user, |
378 | 405 |
parent=self.object, child=self.child) |
406 |
self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=self.child) |
|
379 | 407 |
return redirect(self.request, self.success_url) |
380 | 408 | |
381 | 409 |
remove_child = RoleRemoveChildView.as_view() |
... | ... | |
406 | 434 |
self.object.remove_parent(self.parent) |
407 | 435 |
hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user, |
408 | 436 |
parent=self.parent, child=self.object) |
437 |
self.request.journal.record('manager.role.inheritance.removal', parent=self.parent, child=self.object) |
|
409 | 438 |
return redirect(self.request, self.success_url) |
410 | 439 | |
411 | 440 |
remove_parent = RoleRemoveParentView.as_view() |
... | ... | |
431 | 460 |
administered_role.get_admin_role().add_child(role) |
432 | 461 |
hooks.call_hooks('event', name='manager-add-admin-role', user=self.request.user, |
433 | 462 |
role=administered_role, admin_role=role) |
463 |
self.request.journal.record('manager.role.administrator.role.addition', |
|
464 |
role=administered_role, admin_role=role) |
|
434 | 465 |
return super(RoleAddAdminRoleView, self).form_valid(form) |
435 | 466 | |
436 | 467 |
add_admin_role = RoleAddAdminRoleView.as_view() |
... | ... | |
459 | 490 |
self.object.get_admin_role().remove_child(self.child) |
460 | 491 |
hooks.call_hooks('event', name='manager-remove-admin-role', |
461 | 492 |
user=self.request.user, role=self.object, admin_role=self.child) |
493 |
self.request.journal.record('manager.role.administrator.role.removal', |
|
494 |
role=self.object, admin_role=self.child) |
|
462 | 495 |
return redirect(self.request, self.success_url) |
463 | 496 | |
464 | 497 |
remove_admin_role = RoleRemoveAdminRoleView.as_view() |
... | ... | |
484 | 517 |
administered_role.get_admin_role().members.add(user) |
485 | 518 |
hooks.call_hooks('event', name='manager-add-admin-role-user', user=self.request.user, |
486 | 519 |
role=administered_role, admin=user) |
520 |
self.request.journal.record('manager.role.administrator.user.addition', |
|
521 |
role=administered_role, admin_user=user) |
|
487 | 522 |
return super(RoleAddAdminUserView, self).form_valid(form) |
488 | 523 | |
489 | 524 |
add_admin_user = RoleAddAdminUserView.as_view() |
... | ... | |
512 | 547 |
self.object.get_admin_role().members.remove(self.user) |
513 | 548 |
hooks.call_hooks('event', name='remove-remove-admin-role-user', user=self.request.user, |
514 | 549 |
role=self.object, admin=self.user) |
550 |
self.request.journal.record('manager.role.administrator.user.removal', |
|
551 |
role=self.object, admin_user=self.user) |
|
515 | 552 |
return redirect(self.request, self.success_url) |
516 | 553 | |
517 | 554 |
remove_admin_user = RoleRemoveAdminUserView.as_view() |
... | ... | |
557 | 594 | |
558 | 595 | |
559 | 596 |
roles_import = RolesImportView.as_view() |
597 | ||
598 | ||
599 |
class RoleJournal(views.MultipleOUMixin, views.PermissionMixin, JournalViewWithContext, BaseJournalView): |
|
600 |
template_name = 'authentic2/manager/role_journal.html' |
|
601 |
permissions = ['a2_rbac.view_role'] |
|
602 |
title = _('Journal') |
|
603 | ||
604 |
@cached_property |
|
605 |
def context(self): |
|
606 |
return get_object_or_404(get_role_model(), pk=self.kwargs['pk']) |
|
607 | ||
608 |
def get_context_data(self, **kwargs): |
|
609 |
ctx = super().get_context_data(**kwargs) |
|
610 |
ctx['object'] = self.context |
|
611 |
return ctx |
|
612 | ||
613 |
journal = RoleJournal.as_view() |
|
614 | ||
615 | ||
616 |
class RolesJournal(SearchOUMixin, views.MultipleOUMixin, views.PermissionMixin, |
|
617 |
JournalViewWithContext, BaseJournalView): |
|
618 |
template_name = 'authentic2/manager/roles_journal.html' |
|
619 |
permissions = ['a2_rbac.view_role'] |
|
620 |
title = _('Journal') |
|
621 | ||
622 |
@cached_property |
|
623 |
def context(self): |
|
624 |
return get_role_model() |
|
625 | ||
626 | ||
627 |
roles_journal = RolesJournal.as_view() |
src/authentic2/manager/static/authentic2/manager/js/manager.js | ||
---|---|---|
219 | 219 |
window.history.replaceState({'form': '#search-form', 'values': $('#search-form').values()}, window.document.title, window.location.href) |
220 | 220 |
} |
221 | 221 |
$(window.document).trigger('gadjo:content-update'); |
222 |
function FitToContent(id, maxHeight) |
|
223 |
{ |
|
224 |
var text = id && id.style ? id : document.getElementById(id); |
|
225 |
if ( !text ) |
|
226 |
return; |
|
227 | ||
228 |
} |
|
229 |
$('textarea.js-autoresize').each(function () { |
|
230 |
this.setAttribute('style', 'height:' + (this.scrollHeight) + 'px;overflow-y:hidden;'); |
|
231 |
}) |
|
232 |
$(document).on('input', 'textarea.js-autoresize', function () { |
|
233 |
this.style.height = 'auto'; |
|
234 |
this.style.height = (this.scrollHeight) + 'px'; |
|
235 |
return true; |
|
236 |
}); |
|
222 | 237 |
}); |
223 | 238 |
})(jQuery, window) |
src/authentic2/manager/templates/authentic2/manager/journal.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/base.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block page-title %}{{ block.super }} - {% trans "Journal" %}{% endblock %} |
|
5 | ||
6 |
{% block breadcrumb %} |
|
7 |
{{ block.super }} |
|
8 |
{% block breadcrumb-before-title %} |
|
9 |
{% endblock %} |
|
10 |
<a href="#">{% trans 'Journal' %}</a> |
|
11 |
{% for caption, url in date_hierarchy.back_urls %} |
|
12 |
<a href="{{ url }}">{{ caption }}</a> |
|
13 |
{% endfor %} |
|
14 |
{% endblock %} |
|
15 | ||
16 |
{% block sidebar %} |
|
17 |
<aside id="sidebar"> |
|
18 |
<h3>{% trans "Search" context "title" %}</h3> |
|
19 |
<div class="journal-list--search-form"> |
|
20 |
<form action="{{ form.url }}"> |
|
21 |
{{ form|with_template }} |
|
22 |
<button>{% trans "Search" %}</button> |
|
23 |
</form> |
|
24 |
</div> |
|
25 |
{% if date_hierarchy.choice_name %} |
|
26 |
<h4>{{ date_hierarchy.choice_name }}</h4> |
|
27 |
<p> |
|
28 |
{% for caption, url in date_hierarchy.choice_urls %} |
|
29 |
<a href="{{ url }}">{{ caption }}</a> |
|
30 |
{% endfor %} |
|
31 |
</p> |
|
32 |
{% endif %} |
|
33 |
<div class="documentation"> |
|
34 |
{% for documentation in form.search_engine_class.documentation %} |
|
35 |
<p>{{ documentation|safe }}</p> |
|
36 |
{% endfor %} |
|
37 |
</div> |
|
38 |
</aside> |
|
39 |
{% endblock %} |
|
40 | ||
41 |
{% block main %} |
|
42 |
{% include "journal/event_list.html" %} |
|
43 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/role_common.html | ||
---|---|---|
6 | 6 |
{% block breadcrumb %} |
7 | 7 |
{{ block.super }} |
8 | 8 |
<a href="{% url 'a2-manager-roles' %}">{% trans 'Roles' %}</a> |
9 |
{% firstof ou object.ou as current_ou %} |
|
10 |
{% if multiple_ou and current_ou %} |
|
11 |
<a href="{% url 'a2-manager-roles' %}?search-ou={{ current_ou.id }}">{{ current_ou }}</a> |
|
12 |
{% endif %} |
|
9 | 13 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/role_journal.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/journal.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block breadcrumb-before-title %} |
|
5 |
<a href="{% url 'a2-manager-roles' %}">{% trans 'Roles' %}</a> |
|
6 |
{% if multiple_ou and object.ou %} |
|
7 |
<a href="../?search-ou={{ object.ou.pk }}">{{ object.ou }}</a> |
|
8 |
{% endif %} |
|
9 |
<a href="../">{{ object }}</a> |
|
10 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/role_members.html | ||
---|---|---|
3 | 3 | |
4 | 4 |
{% block breadcrumb %} |
5 | 5 |
{{ block.super }} |
6 |
{% if multiple_ou and object.ou %} |
|
7 |
<a href="../?search-ou={{ object.ou.pk }}">{{ object.ou }}</a> |
|
8 |
{% endif %} |
|
9 | 6 |
<a href="#">{{ object }}</a> |
10 | 7 |
{% endblock %} |
11 | 8 | |
... | ... | |
19 | 16 |
{% block appbar %} |
20 | 17 |
{{ block.super }} |
21 | 18 |
<span class="actions"> |
19 |
<a class="extra-actions-menu-opener"></a> |
|
22 | 20 |
{% if not object.is_internal and view.can_delete %} |
23 | 21 |
<a rel="popup" href="{% url "a2-manager-role-delete" pk=object.pk %}">{% trans "Delete" %}</a> |
24 | 22 |
{% else %} |
... | ... | |
36 | 34 |
{% if perms.a2_rbac.admin_permission %} |
37 | 35 |
<a href="{% url "a2-manager-role-permissions" pk=object.pk %}">{% trans "Permissions" %}</a> |
38 | 36 |
{% endif %} |
37 |
<ul class="extra-actions-menu"> |
|
38 |
<li><a href="{% url "a2-manager-role-journal" pk=object.pk %}">{% trans "Journal" %}</a></li> |
|
39 |
</ul> |
|
39 | 40 |
</span> |
40 | 41 |
{% endblock %} |
41 | 42 |
src/authentic2/manager/templates/authentic2/manager/roles.html | ||
---|---|---|
3 | 3 | |
4 | 4 |
{% block page-title %}{{ block.super }} - {% trans "Roles" %}{% endblock %} |
5 | 5 | |
6 |
{% block page_title %}{% if multiple_ou and ou %}{{ ou }}{% else %}{{ block.super }}{% endif %}{% endblock %} |
|
7 | ||
6 | 8 |
{% block appbar %} |
7 | 9 |
{{ block.super }} |
8 | 10 |
<span class="actions"> |
... | ... | |
13 | 15 |
<a href="#" class="disabled" rel="popup">{% trans "Add role" %}</a> |
14 | 16 |
{% endif %} |
15 | 17 |
<ul class="extra-actions-menu"> |
18 |
<li><a href="{% url "a2-manager-roles-journal" %}{% if multiple_ou and ou %}?search-ou={{ ou.id }}{% endif %}">{% trans "Journal" %}</a></li> |
|
16 | 19 |
<li><a download href="{% url 'a2-manager-roles-export' format="json" %}?{{ request.GET.urlencode }}">{% trans 'Export' %}</a></li> |
17 | 20 |
{% if view.can_add %} |
18 | 21 |
<li><a href="{% url 'a2-manager-roles-import' %}?{{ request.GET.urlencode }}" rel="popup">{% trans 'Import' %}</a></li> |
src/authentic2/manager/templates/authentic2/manager/roles_journal.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/journal.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block breadcrumb-before-title %} |
|
5 |
<a href="{% url 'a2-manager-roles' %}">{% trans 'Roles' %}</a> |
|
6 |
{% if multiple_ou and ou %} |
|
7 |
<a href="../?search-ou={{ ou.id }}">{{ ou }}</a> |
|
8 |
{% endif %} |
|
9 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/user_detail.html | ||
---|---|---|
22 | 22 |
{% if view.is_oidc_services %} |
23 | 23 |
<li><a href="{% url "a2-manager-user-authorizations" pk=object.pk %}">{% trans "Consents" %}</a></li> |
24 | 24 |
{% endif %} |
25 |
<li><a href="{% url "a2-manager-user-journal" pk=object.pk %}">{% trans "Journal" %}</a></li> |
|
25 | 26 |
</ul> |
26 | 27 |
</span> |
27 | 28 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/user_journal.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/journal.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block breadcrumb-before-title %} |
|
5 |
<a href="{% url 'a2-manager-users' %}">{% trans 'Users' %}</a> |
|
6 |
<a href="{% url 'a2-manager-user-detail' pk=object.pk %}">{{ object.get_full_name }}</a> |
|
7 |
{% endblock %} |
src/authentic2/manager/urls.py | ||
---|---|---|
19 | 19 |
from django.views.i18n import JavaScriptCatalog |
20 | 20 |
from django.contrib.auth.decorators import login_required |
21 | 21 |
from django.utils.functional import lazy |
22 |
from . import views, role_views, ou_views, user_views, service_views |
|
22 |
from . import views, role_views, ou_views, user_views, service_views, journal_views
|
|
23 | 23 |
from ..decorators import required |
24 | 24 |
from authentic2 import utils |
25 | 25 | |
... | ... | |
67 | 67 |
name='a2-manager-user-change-email'), |
68 | 68 |
url(r'^users/(?P<pk>\d+)/su/$', user_views.su, |
69 | 69 |
name='a2-manager-user-su'), |
70 |
url(r'^users/(?P<pk>\d+)/authorizations/$', |
|
71 |
user_views.user_authorizations, |
|
72 |
name='a2-manager-user-authorizations'), |
|
73 |
url(r'^users/(?P<pk>\d+)/journal/$', |
|
74 |
user_views.user_journal, |
|
75 |
name='a2-manager-user-journal'), |
|
70 | 76 |
# by uuid |
71 | 77 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/$', user_views.user_detail, |
72 | 78 |
name='a2-manager-user-by-uuid-detail'), |
... | ... | |
81 | 87 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/change-email/$', |
82 | 88 |
user_views.user_change_email, |
83 | 89 |
name='a2-manager-user-by-uuid-change-email'), |
84 |
url(r'^users/(?P<pk>\d+)/authorizations/$',
|
|
85 |
user_views.user_authorizations,
|
|
86 |
name='a2-manager-user-authorizations'),
|
|
90 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/journal/$',
|
|
91 |
user_views.user_journal,
|
|
92 |
name='a2-manager-user-journal'),
|
|
87 | 93 | |
88 | 94 |
# Authentic2 roles |
89 | 95 |
url(r'^roles/$', role_views.listing, |
... | ... | |
94 | 100 |
name='a2-manager-role-add'), |
95 | 101 |
url(r'^roles/export/(?P<format>csv|json)/$', |
96 | 102 |
role_views.export, name='a2-manager-roles-export'), |
103 |
url(r'^roles/journal/$', role_views.roles_journal, |
|
104 |
name='a2-manager-roles-journal'), |
|
97 | 105 |
url(r'^roles/(?P<pk>\d+)/$', role_views.members, |
98 | 106 |
name='a2-manager-role-members'), |
99 | 107 |
url(r'^roles/(?P<pk>\d+)/add-child/$', role_views.add_child, |
... | ... | |
124 | 132 |
name='a2-manager-role-edit'), |
125 | 133 |
url(r'^roles/(?P<pk>\d+)/permissions/$', role_views.permissions, |
126 | 134 |
name='a2-manager-role-permissions'), |
135 |
url(r'^roles/(?P<pk>\d+)/journal/$', role_views.journal, |
|
136 |
name='a2-manager-role-journal'), |
|
127 | 137 | |
128 | 138 | |
129 | 139 |
# Authentic2 organizational units |
... | ... | |
152 | 162 |
url(r'^services/(?P<service_pk>\d+)/edit/$', service_views.edit, |
153 | 163 |
name='a2-manager-service-edit'), |
154 | 164 | |
165 |
# Journal |
|
166 |
url(r'^journal/$', journal_views.journal, |
|
167 |
name='a2-manager-journal'), |
|
168 | ||
155 | 169 |
# backoffice menu as json |
156 | 170 |
url(r'^menu.json$', views.menu_json), |
157 | 171 |
src/authentic2/manager/user_views.py | ||
---|---|---|
20 | 20 |
import operator |
21 | 21 | |
22 | 22 |
from django.db import models |
23 |
from django.utils.functional import cached_property |
|
23 | 24 |
from django.utils.translation import ugettext_lazy as _, pgettext_lazy, ugettext |
24 | 25 |
from django.utils.html import format_html |
25 | 26 |
from django.urls import reverse |
... | ... | |
33 | 34 |
from django.views.generic.edit import BaseFormView |
34 | 35 |
from django.views.generic.detail import SingleObjectMixin |
35 | 36 |
from django.http import Http404, FileResponse, HttpResponseRedirect |
37 |
from django.shortcuts import get_object_or_404 |
|
36 | 38 | |
37 | 39 |
import tablib |
38 | 40 | |
... | ... | |
41 | 43 |
from authentic2.a2_rbac.utils import get_default_ou |
42 | 44 |
from authentic2 import hooks |
43 | 45 |
from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient |
44 |
from django_rbac.utils import get_role_model, get_role_parenting_model, get_ou_model
|
|
46 |
from authentic2.apps.journal.views import JournalViewWithContext
|
|
45 | 47 | |
48 |
from django_rbac.utils import get_role_model, get_role_parenting_model, get_ou_model |
|
46 | 49 | |
47 | 50 |
from .views import (BaseTableView, BaseAddView, BaseEditView, ActionMixin, |
48 | 51 |
OtherActionsMixin, Action, ExportMixin, BaseSubTableView, |
... | ... | |
55 | 58 |
UserEditImportForm, ChooseUserAuthorizationsForm) |
56 | 59 |
from .resources import UserResource |
57 | 60 |
from .utils import get_ou_count, has_show_username |
61 |
from .journal_views import BaseJournalView |
|
58 | 62 |
from . import app_settings |
59 | 63 | |
60 | 64 |
User = get_user_model() |
... | ... | |
199 | 203 |
response = super(UserAddView, self).form_valid(form) |
200 | 204 |
hooks.call_hooks('event', name='manager-add-user', user=self.request.user, |
201 | 205 |
instance=form.instance, form=form) |
206 |
self.request.journal.record('manager.user.creation', form=form) |
|
202 | 207 |
return response |
203 | 208 | |
204 | 209 |
def get_initial(self, *args, **kwargs): |
... | ... | |
270 | 275 | |
271 | 276 |
def action_force_password_change(self, request, *args, **kwargs): |
272 | 277 |
PasswordReset.objects.get_or_create(user=self.object) |
278 |
request.journal.record('manager.user.password.change.force', target_user=self.object) |
|
273 | 279 | |
274 | 280 |
def action_activate(self, request, *args, **kwargs): |
275 | 281 |
self.object.is_active = True |
276 | 282 |
self.object.save() |
283 |
request.journal.record('manager.user.activation', target_user=self.object) |
|
277 | 284 | |
278 | 285 |
def action_deactivate(self, request, *args, **kwargs): |
279 | 286 |
if request.user == self.object: |
... | ... | |
282 | 289 |
else: |
283 | 290 |
self.object.is_active = False |
284 | 291 |
self.object.save() |
292 |
request.journal.record('manager.user.deactivation', target_user=self.object) |
|
285 | 293 | |
286 | 294 |
def action_password_reset(self, request, *args, **kwargs): |
287 | 295 |
user = self.object |
... | ... | |
292 | 300 |
return |
293 | 301 |
send_password_reset_mail(user, request=request) |
294 | 302 |
messages.info(request, _('A mail was sent to %s') % self.object.email) |
303 |
request.journal.record('manager.user.password.reset.request', target_user=self.object) |
|
295 | 304 | |
296 | 305 |
def action_delete_password_reset(self, request, *args, **kwargs): |
297 | 306 |
PasswordReset.objects.filter(user=self.object).delete() |
307 |
request.journal.record('manager.user.password.change.unforce', target_user=self.object) |
|
298 | 308 | |
299 | 309 |
def action_su(self, request, *args, **kwargs): |
300 | 310 |
return redirect(request, 'auth_logout', |
... | ... | |
414 | 424 |
self.object.email_verified = False |
415 | 425 |
self.object.save() |
416 | 426 |
response = super(UserEditView, self).form_valid(form) |
417 |
hooks.call_hooks('event', name='manager-edit-user', user=self.request.user, |
|
418 |
instance=form.instance, form=form) |
|
427 |
if form.has_changed(): |
|
428 |
hooks.call_hooks('event', name='manager-edit-user', user=self.request.user, |
|
429 |
instance=form.instance, form=form) |
|
430 |
self.request.journal.record('manager.user.profile.edit', form=form) |
|
419 | 431 |
return response |
420 | 432 | |
421 | 433 |
user_edit = UserEditView.as_view() |
... | ... | |
502 | 514 |
response = super(UserChangePasswordView, self).form_valid(form) |
503 | 515 |
hooks.call_hooks('event', name='manager-change-password', user=self.request.user, |
504 | 516 |
instance=form.instance, form=form) |
517 |
self.request.journal.record('manager.user.password.change', form=form) |
|
505 | 518 |
return response |
506 | 519 | |
507 | 520 | |
... | ... | |
617 | 630 |
user.roles.add(role) |
618 | 631 |
hooks.call_hooks('event', name='manager-add-role-member', |
619 | 632 |
user=self.request.user, role=role, member=user) |
633 |
self.request.journal.record('manager.role.membership.grant', member=user, role=role) |
|
620 | 634 |
elif action == 'remove': |
621 |
user.roles.remove(role) |
|
622 |
hooks.call_hooks('event', name='manager-remove-role-member', user=self.request.user, |
|
623 |
role=role, member=user) |
|
635 |
if not user.roles.filter(pk=role.pk).exists(): |
|
636 |
user.roles.remove(role) |
|
637 |
hooks.call_hooks('event', name='manager-remove-role-member', user=self.request.user, |
|
638 |
role=role, member=user) |
|
639 |
self.request.journal.record('manager.role.membership.removal', member=user, role=role) |
|
624 | 640 |
return super(UserRolesView, self).form_valid(form) |
625 | 641 | |
626 | 642 |
def get_search_form_kwargs(self): |
... | ... | |
658 | 674 |
self.get_object().mark_as_deleted() |
659 | 675 |
hooks.call_hooks('event', name='manager-delete-user', user=request.user, |
660 | 676 |
instance=self.object) |
677 |
request.journal.record('manager.user.deletion', target_user=self.object) |
|
661 | 678 |
return HttpResponseRedirect(self.get_success_url()) |
662 | 679 | |
663 | 680 | |
... | ... | |
882 | 899 |
if self.can_manage_authorizations: |
883 | 900 |
qs = OIDCAuthorization.objects.filter(user=self.get_object()) |
884 | 901 |
qs = qs.filter(id=auth_id.pk) |
885 |
qs.delete() |
|
902 |
oidc_authorization = qs.first() |
|
903 |
count, cascade = qs.delete() |
|
904 |
if count: |
|
905 |
self.request.journal.record( |
|
906 |
'manager.user.sso.authorization.deletion', |
|
907 |
service=oidc_authorization.client, |
|
908 |
target_user=self.object) |
|
886 | 909 |
return response |
887 | 910 | |
888 | 911 | |
889 | 912 |
user_authorizations = UserAuthorizationsView.as_view() |
913 | ||
914 | ||
915 |
class UserJournal(PermissionMixin, JournalViewWithContext, BaseJournalView): |
|
916 |
template_name = 'authentic2/manager/user_journal.html' |
|
917 |
permissions = ['custom_user.view_user'] |
|
918 |
title = _('Journal') |
|
919 | ||
920 |
@cached_property |
|
921 |
def context(self): |
|
922 |
return get_object_or_404(User, pk=self.kwargs['pk']) |
|
923 | ||
924 |
def get_context_data(self, **kwargs): |
|
925 |
ctx = super().get_context_data(**kwargs) |
|
926 |
ctx['object'] = self.context |
|
927 |
return ctx |
|
928 | ||
929 | ||
930 |
user_journal = UserJournal.as_view() |
src/authentic2/manager/views.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import base64 |
18 | 18 |
import json |
19 |
import inspect
|
|
19 |
import itertools
|
|
20 | 20 |
import pickle |
21 | 21 | |
22 | 22 |
from django.core import signing |
... | ... | |
597 | 597 |
'permission': 'authentic2.search_service', |
598 | 598 |
'slug': 'services', |
599 | 599 |
}, |
600 |
{ |
|
601 |
'class': 'icon-journal', |
|
602 |
'href': reverse_lazy('a2-manager-journal'), |
|
603 |
'label': _('Journal'), |
|
604 |
'order': -1, |
|
605 |
'permission': 'superuser', |
|
606 |
'slug': 'journal', |
|
607 |
}, |
|
600 | 608 |
] |
601 | 609 | |
602 | 610 |
def dispatch(self, request, *args, **kwargs): |
... | ... | |
606 | 614 | |
607 | 615 |
def get_homepage_entries(self): |
608 | 616 |
entries = [] |
609 |
for entry in self.default_entries: |
|
610 |
if 'permission' in entry and not self.request.user.has_perm_any(entry['permission']): |
|
611 |
continue |
|
612 |
entries.append(entry) |
|
613 |
for hook_entries in hooks.call_hooks('manager_homepage_entries', self): |
|
617 |
for hook_entries in itertools.chain( |
|
618 |
self.default_entries, |
|
619 |
hooks.call_hooks('manager_homepage_entries', self)): |
|
614 | 620 |
if not hasattr(hook_entries, 'append'): |
615 | 621 |
hook_entries = [hook_entries] |
616 | 622 |
for entry in hook_entries: |
617 |
if 'permission' in entry and not self.request.user.has_perm_any(entry['permission']): |
|
623 |
permission = entry.get('permission') |
|
624 |
if permission == 'superuser' and not self.request.user.is_superuser: |
|
625 |
continue |
|
626 |
elif permission and not self.request.user.has_perm_any(permission): |
|
618 | 627 |
continue |
619 | 628 |
entries.append(entry) |
620 | 629 |
# use possible key order to sort |
tests/test_manager_journal.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import datetime |
|
18 |
import mock |
|
19 | ||
20 |
from authentic2.custom_user.models import User |
|
21 |
from authentic2.a2_rbac.utils import get_default_ou |
|
22 |
from authentic2.a2_rbac.models import Role |
|
23 |
from authentic2.models import Service |
|
24 |
from authentic2.apps.journal.models import Event, _registry |
|
25 |
from authentic2.journal import journal |
|
26 | ||
27 |
from django.contrib.sessions.models import Session |
|
28 |
from django.utils.timezone import make_aware |
|
29 | ||
30 |
import pytest |
|
31 | ||
32 |
from .utils import login, text_content |
|
33 | ||
34 | ||
35 |
def test_journal_authorization(app, db, admin): |
|
36 |
response = login(app, admin, path='/manage/') |
|
37 |
assert 'Journal' not in response |
|
38 |
app.get('/manage/journal/', status=403) |
|
39 | ||
40 | ||
41 |
@pytest.fixture(autouse=True) |
|
42 |
def events(db, freezer): |
|
43 |
session1 = Session(session_key="1234") |
|
44 |
session2 = Session(session_key="abcd") |
|
45 | ||
46 |
ou = get_default_ou() |
|
47 |
user = User.objects.create( |
|
48 |
username="user", email="user@example.com", ou=ou, uuid="1" * 32, first_name='Johnny', last_name='doe' |
|
49 |
) |
|
50 |
agent = User.objects.create(username="agent", email="agent@example.com", ou=ou, uuid="2" * 32) |
|
51 |
role_user = Role.objects.create(name="role1", ou=ou) |
|
52 |
role_agent = Role.objects.create(name="role2", ou=ou) |
|
53 |
service = Service.objects.create(name="service") |
|
54 | ||
55 |
class EventFactory: |
|
56 |
date = make_aware(datetime.datetime(2020, 1, 1)) |
|
57 | ||
58 |
def __call__(self, name, **kwargs): |
|
59 |
freezer.move_to(self.date) |
|
60 |
journal.record(name, **kwargs) |
|
61 |
assert Event.objects.latest("timestamp").type.name == name |
|
62 |
self.date += datetime.timedelta(hours=1) |
|
63 | ||
64 |
make = EventFactory() |
|
65 |
make("user.registration.request", email=user.email) |
|
66 |
make( |
|
67 |
"user.registration", user=user, session=session1, service=service, how="franceconnect", |
|
68 |
) |
|
69 |
make("user.logout", user=user, session=session1) |
|
70 | ||
71 |
make("user.login.failure", username="user") |
|
72 |
make("user.login.failure", username="agent") |
|
73 |
make("user.login", user=user, session=session1, how="password") |
|
74 |
make("user.password.change", user=user, session=session1) |
|
75 |
edit_profile_form = mock.Mock() |
|
76 |
edit_profile_form.initial = {'email': "user@example.com", 'first_name': "John"} |
|
77 |
edit_profile_form.changed_data = ["first_name"] |
|
78 |
edit_profile_form.cleaned_data = {'first_name': "Jane"} |
|
79 |
make("user.profile.edit", user=user, session=session1, form=edit_profile_form) |
|
80 |
make("user.service.sso.authorization", user=user, session=session1, service=service) |
|
81 |
make("user.service.sso", user=user, session=session1, service=service, how="password") |
|
82 |
make("user.service.sso.unauthorization", user=user, session=session1, service=service) |
|
83 |
make("user.deletion", user=user, session=session1, service=service) |
|
84 | ||
85 |
make("user.password.reset.request", email="USER@example.com", user=user) |
|
86 |
make("user.password.reset.failure", email="USER@example.com") |
|
87 |
make("user.password.reset", user=user) |
|
88 | ||
89 |
make("user.login", user=agent, session=session2, how="saml") |
|
90 | ||
91 |
create_form = mock.Mock(spec=["instance"]) |
|
92 |
create_form.instance = user |
|
93 |
make("manager.user.creation", user=agent, session=session2, form=create_form) |
|
94 | ||
95 |
edit_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"]) |
|
96 |
edit_form.instance = user |
|
97 |
edit_form.initial = {'email': "user@example.com", 'first_name': "John"} |
|
98 |
edit_form.changed_data = ["first_name"] |
|
99 |
edit_form.cleaned_data = {'first_name': "Jane"} |
|
100 |
make("manager.user.profile.edit", user=agent, session=session2, form=edit_form) |
|
101 | ||
102 |
change_email_form = mock.Mock(spec=["instance", "cleaned_data"]) |
|
103 |
change_email_form.instance = user |
|
104 |
change_email_form.cleaned_data = {'new_email': "jane@example.com"} |
|
105 |
make( |
|
106 |
"manager.user.email.change.request", user=agent, session=session2, form=change_email_form, |
|
107 |
) |
|
108 | ||
109 |
password_change_form = mock.Mock(spec=["instance", "cleaned_data"]) |
|
110 |
password_change_form.instance = user |
|
111 |
password_change_form.cleaned_data = {'generate_password': False, 'send_mail': False} |
|
112 |
make( |
|
113 |
"manager.user.password.change", user=agent, session=session2, form=password_change_form, |
|
114 |
) |
|
115 | ||
116 |
password_change_form.cleaned_data["send_mail"] = True |
|
117 |
make( |
|
118 |
"manager.user.password.change", user=agent, session=session2, form=password_change_form, |
|
119 |
) |
|
120 | ||
121 |
make( |
|
122 |
"manager.user.password.reset.request", user=agent, session=session2, target_user=user, |
|
123 |
) |
|
124 | ||
125 |
make( |
|
126 |
"manager.user.password.change.force", user=agent, session=session2, target_user=user, |
|
127 |
) |
|
128 |
make( |
|
129 |
"manager.user.password.change.unforce", user=agent, session=session2, target_user=user, |
|
130 |
) |
|
131 | ||
132 |
make("manager.user.activation", user=agent, session=session2, target_user=user) |
|
133 |
make("manager.user.deactivation", user=agent, session=session2, target_user=user) |
|
134 |
make("manager.user.deletion", user=agent, session=session2, target_user=user) |
|
135 |
make( |
|
136 |
"manager.user.sso.authorization.deletion", |
|
137 |
user=agent, |
|
138 |
session=session2, |
|
139 |
service=service, |
|
140 |
target_user=user, |
|
141 |
) |
|
142 | ||
143 |
make("manager.role.creation", user=agent, session=session2, role=role_user) |
|
144 |
role_edit_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"]) |
|
145 |
role_edit_form.instance = role_user |
|
146 |
role_edit_form.initial = {'name': role_user.name} |
|
147 |
role_edit_form.changed_data = ["name"] |
|
148 |
role_edit_form.cleaned_data = {'name': "changed role name"} |
|
149 |
make( |
|
150 |
"manager.role.edit", user=agent, session=session2, role=role_user, form=role_edit_form, |
|
151 |
) |
|
152 |
make("manager.role.deletion", user=agent, session=session2, role=role_user) |
|
153 |
make( |
|
154 |
"manager.role.membership.grant", user=agent, session=session2, role=role_user, member=user, |
|
155 |
) |
|
156 |
make( |
|
157 |
"manager.role.membership.removal", user=agent, session=session2, role=role_user, member=user, |
|
158 |
) |
|
159 | ||
160 |
make( |
|
161 |
"manager.role.inheritance.addition", user=agent, session=session2, parent=role_agent, child=role_user, |
|
162 |
) |
|
163 |
make( |
|
164 |
"manager.role.inheritance.removal", user=agent, session=session2, parent=role_agent, child=role_user, |
|
165 |
) |
|
166 | ||
167 |
make( |
|
168 |
"manager.role.administrator.role.addition", |
|
169 |
user=agent, |
|
170 |
session=session2, |
|
171 |
role=role_user, |
|
172 |
admin_role=role_agent, |
|
173 |
) |
|
174 |
make( |
|
175 |
"manager.role.administrator.role.removal", |
|
176 |
user=agent, |
|
177 |
session=session2, |
|
178 |
role=role_user, |
|
179 |
admin_role=role_agent, |
|
180 |
) |
|
181 | ||
182 |
make( |
|
183 |
"manager.role.administrator.user.addition", |
|
184 |
user=agent, |
|
185 |
session=session2, |
|
186 |
role=role_user, |
|
187 |
admin_user=user, |
|
188 |
) |
|
189 |
make( |
|
190 |
"manager.role.administrator.user.removal", |
|
191 |
user=agent, |
|
192 |
session=session2, |
|
193 |
role=role_user, |
|
194 |
admin_user=user, |
|
195 |
) |
|
196 | ||
197 |
# verify we created at least one event for each type |
|
198 |
assert set(Event.objects.values_list("type__name", flat=True)) == set(_registry) |
|
199 | ||
200 |
return locals() |
|
201 | ||
202 | ||
203 |
def extract_journal(response): |
|
204 |
rows = [] |
|
205 |
seen_event_ids = set() |
|
206 |
while True: |
|
207 |
for tr in response.pyquery("tr[data-event-type]"): |
|
208 |
# page can overlap when they contain less than 20 items (to prevent orphan rows) |
|
209 |
event_id = tr.attrib["data-event-id"] |
|
210 |
if event_id not in seen_event_ids: |
|
211 |
rows.append(response.pyquery(tr)) |
|
212 |
seen_event_ids.add(event_id) |
|
213 |
if "Previous page" not in response: |
|
214 |
break |
|
215 |
response = response.click("Previous page", index=0) |
|
216 | ||
217 |
rows.reverse() |
|
218 |
content = [ |
|
219 |
{ |
|
220 |
'timestamp': text_content(row.find(".journal-list--timestamp-column")[0]).strip(), |
|
221 |
'type': row[0].attrib["data-event-type"], |
|
222 |
'user': text_content(row.find(".journal-list--user-column")[0]).strip(), |
|
223 |
'message': text_content(row.find(".journal-list--message-column")[0]), |
|
224 |
} |
|
225 |
for row in rows |
|
226 |
] |
|
227 |
return content |
|
228 | ||
229 | ||
230 |
def test_global_journal(app, superuser, events): |
|
231 |
response = login(app, user=superuser, path="/manage/") |
|
232 | ||
233 |
# remove event about admin login |
|
234 |
Event.objects.filter(user=superuser).delete() |
|
235 | ||
236 |
response = response.click(href="journal") |
|
237 | ||
238 |
content = extract_journal(response) |
|
239 | ||
240 |
assert content == [ |
|
241 |
{ |
|
242 |
'message': 'registration request with email "user@example.com"', |
|
243 |
'timestamp': 'Jan. 1, 2020, midnight', |
|
244 |
'type': 'user.registration.request', |
|
245 |
'user': '-', |
|
246 |
}, |
|
247 |
{ |
|
248 |
'message': 'registration using franceconnect', |
|
249 |
'timestamp': 'Jan. 1, 2020, 1 a.m.', |
|
250 |
'type': 'user.registration', |
|
251 |
'user': 'Johnny doe', |
|
252 |
}, |
|
253 |
{ |
|
254 |
'message': 'logout', |
|
255 |
'timestamp': 'Jan. 1, 2020, 2 a.m.', |
|
256 |
'type': 'user.logout', |
|
257 |
'user': 'Johnny doe', |
|
258 |
}, |
|
259 |
{ |
|
260 |
'message': 'login failure with username "user"', |
|
261 |
'timestamp': 'Jan. 1, 2020, 3 a.m.', |
|
262 |
'type': 'user.login.failure', |
|
263 |
'user': '-', |
|
264 |
}, |
|
265 |
{ |
|
266 |
'message': 'login failure with username "agent"', |
|
267 |
'timestamp': 'Jan. 1, 2020, 4 a.m.', |
|
268 |
'type': 'user.login.failure', |
|
269 |
'user': '-', |
|
270 |
}, |
|
271 |
{ |
|
272 |
'message': 'login using password', |
|
273 |
'timestamp': 'Jan. 1, 2020, 5 a.m.', |
|
274 |
'type': 'user.login', |
|
275 |
'user': 'Johnny doe', |
|
276 |
}, |
|
277 |
{ |
|
278 |
'message': 'password change', |
|
279 |
'timestamp': 'Jan. 1, 2020, 6 a.m.', |
|
280 |
'type': 'user.password.change', |
|
281 |
'user': 'Johnny doe', |
|
282 |
}, |
|
283 |
{ |
|
284 |
'message': 'profile edit (first name)', |
|
285 |
'timestamp': 'Jan. 1, 2020, 7 a.m.', |
|
286 |
'type': 'user.profile.edit', |
|
287 |
'user': 'Johnny doe', |
|
288 |
}, |
|
289 |
{ |
|
290 |
'message': 'authorization of single sign on with "service"', |
|
291 |
'timestamp': 'Jan. 1, 2020, 8 a.m.', |
|
292 |
'type': 'user.service.sso.authorization', |
|
293 |
'user': 'Johnny doe', |
|
294 |
}, |
|
295 |
{ |
|
296 |
'message': 'service single sign on with "service"', |
|
297 |
'timestamp': 'Jan. 1, 2020, 9 a.m.', |
|
298 |
'type': 'user.service.sso', |
|
299 |
'user': 'Johnny doe', |
|
300 |
}, |
|
301 |
{ |
|
302 |
'message': 'unauthorization of single sign on with "service"', |
|
303 |
'timestamp': 'Jan. 1, 2020, 10 a.m.', |
|
304 |
'type': 'user.service.sso.unauthorization', |
|
305 |
'user': 'Johnny doe', |
|
306 |
}, |
|
307 |
{ |
|
308 |
'message': 'deletion', |
|
309 |
'timestamp': 'Jan. 1, 2020, 11 a.m.', |
|
310 |
'type': 'user.deletion', |
|
311 |
'user': 'Johnny doe', |
|
312 |
}, |
|
313 |
{ |
|
314 |
'message': 'password reset request with email "user@example.com"', |
|
315 |
'timestamp': 'Jan. 1, 2020, noon', |
|
316 |
'type': 'user.password.reset.request', |
|
317 |
'user': 'Johnny doe', |
|
318 |
}, |
|
319 |
{ |
|
320 |
'message': 'password reset failure with email "USER@example.com"', |
|
321 |
'timestamp': 'Jan. 1, 2020, 1 p.m.', |
|
322 |
'type': 'user.password.reset.failure', |
|
323 |
'user': '-', |
|
324 |
}, |
|
325 |
{ |
|
326 |
'message': 'password reset', |
|
327 |
'timestamp': 'Jan. 1, 2020, 2 p.m.', |
|
328 |
'type': 'user.password.reset', |
|
329 |
'user': 'Johnny doe', |
|
330 |
}, |
|
331 |
{ |
|
332 |
'message': 'login using SAML', |
|
333 |
'timestamp': 'Jan. 1, 2020, 3 p.m.', |
|
334 |
'type': 'user.login', |
|
335 |
'user': 'agent', |
|
336 |
}, |
|
337 |
{ |
|
338 |
'message': 'creation of user "Johnny doe"', |
|
339 |
'timestamp': 'Jan. 1, 2020, 4 p.m.', |
|
340 |
'type': 'manager.user.creation', |
|
341 |
'user': 'agent', |
|
342 |
}, |
|
343 |
{ |
|
344 |
'message': 'edit of user "Johnny doe" (first name)', |
|
345 |
'timestamp': 'Jan. 1, 2020, 5 p.m.', |
|
346 |
'type': 'manager.user.profile.edit', |
|
347 |
'user': 'agent', |
|
348 |
}, |
|
349 |
{ |
|
350 |
'message': 'email change of user "Johnny doe" for email address "jane@example.com"', |
|
351 |
'timestamp': 'Jan. 1, 2020, 6 p.m.', |
|
352 |
'type': 'manager.user.email.change.request', |
|
353 |
'user': 'agent', |
|
354 |
}, |
|
355 |
{ |
|
356 |
'message': 'password change of user "Johnny doe"', |
|
357 |
'timestamp': 'Jan. 1, 2020, 7 p.m.', |
|
358 |
'type': 'manager.user.password.change', |
|
359 |
'user': 'agent', |
|
360 |
}, |
|
361 |
{ |
|
362 |
'message': 'password change of user "Johnny doe" and notification by mail', |
|
363 |
'timestamp': 'Jan. 1, 2020, 8 p.m.', |
|
364 |
'type': 'manager.user.password.change', |
|
365 |
'user': 'agent', |
|
366 |
}, |
|
367 |
{ |
|
368 |
'message': 'password reset request of "Johnny doe" sent to "user@example.com"', |
|
369 |
'timestamp': 'Jan. 1, 2020, 9 p.m.', |
|
370 |
'type': 'manager.user.password.reset.request', |
|
371 |
'user': 'agent', |
|
372 |
}, |
|
373 |
{ |
|
374 |
'message': 'mandatory password change at next login set for user "Johnny doe"', |
|
375 |
'timestamp': 'Jan. 1, 2020, 10 p.m.', |
|
376 |
'type': 'manager.user.password.change.force', |
|
377 |
'user': 'agent', |
|
378 |
}, |
|
379 |
{ |
|
380 |
'message': 'mandatory password change at next login unset for user "Johnny doe"', |
|
381 |
'timestamp': 'Jan. 1, 2020, 11 p.m.', |
|
382 |
'type': 'manager.user.password.change.unforce', |
|
383 |
'user': 'agent', |
|
384 |
}, |
|
385 |
{ |
|
386 |
'message': 'activation of user "Johnny doe"', |
|
387 |
'timestamp': 'Jan. 2, 2020, midnight', |
|
388 |
'type': 'manager.user.activation', |
|
389 |
'user': 'agent', |
|
390 |
}, |
|
391 |
{ |
|
392 |
'message': 'deactivation of user "Johnny doe"', |
|
393 |
'timestamp': 'Jan. 2, 2020, 1 a.m.', |
|
394 |
'type': 'manager.user.deactivation', |
|
395 |
'user': 'agent', |
|
396 |
}, |
|
397 |
{ |
|
398 |
'message': 'deletion of user "Johnny doe"', |
|
399 |
'timestamp': 'Jan. 2, 2020, 2 a.m.', |
|
400 |
'type': 'manager.user.deletion', |
|
401 |
'user': 'agent', |
|
402 |
}, |
|
403 |
{ |
|
404 |
'message': 'deletion of authorization of single sign on with "service" of ' 'user "Johnny doe"', |
|
405 |
'timestamp': 'Jan. 2, 2020, 3 a.m.', |
|
406 |
'type': 'manager.user.sso.authorization.deletion', |
|
407 |
'user': 'agent', |
|
408 |
}, |
|
409 |
{ |
|
410 |
'message': 'creation of role "role1"', |
|
411 |
'timestamp': 'Jan. 2, 2020, 4 a.m.', |
|
412 |
'type': 'manager.role.creation', |
|
413 |
'user': 'agent', |
|
414 |
}, |
|
415 |
{ |
|
416 |
'message': 'edit of role "role1" (name)', |
|
417 |
'timestamp': 'Jan. 2, 2020, 5 a.m.', |
|
418 |
'type': 'manager.role.edit', |
|
419 |
'user': 'agent', |
|
420 |
}, |
|
421 |
{ |
|
422 |
'message': 'deletion of role "role1"', |
|
423 |
'timestamp': 'Jan. 2, 2020, 6 a.m.', |
|
424 |
'type': 'manager.role.deletion', |
|
425 |
'user': 'agent', |
|
426 |
}, |
|
427 |
{ |
|
428 |
'message': 'membership grant to user "user (111111)" in role "role1"', |
|
429 |
'timestamp': 'Jan. 2, 2020, 7 a.m.', |
|
430 |
'type': 'manager.role.membership.grant', |
|
431 |
'user': 'agent', |
|
432 |
}, |
|
433 |
{ |
|
434 |
'message': 'membership removal of user "user (111111)" from role "role1"', |
|
435 |
'timestamp': 'Jan. 2, 2020, 8 a.m.', |
|
436 |
'type': 'manager.role.membership.removal', |
|
437 |
'user': 'agent', |
|
438 |
}, |
|
439 |
{ |
|
440 |
'message': 'inheritance addition from parent role "role2" to child role ' '"role1"', |
|
441 |
'timestamp': 'Jan. 2, 2020, 9 a.m.', |
|
442 |
'type': 'manager.role.inheritance.addition', |
|
443 |
'user': 'agent', |
|
444 |
}, |
|
445 |
{ |
|
446 |
'message': 'inheritance removal from parent role "role2" to child role ' '"role1"', |
|
447 |
'timestamp': 'Jan. 2, 2020, 10 a.m.', |
|
448 |
'type': 'manager.role.inheritance.removal', |
|
449 |
'user': 'agent', |
|
450 |
}, |
|
451 |
{ |
|
452 |
'message': 'addition of role "role2" as administrator of role "role1"', |
|
453 |
'timestamp': 'Jan. 2, 2020, 11 a.m.', |
|
454 |
'type': 'manager.role.administrator.role.addition', |
|
455 |
'user': 'agent', |
|
456 |
}, |
|
457 |
{ |
|
458 |
'message': 'removal of role "role2" as administrator of role "role1"', |
|
459 |
'timestamp': 'Jan. 2, 2020, noon', |
|
460 |
'type': 'manager.role.administrator.role.removal', |
|
461 |
'user': 'agent', |
|
462 |
}, |
|
463 |
{ |
|
464 |
'message': 'addition of user "user (111111)" as administrator of role ' '"role1"', |
|
465 |
'timestamp': 'Jan. 2, 2020, 1 p.m.', |
|
466 |
'type': 'manager.role.administrator.user.addition', |
|
467 |
'user': 'agent', |
|
468 |
}, |
|
469 |
{ |
|
470 |
'message': 'removal of user "user (111111)" as administrator of role "role1"', |
|
471 |
'timestamp': 'Jan. 2, 2020, 2 p.m.', |
|
472 |
'type': 'manager.role.administrator.user.removal', |
|
473 |
'user': 'agent', |
|
474 |
}, |
|
475 |
] |
|
476 | ||
477 | ||
478 |
def test_user_journal(app, superuser, events): |
|
479 |
response = login(app, user=superuser, path="/manage/") |
|
480 |
user = User.objects.get(username="user") |
|
481 | ||
482 |
response = app.get("/manage/users/%s/journal/" % user.id) |
|
483 |
content = extract_journal(response) |
|
484 | ||
485 |
assert content == [ |
|
486 |
{ |
|
487 |
'message': 'registration using franceconnect', |
|
488 |
'timestamp': 'Jan. 1, 2020, 1 a.m.', |
|
489 |
'type': 'user.registration', |
|
490 |
'user': 'Johnny doe', |
|
491 |
}, |
|
492 |
{ |
|
493 |
'message': 'logout', |
|
494 |
'timestamp': 'Jan. 1, 2020, 2 a.m.', |
|
495 |
'type': 'user.logout', |
|
496 |
'user': 'Johnny doe', |
|
497 |
}, |
|
498 |
{ |
|
499 |
'message': 'login using password', |
|
500 |
'timestamp': 'Jan. 1, 2020, 5 a.m.', |
|
501 |
'type': 'user.login', |
|
502 |
'user': 'Johnny doe', |
|
503 |
}, |
|
504 |
{ |
|
505 |
'message': 'password change', |
|
506 |
'timestamp': 'Jan. 1, 2020, 6 a.m.', |
|
507 |
'type': 'user.password.change', |
|
508 |
'user': 'Johnny doe', |
|
509 |
}, |
|
510 |
{ |
|
511 |
'message': 'profile edit (first name)', |
|
512 |
'timestamp': 'Jan. 1, 2020, 7 a.m.', |
|
513 |
'type': 'user.profile.edit', |
|
514 |
'user': 'Johnny doe', |
|
515 |
}, |
|
516 |
{ |
|
517 |
'message': 'authorization of single sign on with "service"', |
|
518 |
'timestamp': 'Jan. 1, 2020, 8 a.m.', |
|
519 |
'type': 'user.service.sso.authorization', |
|
520 |
'user': 'Johnny doe', |
|
521 |
}, |
|
522 |
{ |
|
523 |
'message': 'service single sign on with "service"', |
|
524 |
'timestamp': 'Jan. 1, 2020, 9 a.m.', |
|
525 |
'type': 'user.service.sso', |
|
526 |
'user': 'Johnny doe', |
|
527 |
}, |
|
528 |
{ |
|
529 |
'message': 'unauthorization of single sign on with "service"', |
|
530 |
'timestamp': 'Jan. 1, 2020, 10 a.m.', |
|
531 |
'type': 'user.service.sso.unauthorization', |
|
532 |
'user': 'Johnny doe', |
|
533 |
}, |
|
534 |
{ |
|
535 |
'message': 'deletion', |
|
536 |
'timestamp': 'Jan. 1, 2020, 11 a.m.', |
|
537 |
'type': 'user.deletion', |
|
538 |
'user': 'Johnny doe', |
|
539 |
}, |
|
540 |
{ |
|
541 |
'message': 'password reset request with email "user@example.com"', |
|
542 |
'timestamp': 'Jan. 1, 2020, noon', |
|
543 |
'type': 'user.password.reset.request', |
|
544 |
'user': 'Johnny doe', |
|
545 |
}, |
|
546 |
{ |
|
547 |
'message': 'password reset', |
|
548 |
'timestamp': 'Jan. 1, 2020, 2 p.m.', |
|
549 |
'type': 'user.password.reset', |
|
550 |
'user': 'Johnny doe', |
|
551 |
}, |
|
552 |
{ |
|
553 |
'message': 'creation by administrator', |
|
554 |
'timestamp': 'Jan. 1, 2020, 4 p.m.', |
|
555 |
'type': 'manager.user.creation', |
|
556 |
'user': 'agent', |
|
557 |
}, |
|
558 |
{ |
|
559 |
'message': 'edit by administrator (first name)', |
|
560 |
'timestamp': 'Jan. 1, 2020, 5 p.m.', |
|
561 |
'type': 'manager.user.profile.edit', |
|
562 |
'user': 'agent', |
|
563 |
}, |
|
564 |
{ |
|
565 |
'message': 'email change for email address "jane@example.com" requested by administrator', |
|
566 |
'timestamp': 'Jan. 1, 2020, 6 p.m.', |
|
567 |
'type': 'manager.user.email.change.request', |
|
568 |
'user': 'agent', |
|
569 |
}, |
|
570 |
{ |
|
571 |
'message': 'password change by administrator', |
|
572 |
'timestamp': 'Jan. 1, 2020, 7 p.m.', |
|
573 |
'type': 'manager.user.password.change', |
|
574 |
'user': 'agent', |
|
575 |
}, |
|
576 |
{ |
|
577 |
'message': 'password change by administrator and notification by mail', |
|
578 |
'timestamp': 'Jan. 1, 2020, 8 p.m.', |
|
579 |
'type': 'manager.user.password.change', |
|
580 |
'user': 'agent', |
|
581 |
}, |
|
582 |
{ |
|
583 |
'message': "password reset request by administrator sent to " '"user@example.com"', |
|
584 |
'timestamp': 'Jan. 1, 2020, 9 p.m.', |
|
585 |
'type': 'manager.user.password.reset.request', |
|
586 |
'user': 'agent', |
|
587 |
}, |
|
588 |
{ |
|
589 |
'message': 'mandatory password change at next login set by administrator', |
|
590 |
'timestamp': 'Jan. 1, 2020, 10 p.m.', |
|
591 |
'type': 'manager.user.password.change.force', |
|
592 |
'user': 'agent', |
|
593 |
}, |
|
594 |
{ |
|
595 |
'message': 'mandatory password change at next login unset by administrator', |
|
596 |
'timestamp': 'Jan. 1, 2020, 11 p.m.', |
|
597 |
'type': 'manager.user.password.change.unforce', |
|
598 |
'user': 'agent', |
|
599 |
}, |
|
600 |
{ |
|
601 |
'message': 'activation by administrator', |
|
602 |
'timestamp': 'Jan. 2, 2020, midnight', |
|
603 |
'type': 'manager.user.activation', |
|
604 |
'user': 'agent', |
|
605 |
}, |
|
606 |
{ |
|
607 |
'message': 'deactivation by administrator', |
|
608 |
'timestamp': 'Jan. 2, 2020, 1 a.m.', |
|
609 |
'type': 'manager.user.deactivation', |
|
610 |
'user': 'agent', |
|
611 |
}, |
|
612 |
{ |
|
613 |
'message': 'deletion by administrator', |
|
614 |
'timestamp': 'Jan. 2, 2020, 2 a.m.', |
|
615 |
'type': 'manager.user.deletion', |
|
616 |
'user': 'agent', |
|
617 |
}, |
|
618 |
{ |
|
619 |
'message': 'deletion of authorization of single sign on with "service" by ' "administrator", |
|
620 |
'timestamp': 'Jan. 2, 2020, 3 a.m.', |
|
621 |
'type': 'manager.user.sso.authorization.deletion', |
|
622 |
'user': 'agent', |
|
623 |
}, |
|
624 |
{ |
|
625 |
'message': 'membership grant in role "role1"', |
|
626 |
'timestamp': 'Jan. 2, 2020, 7 a.m.', |
|
627 |
'type': 'manager.role.membership.grant', |
|
628 |
'user': 'agent', |
|
629 |
}, |
|
630 |
{ |
|
631 |
'message': 'membership removal from role "role1"', |
|
632 |
'timestamp': 'Jan. 2, 2020, 8 a.m.', |
|
633 |
'type': 'manager.role.membership.removal', |
|
634 |
'user': 'agent', |
|
635 |
}, |
|
636 |
{ |
|
637 |
'message': 'addition as administrator of role "role1"', |
|
638 |
'timestamp': 'Jan. 2, 2020, 1 p.m.', |
|
639 |
'type': 'manager.role.administrator.user.addition', |
|
640 |
'user': 'agent', |
|
641 |
}, |
|
642 |
{ |
|
643 |
'message': 'removal as administrator of role "role1"', |
|
644 |
'timestamp': 'Jan. 2, 2020, 2 p.m.', |
|
645 |
'type': 'manager.role.administrator.user.removal', |
|
646 |
'user': 'agent', |
|
647 |
}, |
|
648 |
] |
|
649 | ||
650 | ||
651 |
def test_role_journal(app, superuser, events): |
|
652 |
response = login(app, user=superuser, path="/manage/") |
|
653 |
role1 = Role.objects.get(name="role1") |
|
654 |
role2 = Role.objects.get(name="role2") |
|
655 | ||
656 |
response = app.get("/manage/roles/%s/journal/" % role1.id) |
|
657 |
content = extract_journal(response) |
|
658 | ||
659 |
assert content == [ |
|
660 |
{ |
|
661 |
'message': 'creation', |
|
662 |
'timestamp': 'Jan. 2, 2020, 4 a.m.', |
|
663 |
'type': 'manager.role.creation', |
|
664 |
'user': 'agent', |
|
665 |
}, |
|
666 |
{ |
|
667 |
'message': 'edit (name)', |
|
668 |
'timestamp': 'Jan. 2, 2020, 5 a.m.', |
|
669 |
'type': 'manager.role.edit', |
|
670 |
'user': 'agent', |
|
671 |
}, |
|
672 |
{ |
|
673 |
'message': 'deletion', |
|
674 |
'timestamp': 'Jan. 2, 2020, 6 a.m.', |
|
675 |
'type': 'manager.role.deletion', |
|
676 |
'user': 'agent', |
|
677 |
}, |
|
678 |
{ |
|
679 |
'message': 'membership grant to user "user (111111)"', |
|
680 |
'timestamp': 'Jan. 2, 2020, 7 a.m.', |
|
681 |
'type': 'manager.role.membership.grant', |
|
682 |
'user': 'agent', |
|
683 |
}, |
|
684 |
{ |
|
685 |
'message': 'membership removal of user "user (111111)"', |
|
686 |
'timestamp': 'Jan. 2, 2020, 8 a.m.', |
|
687 |
'type': 'manager.role.membership.removal', |
|
688 |
'user': 'agent', |
|
689 |
}, |
|
690 |
{ |
|
691 |
'message': 'inheritance addition from parent role "role2"', |
|
692 |
'timestamp': 'Jan. 2, 2020, 9 a.m.', |
|
693 |
'type': 'manager.role.inheritance.addition', |
|
694 |
'user': 'agent', |
|
695 |
}, |
|
696 |
{ |
|
697 |
'message': 'inheritance removal from parent role "role2"', |
|
698 |
'timestamp': 'Jan. 2, 2020, 10 a.m.', |
|
699 |
'type': 'manager.role.inheritance.removal', |
|
700 |
'user': 'agent', |
|
701 |
}, |
|
702 |
{ |
|
703 |
'message': 'addition of role "role2" as administrator', |
|
704 |
'timestamp': 'Jan. 2, 2020, 11 a.m.', |
|
705 |
'type': 'manager.role.administrator.role.addition', |
|
706 |
'user': 'agent', |
|
707 |
}, |
|
708 |
{ |
|
709 |
'message': 'removal of role "role2" as administrator', |
|
710 |
'timestamp': 'Jan. 2, 2020, noon', |
|
711 |
'type': 'manager.role.administrator.role.removal', |
|
712 |
'user': 'agent', |
|
713 |
}, |
|
714 |
{ |
|
715 |
'message': 'addition of user "user (111111)" as administrator', |
|
716 |
'timestamp': 'Jan. 2, 2020, 1 p.m.', |
|
717 |
'type': 'manager.role.administrator.user.addition', |
|
718 |
'user': 'agent', |
|
719 |
}, |
|
720 |
{ |
|
721 |
'message': 'removal of user "user (111111)" as administrator', |
|
722 |
'timestamp': 'Jan. 2, 2020, 2 p.m.', |
|
723 |
'type': 'manager.role.administrator.user.removal', |
|
724 |
'user': 'agent', |
|
725 |
}, |
|
726 |
] |
|
727 | ||
728 |
response = app.get("/manage/roles/%s/journal/" % role2.id) |
|
729 |
content = extract_journal(response) |
|
730 | ||
731 |
assert content == [ |
|
732 |
{ |
|
733 |
'message': 'inheritance addition to child role "role1"', |
|
734 |
'timestamp': 'Jan. 2, 2020, 9 a.m.', |
|
735 |
'type': 'manager.role.inheritance.addition', |
|
736 |
'user': 'agent', |
|
737 |
}, |
|
738 |
{ |
|
739 |
'message': 'inheritance removal to child role "role1"', |
|
740 |
'timestamp': 'Jan. 2, 2020, 10 a.m.', |
|
741 |
'type': 'manager.role.inheritance.removal', |
|
742 |
'user': 'agent', |
|
743 |
}, |
|
744 |
{ |
|
745 |
'message': 'addition as administrator of role "role1"', |
|
746 |
'timestamp': 'Jan. 2, 2020, 11 a.m.', |
|
747 |
'type': 'manager.role.administrator.role.addition', |
|
748 |
'user': 'agent', |
|
749 |
}, |
|
750 |
{ |
|
751 |
'message': 'removal as administrator of role "role1"', |
|
752 |
'timestamp': 'Jan. 2, 2020, noon', |
|
753 |
'type': 'manager.role.administrator.role.removal', |
|
754 |
'user': 'agent', |
|
755 |
}, |
|
756 |
] |
|
757 | ||
758 | ||
759 |
def test_roles_journal(app, superuser, events): |
|
760 |
response = login(app, user=superuser, path='/manage/') |
|
761 |
response = response.click('Role') |
|
762 |
response = response.click('Journal') |
|
763 | ||
764 |
content = extract_journal(response) |
|
765 | ||
766 |
assert content == [ |
|
767 |
{ |
|
768 |
'message': 'creation of role "role1"', |
|
769 |
'timestamp': 'Jan. 2, 2020, 4 a.m.', |
|
770 |
'type': 'manager.role.creation', |
|
771 |
'user': 'agent', |
|
772 |
}, |
|
773 |
{ |
|
774 |
'message': 'edit of role "role1" (name)', |
|
775 |
'timestamp': 'Jan. 2, 2020, 5 a.m.', |
|
776 |
'type': 'manager.role.edit', |
|
777 |
'user': 'agent', |
|
778 |
}, |
|
779 |
{ |
|
780 |
'message': 'deletion of role "role1"', |
|
781 |
'timestamp': 'Jan. 2, 2020, 6 a.m.', |
|
782 |
'type': 'manager.role.deletion', |
|
783 |
'user': 'agent', |
|
784 |
}, |
|
785 |
{ |
|
786 |
'message': 'membership grant to user "user (111111)" in role "role1"', |
|
787 |
'timestamp': 'Jan. 2, 2020, 7 a.m.', |
|
788 |
'type': 'manager.role.membership.grant', |
|
789 |
'user': 'agent', |
|
790 |
}, |
|
791 |
{ |
|
792 |
'message': 'membership removal of user "user (111111)" from role "role1"', |
|
793 |
'timestamp': 'Jan. 2, 2020, 8 a.m.', |
|
794 |
'type': 'manager.role.membership.removal', |
|
795 |
'user': 'agent', |
|
796 |
}, |
|
797 |
{ |
|
798 |
'message': 'inheritance addition from parent role "role2" to child role ' '"role1"', |
|
799 |
'timestamp': 'Jan. 2, 2020, 9 a.m.', |
|
800 |
'type': 'manager.role.inheritance.addition', |
|
801 |
'user': 'agent', |
|
802 |
}, |
|
803 |
{ |
|
804 |
'message': 'inheritance removal from parent role "role2" to child role ' '"role1"', |
|
805 |
'timestamp': 'Jan. 2, 2020, 10 a.m.', |
|
806 |
'type': 'manager.role.inheritance.removal', |
|
807 |
'user': 'agent', |
|
808 |
}, |
|
809 |
{ |
|
810 |
'message': 'addition of role "role2" as administrator of role "role1"', |
|
811 |
'timestamp': 'Jan. 2, 2020, 11 a.m.', |
|
812 |
'type': 'manager.role.administrator.role.addition', |
|
813 |
'user': 'agent', |
|
814 |
}, |
|
815 |
{ |
|
816 |
'message': 'removal of role "role2" as administrator of role "role1"', |
|
817 |
'timestamp': 'Jan. 2, 2020, noon', |
|
818 |
'type': 'manager.role.administrator.role.removal', |
|
819 |
'user': 'agent', |
|
820 |
}, |
|
821 |
{ |
|
822 |
'message': 'addition of user "user (111111)" as administrator of role ' '"role1"', |
|
823 |
'timestamp': 'Jan. 2, 2020, 1 p.m.', |
|
824 |
'type': 'manager.role.administrator.user.addition', |
|
825 |
'user': 'agent', |
|
826 |
}, |
|
827 |
{ |
|
828 |
'message': 'removal of user "user (111111)" as administrator of role "role1"', |
|
829 |
'timestamp': 'Jan. 2, 2020, 2 p.m.', |
|
830 |
'type': 'manager.role.administrator.user.removal', |
|
831 |
'user': 'agent', |
|
832 |
}, |
|
833 |
] |
|
834 | ||
835 | ||
836 |
def test_date_navigation(app, superuser, events): |
|
837 |
response = login(app, user=superuser, path="/manage/journal/") |
|
838 |
response = response.click('2020') |
|
839 |
response = response.click('January') |
|
840 |
response = response.click('1') |
|
841 |
response = response.click('January 2020') |
|
842 |
response = response.click('2020') |
|
843 |
response = response.click('All dates') |
|
844 | ||
845 | ||
846 |
def test_search(app, superuser, events): |
|
847 |
response = login(app, user=superuser, path="/manage/journal/") |
|
848 |
response.form.set('search', 'event:registration') |
|
849 |
response = response.form.submit() |
|
850 |
assert len(response.pyquery('tbody tr')) == 1 |
|
851 | ||
852 |
response.form.set('search', 'username:agent event:login') |
|
853 |
response = response.form.submit() |
|
854 |
assert len(response.pyquery('tbody tr')) == 1 |
|
855 |
assert all( |
|
856 |
'agent' == text_content(node) for node in response.pyquery('tbody tr td.journal-list--user-column') |
|
857 |
) |
|
858 | ||
859 |
response.form.set('search', 'uuid:%s event:reset' % events['user'].uuid) |
|
860 |
response = response.form.submit() |
|
861 |
assert len(response.pyquery('tbody tr')) == 1 |
|
862 | ||
863 |
response.form.set('search', 'session:1234') |
|
864 |
response = response.form.submit() |
|
865 |
assert len(response.pyquery('tbody tr')) == 9 |
|
866 |
assert all( |
|
867 |
text_content(node) == 'Johnny doe' |
|
868 |
for node in response.pyquery('tbody tr td.journal-list--user-column') |
|
869 |
) |
|
870 | ||
871 |
response.form.set('search', 'email:jane@example.com') |
|
872 |
response = response.form.submit() |
|
873 |
assert ( |
|
874 |
text_content(response.pyquery('tbody tr td.journal-list--message-column')[0]).strip() |
|
875 |
== 'email change of user "Johnny doe" for email address "jane@example.com"' |
|
876 |
) |
|
877 | ||
878 |
response.form.set('search', 'jane@example.com') |
|
879 |
response = response.form.submit() |
|
880 |
assert ( |
|
881 |
text_content(response.pyquery('tbody tr td.journal-list--message-column')[0]).strip() |
|
882 |
== 'email change of user "Johnny doe" for email address "jane@example.com"' |
|
883 |
) |
|
884 | ||
885 |
response.form.set('search', 'johny doe event:login') |
|
886 |
response = response.form.submit() |
|
887 |
pq = response.pyquery |
|
888 | ||
889 |
assert [ |
|
890 |
list(map(text_content, p)) |
|
891 |
for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column')) |
|
892 |
] == [['Johnny doe', 'login using password']] |
|
0 |
- |