Projet

Général

Profil

0002-views-ask-for-reauthentication-when-deleting-account.patch

Benjamin Dauvergne, 18 juin 2019 12:05

Télécharger (6,92 ko)

Voir les différences:

Subject: [PATCH 2/2] views: ask for reauthentication when deleting account
 (#28853)

 src/authentic2/forms/profile.py | 14 ----------
 src/authentic2/views.py         | 30 +++++++++++++++------
 tests/test_views.py             | 48 +++++++++++++++++++++++++++++----
 3 files changed, 65 insertions(+), 27 deletions(-)
src/authentic2/forms/profile.py
24 24
from .utils import NextUrlFormMixin
25 25

  
26 26

  
27
class DeleteAccountForm(forms.Form):
28
    password = forms.CharField(widget=forms.PasswordInput, label=_("Password"))
29

  
30
    def __init__(self, *args, **kwargs):
31
        self.user = kwargs.pop('user')
32
        super(DeleteAccountForm, self).__init__(*args, **kwargs)
33

  
34
    def clean_password(self):
35
        password = self.cleaned_data.get('password')
36
        if password and not self.user.check_password(password):
37
            raise forms.ValidationError(ugettext('Password is invalid'))
38
        return password
39

  
40

  
41 27
class EmailChangeFormNoPassword(forms.Form):
42 28
    email = forms.EmailField(label=_('New email'))
43 29

  
src/authentic2/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import time
17 18
import collections
18 19
import logging
19 20
import random
......
49 50
from django.views.generic.edit import CreateView
50 51
from django.forms import CharField, Form
51 52
from django.core.urlresolvers import reverse_lazy
53
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
52 54
from django.http import HttpResponseBadRequest
53 55

  
54 56
from . import (utils, app_settings, compat, decorators, constants,
......
1082 1084
    template_name = 'authentic2/accounts_delete.html'
1083 1085
    success_url = reverse_lazy('auth_logout')
1084 1086
    title = _('Delete account')
1087
    last_authentication_timeout = 60
1088
    token_timeout = 10 * 60
1085 1089

  
1086 1090
    def dispatch(self, request, *args, **kwargs):
1087 1091
        if not app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT:
1088 1092
            return utils.redirect(request, '..')
1093
        token = self.request.GET.get('token')
1094
        signer = TimestampSigner(salt='delete-account')
1095
        try:
1096
            token_valid = token and signer.unsign(token, max_age=self.token_timeout) == str(self.request.user.uuid)
1097
        except (BadSignature, SignatureExpired):
1098
            token_valid = False
1099
        if not token_valid:
1100
            if self.has_recent_authentication(request):
1101
                return utils.redirect(request, request.path, params={'token': signer.sign(str(self.request.user.uuid))})
1102
            else:
1103
                messages.info(request,
1104
                              _('Your last authentication is too old, '
1105
                                'you need to reauthenticate to delete your account'))
1106
                return utils.login_require(request)
1089 1107
        return super(DeleteView, self).dispatch(request, *args, **kwargs)
1090 1108

  
1109
    def has_recent_authentication(self, request):
1110
        delta = time.time() - utils.last_authentication_event(request=request)['when']
1111
        return delta < self.last_authentication_timeout
1112

  
1091 1113
    def post(self, request, *args, **kwargs):
1092 1114
        if 'cancel' in request.POST:
1093 1115
            return utils.redirect(request, 'account_management')
1094 1116
        return super(DeleteView, self).post(request, *args, **kwargs)
1095 1117

  
1096 1118
    def get_form_class(self):
1097
        if self.request.user.has_usable_password():
1098
            return profile_forms.DeleteAccountForm
1099 1119
        return Form
1100 1120

  
1101
    def get_form_kwargs(self, **kwargs):
1102
        kwargs = super(DeleteView, self).get_form_kwargs(**kwargs)
1103
        if self.request.user.has_usable_password():
1104
            kwargs['user'] = self.request.user
1105
        return kwargs
1106

  
1107 1121
    def form_valid(self, form):
1108 1122
        utils.send_account_deletion_mail(self.request, self.request.user)
1109 1123
        models.DeletedUser.objects.delete_user(self.request.user)
tests/test_views.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16
# authentic2
17 17

  
18
import time
19
import datetime
20

  
18 21
from utils import login
19 22
import pytest
20 23

  
......
40 43
def test_account_delete(app, simple_user, mailoutbox):
41 44
    assert simple_user.is_active
42 45
    assert not len(mailoutbox)
43
    page = login(app, simple_user, path=reverse('delete_account'))
44
    page.form.set('password', simple_user.username)
45
    # FIXME: webtest does not set the Referer header, so the logout page will always ask for
46
    # confirmation under tests
47
    response = page.form.submit(name='submit').follow()
46
    response = login(app, simple_user, path=reverse('delete_account')).follow()
47
    response = response.form.submit(name='submit').follow()
48
    response = response.form.submit()
49
    assert len(mailoutbox) == 1
50
    assert not User.objects.get(pk=simple_user.pk).is_active
51
    assert urlparse(response.location).path == '/'
52
    response = response.follow().follow()
53
    assert response.request.url.endswith('/login/?next=/')
54

  
55

  
56
def test_account_delete_expired_authentication(app, simple_user, mailoutbox, freezer):
57
    t = time.time()
58
    assert simple_user.is_active
59
    assert not len(mailoutbox)
60
    response = login(app, simple_user, path='/accounts/')
61

  
62
    # move 80 seconds in the future, so that last authentication is at least 60 seconds old
63
    freezer.move_to(datetime.timedelta(seconds=80))
64
    assert time.time() - t > 60
65

  
66
    # check we are redirected to login page
67
    response = response.click('Delete account')
68
    assert urlparse(response.location).path == '/login/'
69
    response = response.follow()
70
    assert 'you need to reauthenticate' in response.text
71

  
72
    response.form.set('username', simple_user.username)
73
    response.form.set('password', simple_user.username)
74
    response = response.form.submit(name='login-password-submit')
75

  
76
    # check we are redirected to the delete account page
77
    assert urlparse(response.location).path == '/accounts/delete/'
78
    response = response.follow()
79

  
80
    # check a token is added
81
    assert 'token' in response.location
82
    response = response.follow()
83

  
84
    # check delete view is now working
85
    response = response.form.submit(name='submit').follow()
48 86
    response = response.form.submit()
49 87
    assert len(mailoutbox) == 1
50 88
    assert not User.objects.get(pk=simple_user.pk).is_active
51
-