Projet

Général

Profil

0003-manager-add-a-change-email-action-on-users-fixes-197.patch

Benjamin Dauvergne, 07 décembre 2017 11:54

Télécharger (15,1 ko)

Voir les différences:

Subject: [PATCH 3/3] manager: add a change email action on users (fixes
 #19716)

It's only visible for OU with the validate_emails flag.
 src/authentic2/a2_rbac/models.py                   |  1 +
 src/authentic2/api_views.py                        |  3 +-
 src/authentic2/manager/forms.py                    | 10 ++++-
 .../authentic2/manager/user_change_email.html      | 11 ++++++
 .../user_change_email_notification_body.txt        | 35 ++++++++++++++++++
 .../user_change_email_notification_subject.txt     |  1 +
 src/authentic2/manager/urls.py                     |  6 +++
 src/authentic2/manager/user_views.py               | 43 ++++++++++++++++++++--
 src/authentic2/settings.py                         |  3 +-
 src/authentic2/utils.py                            |  2 +-
 src/authentic2/views.py                            |  2 +-
 tests/test_user_manager.py                         | 35 ++++++++++++++++++
 12 files changed, 142 insertions(+), 10 deletions(-)
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/user_change_email.html
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/user_change_email_notification_body.txt
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/user_change_email_notification_subject.txt
 create mode 100644 tests/test_user_manager.py
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
-