Projet

Général

Profil

0001-views-use-one-time-token-for-registration-41792.patch

Valentin Deniaud, 29 avril 2020 14:14

Télécharger (15,5 ko)

Voir les différences:

Subject: [PATCH 1/4] views: use one-time token for registration (#41792)

 src/authentic2/urls.py           |  2 +-
 src/authentic2/utils/__init__.py |  9 ++++--
 src/authentic2/views.py          | 36 ++++++++++-----------
 tests/test_all.py                | 35 ++------------------
 tests/test_attribute_kinds.py    | 55 +++++++++++++++++++++-----------
 tests/test_registration.py       | 53 ++++++++++++++++++++++++++++++
 6 files changed, 116 insertions(+), 74 deletions(-)
src/authentic2/urls.py
30 30
handler500 = 'authentic2.views.server_error'
31 31

  
32 32
accounts_urlpatterns = [
33
    url(r'^activate/(?P<registration_token>[\w: -]+)/$',
33
    url(r'^activate/(?P<registration_token>[A-Za-z0-9_ -]+)/$',
34 34
        views.registration_completion, name='registration_activate'),
35 35
    url(r'^register/$',
36 36
        views.RegistrationView.as_view(),
src/authentic2/utils/__init__.py
687 687

  
688 688

  
689 689
def build_activation_url(request, email, next_url=None, ou=None, **kwargs):
690
    from authentic2.models import Token
691

  
690 692
    data = kwargs.copy()
691 693
    data['email'] = email
692 694
    if ou:
693 695
        data['ou'] = ou.pk
694 696
    data[REDIRECT_FIELD_NAME] = next_url
695
    registration_token = signing.dumps(data)
697
    lifetime = settings.ACCOUNT_ACTIVATION_DAYS * 3600 * 24
698
    # invalidate any token associated with this address
699
    Token.objects.filter(kind='registration', content__email=email).delete()
700
    token = Token.create('registration', data, duration=lifetime)
696 701
    activate_url = request.build_absolute_uri(
697
        reverse('registration_activate', kwargs={'registration_token': registration_token}))
702
        reverse('registration_activate', kwargs={'registration_token': token.uuid_b64url}))
698 703
    return activate_url
699 704

  
700 705

  
src/authentic2/views.py
770 770
password_reset_confirm = PasswordResetConfirmView.as_view()
771 771

  
772 772

  
773
def valid_token(method):
774
    def f(request, *args, **kwargs):
775
        try:
776
            request.token = signing.loads(kwargs['registration_token'].replace(' ', ''),
777
                                          max_age=settings.ACCOUNT_ACTIVATION_DAYS * 3600 * 24)
778
        except signing.SignatureExpired:
779
            messages.warning(request, _('Your activation key is expired'))
780
            return utils.redirect(request, 'registration_register')
781
        except signing.BadSignature:
782
            messages.warning(request, _('Activation failed'))
783
            return utils.redirect(request, 'registration_register')
784
        return method(request, *args, **kwargs)
785
    return f
786

  
787

  
788 773
class BaseRegistrationView(FormView):
789 774
    form_class = registration_forms.RegistrationForm
790 775
    template_name = 'registration/registration_form.html'
......
889 874
        return url
890 875

  
891 876
    def dispatch(self, request, *args, **kwargs):
892
        self.token = request.token
877
        registration_token = kwargs['registration_token'].replace(' ', '')
878
        try:
879
            token = models.Token.use('registration', registration_token, delete=False)
880
        except models.Token.DoesNotExist:
881
            messages.warning(request, _('Your activation key is unknown or expired'))
882
            return utils.redirect(request, 'registration_register')
883
        except (TypeError, ValueError):
884
            messages.warning(request, _('Activation failed'))
885
            return utils.redirect(request, 'registration_register')
886
        self.token_obj = token
887
        self.token = token.content
888

  
893 889
        self.authentication_method = self.token.get('authentication_method', 'email')
894
        self.email = request.token['email']
890
        self.email = self.token['email']
895 891
        if 'ou' in self.token:
896 892
            self.ou = OU.objects.get(pk=self.token['ou'])
897 893
        else:
......
1087 1083
                ou=self.ou,
1088 1084
                next_url=self.get_success_url(),
1089 1085
                **data)
1086
            self.token_obj.delete()
1090 1087
            self.request.session['registered_email'] = form.cleaned_data['email']
1091 1088
            return utils.redirect(self.request, 'registration_complete')
1092 1089
        super(RegistrationCompletionView, self).form_valid(form)
......
1095 1092
    def registration_success(self, request, user, form):
1096 1093
        hooks.call_hooks('event', name='registration', user=user, form=form, view=self,
1097 1094
                         authentication_method=self.authentication_method,
1098
                         token=request.token, service=self.service)
1095
                         token=self.token, service=self.service)
1096
        self.token_obj.delete()
1099 1097
        utils.simulate_authentication(
1100 1098
            request, user,
1101 1099
            method=self.authentication_method,
......
1123 1121
                                  request=self.request)
1124 1122

  
1125 1123

  
1126
registration_completion = valid_token(RegistrationCompletionView.as_view())
1124
registration_completion = RegistrationCompletionView.as_view()
1127 1125

  
1128 1126

  
1129 1127
class DeleteView(TemplateView):
tests/test_all.py
604 604
        self.assertEqual(len(mail.outbox), outbox_level + 1)
605 605
        outbox_level = len(mail.outbox)
606 606

  
607
        # Second registration
608
        response2 = client.post(reverse('a2-api-register'),
609
                                content_type='application/json',
610
                                data=json.dumps(payload))
611
        self.assertEqual(response2.status_code, status.HTTP_202_ACCEPTED)
612
        self.assertIn('result', response2.data)
613
        self.assertEqual(response2.data['result'], 1)
614
        self.assertIn('token', response2.data)
615
        token2 = response2.data['token']
616
        self.assertEqual(len(mail.outbox), outbox_level + 1)
617

  
618
        activation_mail1, activation_mail2 = mail.outbox
619

  
620
        # User side - user click on first email
607
        # User side - user click on email
621 608
        client = Client()
622
        activation_url = get_link_from_mail(activation_mail1)
609
        activation_url = get_link_from_mail(mail.outbox[0])
623 610
        response = client.get(activation_url)
624 611
        self.assertEqual(response.status_code, status.HTTP_200_OK)
625 612
        assert utils.make_url(return_url, params={'token': token}) in force_text(response.content)
......
632 619
        self.assertEqual(last_user.ou.slug, self.ou.slug)
633 620
        self.assertTrue(last_user.check_password(password))
634 621

  
635
        # User click on second email
636
        client = Client()
637
        activation_url = get_link_from_mail(activation_mail2)
638
        response = client.get(activation_url)
639
        self.assertEqual(response.status_code, status.HTTP_302_FOUND)
640
        self.assertEqual(response['Location'],
641
                         utils.make_url(return_url, params={'token': token2}))
642
        self.assertEqual(User.objects.count(), user_count + 1)
643
        response = client.get(reverse('auth_homepage'))
644
        self.assertContains(response, username)
645
        last_user2 = User.objects.order_by('id').last()
646
        self.assertEqual(User.objects.filter(email=payload['email']).count(), 1)
647
        self.assertEqual(last_user.id, last_user2.id)
648
        self.assertEqual(last_user2.username, username)
649
        self.assertEqual(last_user2.email, email)
650
        self.assertEqual(last_user2.ou.slug, self.ou.slug)
651
        self.assertTrue(last_user2.check_password(password))
652

  
653 622
        # Test email is unique with case change
654 623
        client = test.APIClient()
655 624
        client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
tests/test_attribute_kinds.py
79 79

  
80 80

  
81 81
def test_fr_postcode(db, app, admin, mailoutbox):
82

  
83
    def register_john():
84
        response = app.get('/accounts/register/')
85
        form = response.form
86
        form.set('email', 'john.doe@example.com')
87
        response = form.submit().follow()
88
        assert 'john.doe@example.com' in response
89
        return get_link_from_mail(mailoutbox[-1])
90

  
82 91
    Attribute.objects.create(name='postcode', label='postcode', kind='fr_postcode',
83 92
                             asked_on_registration=True)
84 93
    qs = User.objects.filter(first_name='John')
85 94

  
86
    response = app.get('/accounts/register/')
87
    form = response.form
88
    form.set('email', 'john.doe@example.com')
89
    response = form.submit().follow()
90
    assert 'john.doe@example.com' in response
91
    url = get_link_from_mail(mailoutbox[0])
95
    url = register_john()
92 96
    response = app.get(url)
93 97

  
94 98
    form = response.form
......
115 119
    assert qs.get().attributes.postcode == '12345'
116 120
    qs.delete()
117 121

  
122
    url = register_john()
118 123
    response = app.get(url)
119 124
    form = response.form
120 125
    form.set('first_name', 'John')
......
126 131
    assert qs.get().attributes.postcode == '12345'
127 132
    qs.delete()
128 133

  
134
    url = register_john()
129 135
    response = app.get(url)
130 136
    form = response.form
131 137
    form.set('first_name', 'John')
......
182 188

  
183 189

  
184 190
def test_phone_number(db, app, admin, mailoutbox):
191

  
192
    def register_john():
193
        response = app.get('/accounts/register/')
194
        form = response.form
195
        form.set('email', 'john.doe@example.com')
196
        response = form.submit().follow()
197
        assert 'john.doe@example.com' in response
198
        return get_link_from_mail(mailoutbox[-1])
199

  
185 200
    Attribute.objects.create(name='phone_number', label='phone', kind='phone_number',
186 201
                             asked_on_registration=True)
187 202
    qs = User.objects.filter(first_name='John')
188 203

  
189
    response = app.get('/accounts/register/')
190
    form = response.form
191
    form.set('email', 'john.doe@example.com')
192
    response = form.submit().follow()
193
    assert 'john.doe@example.com' in response
194
    url = get_link_from_mail(mailoutbox[0])
195

  
204
    url = register_john()
196 205
    response = app.get(url)
197 206
    form = response.form
198 207
    form.set('first_name', 'John')
......
219 228
    assert qs.get().attributes.phone_number == '12345'
220 229
    qs.delete()
221 230

  
231
    url = register_john()
222 232
    response = app.get(url)
223 233
    form = response.form
224 234
    form.set('first_name', 'John')
......
230 240
    assert qs.get().attributes.phone_number == '+12345'
231 241
    qs.delete()
232 242

  
243
    url = register_john()
233 244
    response = app.get(url)
234 245
    form = response.form
235 246
    form.set('first_name', 'John')
......
241 252
    assert qs.get().attributes.phone_number == ''
242 253
    qs.delete()
243 254

  
255
    url = register_john()
244 256
    response = app.get(url)
245 257
    form = response.form
246 258
    form.set('first_name', 'John')
......
306 318

  
307 319

  
308 320
def test_birthdate(db, app, admin, mailoutbox, freezer):
321

  
322
    def register_john():
323
        response = app.get('/accounts/register/')
324
        form = response.form
325
        form.set('email', 'john.doe@example.com')
326
        response = form.submit().follow()
327
        assert 'john.doe@example.com' in response
328
        return get_link_from_mail(mailoutbox[-1])
329

  
309 330
    freezer.move_to('2018-01-01')
310 331
    Attribute.objects.create(name='birthdate', label='birthdate', kind='birthdate',
311 332
                             asked_on_registration=True)
312 333
    qs = User.objects.filter(first_name='John')
313 334

  
314
    response = app.get('/accounts/register/')
315
    form = response.form
316
    form.set('email', 'john.doe@example.com')
317
    response = form.submit().follow()
318
    assert 'john.doe@example.com' in response
319
    url = get_link_from_mail(mailoutbox[0])
335
    url = register_john()
320 336
    response = app.get(url)
321 337

  
322 338
    form = response.form
......
335 351
    assert qs.get().attributes.birthdate == datetime.date(2017, 12, 31)
336 352
    qs.delete()
337 353

  
354
    url = register_john()
338 355
    response = app.get(url)
339 356
    form = response.form
340 357
    form.set('first_name', 'John')
tests/test_registration.py
261 261
    app.session.flush()
262 262

  
263 263
    # try again
264
    response = app.get(reverse('registration_register'))
265
    response.form.set('email', 'testbot@entrouvert.com')
266
    response = response.form.submit()
267
    link = get_link_from_mail(mailoutbox[2])
268

  
264 269
    response = app.get(link)
265 270
    response = response.click('create')
266 271

  
......
666 671
    assert hooks.calls['event'][-2]['kwargs']['authentication_method'] == 'another'
667 672
    assert hooks.calls['event'][-1]['kwargs']['name'] == 'login'
668 673
    assert hooks.calls['event'][-1]['kwargs']['how'] == 'another'
674

  
675

  
676
def test_registration_link_unique_use(app, db, mailoutbox):
677
    models.Attribute.objects.update(disabled=True)
678

  
679
    response = app.get(reverse('registration_register'))
680
    response.form.set('email', 'testbot@entrouvert.com')
681
    response = response.form.submit()
682

  
683
    link = get_link_from_mail(mailoutbox[0])
684

  
685
    response = app.get(link)
686
    response.form.set('password1', 'T0==toto')
687

  
688
    # accessing multiple times work
689
    response = app.get(link)
690
    response.form.set('password1', 'T0==toto')
691
    response.form.set('password2', 'T0==toto')
692
    response = response.form.submit().follow()
693
    assert 'You have just created an account.' in response.text
694

  
695
    response = app.get(link)
696
    assert urlparse(response['Location']).path == reverse('registration_register')
697
    response = response.follow()
698
    assert 'activation key is unknown or expired' in response.text
699

  
700

  
701
def test_double_registration_impossible(app, db, mailoutbox):
702
    models.Attribute.objects.update(disabled=True)
703

  
704
    for _ in range(2):
705
        response = app.get(reverse('registration_register'))
706
        response.form.set('email', 'testbot@entrouvert.com')
707
        response = response.form.submit()
708
    assert len(mailoutbox) == 2
709

  
710
    link1, link2 = get_link_from_mail(mailoutbox[0]), get_link_from_mail(mailoutbox[1])
711

  
712
    response = app.get(link1)
713
    assert urlparse(response['Location']).path == reverse('registration_register')
714
    response = response.follow()
715
    assert 'activation key is unknown or expired' in response.text
716

  
717
    response = app.get(link2)
718
    response.form.set('password1', 'T0==toto')
719
    response.form.set('password2', 'T0==toto')
720
    response = response.form.submit().follow()
721
    assert 'You have just created an account.' in response.text
669
-