0003-manager-add-a-change-email-action-on-users-fixes-197.patch
src/authentic2/a2_rbac/models.py | ||
---|---|---|
247 | 247 |
CHANGE_PASSWORD_OP = Operation(name=_('Change password'), slug='change_password') |
248 | 248 |
RESET_PASSWORD_OP = Operation(name=_('Reset password'), slug='reset_password') |
249 | 249 |
ACTIVATE_OP = Operation(name=_('Activate'), slug='activate') |
250 |
CHANGE_EMAIL_OP = Operation(name=_('Change email'), slug='change_email') |
src/authentic2/api_views.py | ||
---|---|---|
564 | 564 |
@detail_route(methods=['post'], |
565 | 565 |
permission_classes=(DjangoPermission('custom_user.change_user'),)) |
566 | 566 |
def email(self, request, uuid): |
567 |
from authentic2.views import EmailChangeView |
|
568 | 567 |
user = self.get_object() |
569 | 568 |
serializer = ChangeEmailSerializer(data=request.data) |
570 | 569 |
if not serializer.is_valid(): |
... | ... | |
573 | 572 |
'errors': serializer.errors |
574 | 573 |
} |
575 | 574 |
return Response(response, status.HTTP_400_BAD_REQUEST) |
576 |
EmailChangeView.send_email_change_email(request, user, serializer.validated_data['email'])
|
|
575 |
utils.send_email_change_email(user, serializer.validated_data['email'], request=request)
|
|
577 | 576 |
return Response({'result': 1}) |
578 | 577 | |
579 | 578 |
src/authentic2/manager/forms.py | ||
---|---|---|
622 | 622 | |
623 | 623 |
class Meta: |
624 | 624 |
model = get_ou_model() |
625 |
fields = ('name', 'default', 'username_is_unique', 'email_is_unique') |
|
625 |
fields = ('name', 'default', 'username_is_unique', 'email_is_unique', 'validate_emails')
|
|
626 | 626 | |
627 | 627 | |
628 | 628 |
def get_role_form_class(): |
629 | 629 |
if app_settings.ROLE_FORM_CLASS: |
630 | 630 |
return import_module_or_class(app_settings.ROLE_FORM_CLASS) |
631 | 631 |
return RoleEditForm |
632 | ||
633 | ||
634 |
class UserChangeEmailForm(CssClass, forms.ModelForm): |
|
635 |
def save(self, *args, **kwargs): |
|
636 |
return self.instance |
|
637 | ||
638 |
class Meta: |
|
639 |
fields = ('email',) |
src/authentic2/manager/templates/authentic2/manager/user_change_email.html | ||
---|---|---|
1 |
{% extends "authentic2/manager/form.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block beforeform %} |
|
5 |
<p> |
|
6 |
{% blocktrans %}User's email will not be changed immediately. First an email will be sent to this |
|
7 |
new email address containing a link on which the user's will have to click to verify that it owns |
|
8 |
the email address, then it will be changed.{% endblocktrans %} |
|
9 |
</p> |
|
10 |
{{ block.super }} |
|
11 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/user_change_email_notification_body.txt | ||
---|---|---|
1 |
{% load i18n %}{% autoescape off %}{% if email_is_not_unique%}{% blocktrans with name=user.get_short_name old_email=user.email %}Hi {{ name }} ! |
|
2 | ||
3 |
An administrator requested for changing your email on {{ domain }} from: |
|
4 | ||
5 |
{{ old_email }} |
|
6 | ||
7 |
to: |
|
8 | ||
9 |
{{ email }} |
|
10 | ||
11 |
But this email is already linked to another account. |
|
12 | ||
13 |
You can recover this account password using the password reset form: |
|
14 | ||
15 |
{{ password_reset_url }} |
|
16 | ||
17 |
-- |
|
18 |
{{ domain }}{% endblocktrans %}{% else %}{% blocktrans with name=user.get_short_name old_email=user.email %}Hi {{ name }} ! |
|
19 | ||
20 |
And administrator requested for changing your email on {{ domain }} from: |
|
21 | ||
22 |
{{ old_email }} |
|
23 | ||
24 |
to: |
|
25 | ||
26 |
{{ email }} |
|
27 | ||
28 |
To validate this change please click on the following link: |
|
29 | ||
30 |
{{ link }} |
|
31 | ||
32 |
This link will be valid for {{ token_lifetime }}. |
|
33 | ||
34 |
-- |
|
35 |
{{ domain }}{% endblocktrans %}{% endif %}{% endautoescape %} |
src/authentic2/manager/templates/authentic2/manager/user_change_email_notification_subject.txt | ||
---|---|---|
1 |
{% load i18n %}{% autoescape off %}{% blocktrans %}Change email on {{ domain }} requested by an administrator{% endblocktrans %}{% endautoescape %} |
src/authentic2/manager/urls.py | ||
---|---|---|
36 | 36 |
url(r'^users/(?P<pk>\d+)/change-password/$', |
37 | 37 |
user_views.user_change_password, |
38 | 38 |
name='a2-manager-user-change-password'), |
39 |
url(r'^users/(?P<pk>\d+)/change-email/$', |
|
40 |
user_views.user_change_email, |
|
41 |
name='a2-manager-user-change-email'), |
|
39 | 42 |
# by uuid |
40 | 43 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/$', user_views.user_detail, |
41 | 44 |
name='a2-manager-user-by-uuid-detail'), |
... | ... | |
47 | 50 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/change-password/$', |
48 | 51 |
user_views.user_change_password, |
49 | 52 |
name='a2-manager-user-by-uuid-change-password'), |
53 |
url(r'^users/uuid:(?P<slug>[a-z0-9]+)/change-email/$', |
|
54 |
user_views.user_change_email, |
|
55 |
name='a2-manager-user-by-uuid-change-email'), |
|
50 | 56 | |
51 | 57 |
# Authentic2 roles |
52 | 58 |
url(r'^roles/$', role_views.listing, |
src/authentic2/manager/user_views.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
from authentic2.constants import SWITCH_USER_SESSION_KEY |
18 | 18 |
from authentic2.models import Attribute, PasswordReset |
19 |
from authentic2.utils import switch_user, send_password_reset_mail, redirect |
|
19 |
from authentic2.utils import switch_user, send_password_reset_mail, redirect, send_email_change_email
|
|
20 | 20 |
from authentic2.a2_rbac.utils import get_default_ou |
21 | 21 |
from authentic2 import hooks |
22 | 22 |
from django_rbac.utils import get_role_model, get_role_parenting_model, get_ou_model |
... | ... | |
26 | 26 |
BaseEditView, ActionMixin, OtherActionsMixin, Action, ExportMixin, \ |
27 | 27 |
BaseSubTableView, HideOUColumnMixin, BaseDeleteView, BaseDetailView |
28 | 28 |
from .tables import UserTable, UserRolesTable, OuUserRolesTable |
29 |
from .forms import UserSearchForm, UserAddForm, UserEditForm, \
|
|
30 |
UserChangePasswordForm, ChooseUserRoleForm, UserRoleSearchForm |
|
29 |
from .forms import (UserSearchForm, UserAddForm, UserEditForm,
|
|
30 |
UserChangePasswordForm, ChooseUserRoleForm, UserRoleSearchForm, UserChangeEmailForm)
|
|
31 | 31 |
from .resources import UserResource |
32 | 32 |
from . import app_settings |
33 | 33 | |
... | ... | |
164 | 164 |
permission='custom_user.change_password_user') |
165 | 165 |
if self.request.user.is_superuser: |
166 | 166 |
yield Action('switch_user', _('Impersonate this user')) |
167 |
if self.object.ou and self.object.ou.validate_emails: |
|
168 |
yield Action('change_email', _('Change user email'), |
|
169 |
url_name='a2-manager-user-change-email', |
|
170 |
permission='custom_user.change_email_user') |
|
167 | 171 | |
168 | 172 |
def action_force_password_change(self, request, *args, **kwargs): |
169 | 173 |
PasswordReset.objects.get_or_create(user=self.object) |
... | ... | |
256 | 260 |
template_name = 'authentic2/manager/user_edit.html' |
257 | 261 |
form_class = UserEditForm |
258 | 262 |
permissions = ['custom_user.change_user'] |
259 |
fields = ['username', 'ou', 'first_name', 'last_name', 'email']
|
|
263 |
fields = ['username', 'ou', 'first_name', 'last_name'] |
|
260 | 264 |
success_url = '..' |
261 | 265 |
slug_field = 'uuid' |
262 | 266 |
action = _('Change') |
... | ... | |
264 | 268 | |
265 | 269 |
def get_fields(self): |
266 | 270 |
fields = list(self.fields) |
271 |
if not self.object.ou or not self.object.ou.validate_emails: |
|
272 |
fields.append('email') |
|
267 | 273 |
for attribute in Attribute.objects.all(): |
268 | 274 |
fields.append(attribute.name) |
269 | 275 |
if self.request.user.is_superuser and \ |
... | ... | |
325 | 331 |
user_change_password = UserChangePasswordView.as_view() |
326 | 332 | |
327 | 333 | |
334 |
class UserChangeEmailView(BaseEditView): |
|
335 |
template_name = 'authentic2/manager/user_change_email.html' |
|
336 |
model = get_user_model() |
|
337 |
form_class = UserChangeEmailForm |
|
338 |
permissions = ['custom_user.change_email_user'] |
|
339 |
success_url = '..' |
|
340 |
slug_field = 'uuid' |
|
341 |
title = _('Change user email') |
|
342 | ||
343 |
def get_success_message(self, cleaned_data): |
|
344 |
return ugettext('A mail was sent to %s to verify it.') % cleaned_data['email'] |
|
345 | ||
346 |
def get_form_kwargs(self): |
|
347 |
kwargs = super(UserChangeEmailView, self).get_form_kwargs() |
|
348 |
kwargs.setdefault('initial', {})['email'] = self.object.email |
|
349 |
return kwargs |
|
350 | ||
351 |
def form_valid(self, form): |
|
352 |
response = super(UserChangeEmailView, self).form_valid(form) |
|
353 |
email = form.cleaned_data['email'] |
|
354 |
hooks.call_hooks('event', name='manager-change-email-request', user=self.request.user, |
|
355 |
instance=form.instance, form=form, email=email) |
|
356 |
send_email_change_email(self.object, email, request=self.request, |
|
357 |
template_names=['authentic2/manager/user_change_email_notification']) |
|
358 |
return response |
|
359 | ||
360 |
user_change_email = UserChangeEmailView.as_view() |
|
361 | ||
362 | ||
328 | 363 |
class UserRolesView(HideOUColumnMixin, BaseSubTableView): |
329 | 364 |
model = get_user_model() |
330 | 365 |
form_class = ChooseUserRoleForm |
src/authentic2/settings.py | ||
---|---|---|
299 | 299 |
DJANGO_RBAC_PERMISSIONS_HIERARCHY = { |
300 | 300 |
'view': ['search'], |
301 | 301 |
'change_password': ['view', 'search'], |
302 |
'change_email': ['view', 'search'], |
|
302 | 303 |
'reset_password': ['view', 'search'], |
303 | 304 |
'activate': ['view', 'search'], |
304 | 305 |
'admin': ['change', 'delete', 'add', 'view', 'change_password', 'reset_password', 'activate', |
305 |
'search'], |
|
306 |
'search', 'change_email'],
|
|
306 | 307 |
'change': ['view', 'search'], |
307 | 308 |
'delete': ['view', 'search'], |
308 | 309 |
'add': ['view', 'search'], |
src/authentic2/utils.py | ||
---|---|---|
981 | 981 |
return app_settings.LOGIN_URL or settings.LOGIN_URL |
982 | 982 | |
983 | 983 | |
984 |
def send_email_change_mail(user, email, request=None, context=None, template_names=None): |
|
984 |
def send_email_change_email(user, email, request=None, context=None, template_names=None):
|
|
985 | 985 |
'''Send an email to verify that user can take email as its new email''' |
986 | 986 |
assert user |
987 | 987 |
assert email |
src/authentic2/views.py | ||
---|---|---|
162 | 162 | |
163 | 163 |
def form_valid(self, form): |
164 | 164 |
email = form.cleaned_data['email'] |
165 |
utils.send_email_change_mail(self.request.user, email, request=self.request) |
|
165 |
utils.send_email_change_email(self.request.user, email, request=self.request)
|
|
166 | 166 |
hooks.call_hooks('event', name='change-email', user=self.request.user, email=email) |
167 | 167 |
messages.info( |
168 | 168 |
self.request, |
tests/test_user_manager.py | ||
---|---|---|
1 |
from django.core.urlresolvers import reverse |
|
2 | ||
3 |
from authentic2.a2_rbac.utils import get_default_ou |
|
4 |
from utils import login, get_link_from_mail |
|
5 | ||
6 | ||
7 |
def test_manager_user_change_email(app, superuser_or_admin, simple_user, mailoutbox): |
|
8 |
ou = get_default_ou() |
|
9 |
ou.validate_emails = True |
|
10 |
ou.save() |
|
11 | ||
12 |
response = login(app, superuser_or_admin, |
|
13 |
reverse('a2-manager-user-by-uuid-detail', |
|
14 |
kwargs={'slug': unicode(simple_user.uuid)})) |
|
15 |
assert 'Change user email' in response.content |
|
16 |
# cannot click it's a submit button :/ |
|
17 |
response = app.get(reverse('a2-manager-user-by-uuid-change-email', |
|
18 |
kwargs={'slug': unicode(simple_user.uuid)})) |
|
19 |
response.form.set('email', 'john.doe@example.com') |
|
20 |
assert len(mailoutbox) == 0 |
|
21 |
response = response.form.submit().follow() |
|
22 |
assert 'A mail was sent to john.doe@example.com to verify it.' in response.content |
|
23 |
assert 'Change user email' in response.content |
|
24 |
# cannot click it's a submit button :/ |
|
25 |
assert len(mailoutbox) == 1 |
|
26 |
# logout |
|
27 |
app.session.flush() |
|
28 | ||
29 |
link = get_link_from_mail(mailoutbox[0]) |
|
30 |
response = app.get(link).maybe_follow() |
|
31 |
assert ( |
|
32 |
'your request for changing your email for john.doe@example.com is successful' |
|
33 |
in response.content) |
|
34 |
simple_user.refresh_from_db() |
|
35 |
assert simple_user.email == 'john.doe@example.com' |
|
0 |
- |