Projet

Général

Profil

0004-views-warn-user-before-generating-new-token-41792.patch

Valentin Deniaud, 29 avril 2020 14:14

Télécharger (7,31 ko)

Voir les différences:

Subject: [PATCH 4/4] views: warn user before generating new token (#41792)

 src/authentic2/app_settings.py   |  3 +++
 src/authentic2/utils/__init__.py |  4 ++--
 src/authentic2/views.py          | 39 +++++++++++++++++++++++++++++---
 tests/settings.py                |  2 ++
 tests/test_attribute_kinds.py    |  3 ++-
 tests/test_views.py              | 21 +++++++++++++++++
 6 files changed, 66 insertions(+), 6 deletions(-)
src/authentic2/app_settings.py
320 320
    A2_EMAILS_ADDRESS_RATELIMIT=Setting(
321 321
        default='3/d',
322 322
        definition='Maximum rate of emails sent to the same email address.'),
323
    A2_TOKEN_EXISTS_WARNING=Setting(
324
        default=True,
325
        definition='If an active token exists, warn user before generating a new one.')
323 326
)
324 327

  
325 328
app_settings = AppSettings(default_settings)
src/authentic2/utils/__init__.py
801 801
        user.save()
802 802
    lifetime = settings.PASSWORD_RESET_TIMEOUT_DAYS * 3600 * 24
803 803
    # invalidate any token associated with this user
804
    Token.objects.filter(kind='pw-reset', content__user=user.pk).delete()
805
    token = Token.create('pw-reset', {'user': user.pk}, duration=lifetime)
804
    Token.objects.filter(kind='pw-reset', content__user=user.pk, content__email=user.email).delete()
805
    token = Token.create('pw-reset', {'user': user.pk, 'email': user.email}, duration=lifetime)
806 806
    reset_url = make_url(
807 807
        'password_reset_confirm',
808 808
        kwargs={'token': token.uuid_b64url},
src/authentic2/views.py
33 33
from django.core import signing
34 34
from django.core.exceptions import ValidationError
35 35
from django.contrib import messages
36
from django.utils import six
36
from django.utils import six, timezone
37 37
from django.utils.translation import ugettext as _
38 38
from django.urls import reverse
39 39
from django.contrib.auth import logout as auth_logout
......
659 659
        return ctx
660 660

  
661 661
    def form_valid(self, form):
662
        email = form.cleaned_data['email']
663

  
664
        # if an email has already been sent, warn once before allowing resend
665
        token = models.Token.objects.filter(
666
            kind='pw-reset', content__email=email, expires__gt=timezone.now()
667
        ).exists()
668
        resend_key = 'pw-reset-allow-resend'
669
        if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key):
670
            self.request.session[resend_key] = True
671
            form.add_error(
672
                'email',
673
                _('An email has already been sent to %s. Click "Validate" again if '
674
                  'you really want it to be sent again.') % email
675
            )
676
            return self.form_invalid(form)
677
        self.request.session[resend_key] = False
678

  
662 679
        if is_ratelimited(self.request, key='post:email', group='pw-reset-email',
663 680
                          rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True):
664 681
            form.add_error(
......
677 694
            return self.form_invalid(form)
678 695

  
679 696
        form.save()
680
        self.request.session['reset_email'] = form.cleaned_data['email']
697
        self.request.session['reset_email'] = email
681 698
        return super(PasswordResetView, self).form_valid(form)
682 699

  
683 700
password_reset = PasswordResetView.as_view()
......
791 808
        return super(BaseRegistrationView, self).dispatch(request, *args, **kwargs)
792 809

  
793 810
    def form_valid(self, form):
811
        email = form.cleaned_data.pop('email')
812

  
813
        # if an email has already been sent, warn once before allowing resend
814
        token = models.Token.objects.filter(
815
            kind='registration', content__email=email, expires__gt=timezone.now()
816
        ).exists()
817
        resend_key = 'registration-allow-resend'
818
        if app_settings.A2_TOKEN_EXISTS_WARNING and token and not self.request.session.get(resend_key):
819
            self.request.session[resend_key] = True
820
            form.add_error(
821
                'email',
822
                _('An email has already been sent to %s. Click "Validate" again if '
823
                  'you really want it to be sent again.') % email
824
            )
825
            return self.form_invalid(form)
826
        self.request.session[resend_key] = False
827

  
794 828
        if is_ratelimited(self.request, key='post:email', group='registration-email',
795 829
                          rate=app_settings.A2_EMAILS_ADDRESS_RATELIMIT, increment=True):
796 830
            form.add_error(
......
808 842
            )
809 843
            return self.form_invalid(form)
810 844

  
811
        email = form.cleaned_data.pop('email')
812 845
        for field in form.cleaned_data:
813 846
            self.token[field] = form.cleaned_data[field]
814 847

  
tests/settings.py
48 48
TEMPLATES[0]['DIRS'].append('tests/templates')
49 49

  
50 50
SITE_BASE_URL = 'http://localhost'
51

  
52
A2_TOKEN_EXISTS_WARNING = False
tests/test_attribute_kinds.py
187 187
    qs.delete()
188 188

  
189 189

  
190
def test_phone_number(db, app, admin, mailoutbox):
190
def test_phone_number(db, app, admin, mailoutbox, settings):
191
    settings.A2_EMAILS_ADDRESS_RATELIMIT = None
191 192

  
192 193
    def register_john():
193 194
        response = app.get('/accounts/register/')
tests/test_views.py
203 203
    response.form.set('email', simple_user.email)
204 204
    response = response.form.submit()
205 205
    assert len(mailoutbox) == 12
206

  
207

  
208
@pytest.mark.parametrize('view_name', ['registration_register', 'password_reset'])
209
def test_views_email_token_resend(app, simple_user, settings, mailoutbox, view_name):
210
    settings.A2_TOKEN_EXISTS_WARNING = True
211

  
212
    response = app.get(reverse(view_name))
213
    response.form.set('email', simple_user.email)
214
    response = response.form.submit()
215
    assert len(mailoutbox) == 1
216

  
217
    # warn user token has already been sent
218
    response = app.get(reverse(view_name))
219
    response.form.set('email', simple_user.email)
220
    response = response.form.submit()
221
    assert 'email has already been sent' in response.text
222
    assert len(mailoutbox) == 1
223

  
224
    # validating again anyway works
225
    response = response.form.submit()
226
    assert len(mailoutbox) == 2
206
-