Projet

Général

Profil

0003-misc-prevent-internal-URL-leak-in-browser-history-47.patch

Benjamin Dauvergne, 13 octobre 2020 00:48

Télécharger (11,9 ko)

Voir les différences:

Subject: [PATCH 3/3] misc: prevent internal URL leak in browser history
 (#47302)

 src/authentic2/middleware.py  | 16 ++--------------
 src/authentic2/urls.py        |  1 +
 src/authentic2/views.py       | 28 ++++++++++++++++++++++++++++
 tests/auth_fc/test_auth_fc.py | 33 +++++++++++++++++----------------
 tests/settings.py             |  1 +
 tests/test_all.py             |  6 +++---
 tests/test_registration.py    | 12 ++++++------
 7 files changed, 58 insertions(+), 39 deletions(-)
src/authentic2/middleware.py
160 160
            return response
161 161
        # Check if there is some messages to show
162 162
        storage = messages.get_messages(request)
163
        if not storage:
163
        if not len(storage):
164 164
            return response
165
        only_info = True
166
        some_message = False
167
        for message in storage:
168
            some_message = True
169
            if message.level != messages.INFO:
170
                # If there are warnin or error messages, the intermediate page must not redirect
171
                # automatically but should ask for an user confirmation
172
                only_info = False
173
        storage.used = False
174
        if not some_message:
175
            return response
176
        return render(request, 'authentic2/display_message_and_continue.html',
177
                      {'url': url, 'only_info': only_info})
165
        return utils.redirect(request, 'continue', resolve=True, params={'next': url})
178 166

  
179 167

  
180 168
class ServiceAccessControlMiddleware(MiddlewareMixin):
src/authentic2/urls.py
114 114
    url(r'^idp/', include('authentic2.idp.urls')),
115 115
    url(r'^manage/', include('authentic2.manager.urls')),
116 116
    url(r'^api/', include('authentic2.api_urls')),
117
    url(r'^continue/$', views.display_message_and_continue, name='continue'),
117 118
]
118 119

  
119 120
try:
src/authentic2/views.py
1349 1349
    if message:
1350 1350
        messages.info(request, message)
1351 1351
    return utils.redirect(request, to=to)
1352

  
1353

  
1354
class DisplayMessageAndContinueView(TemplateView):
1355
    template_name = 'authentic2/display_message_and_continue.html'
1356

  
1357
    def get(self, request, *args, **kwargs):
1358
        self.url = utils.select_next_url(self.request, reverse('account_management'))
1359
        self.only_info = True
1360

  
1361
        storage = messages.get_messages(request)
1362
        if not len(storage):
1363
            return utils.redirect(request, self.url, resolve=False)
1364

  
1365
        for message in storage:
1366
            if message.level != messages.INFO:
1367
                # If there are warning or error messages, the intermediate page must not redirect
1368
                # automatically but should ask for an user confirmation
1369
                self.only_info = False
1370
        storage.used = False
1371
        return super().get(request, *args, **kwargs)
1372

  
1373
    def get_context_data(self, **kwargs):
1374
        ctx = super().get_context_data(**kwargs)
1375
        ctx['url'] = self.url
1376
        ctx['only_info'] = self.only_info
1377
        return ctx
1378

  
1379
display_message_and_continue = DisplayMessageAndContinueView.as_view()
tests/auth_fc/test_auth_fc.py
191 191
        response.form.set('new_password1', 'ikKL1234')
192 192
        response.form.set('new_password2', 'ikKL1234')
193 193
        response = response.form.submit(name='unlink')
194
        assert 'The link with the FranceConnect account has been deleted' in response.text
195 194
        assert models.FcAccount.objects.count() == 0
196
        continue_url = response.pyquery('a#a2-continue').attr['href']
195
        continue_url = response.location
197 196
        state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0]
198 197
        assert app.session['fc_states'][state]['next'] == '/accounts/'
199
        response = app.get(reverse('fc-logout') + '?state=' + state)
200
        assert path(response['Location']) == '/accounts/'
198
        response = app.get(reverse('fc-logout') + '?state=' + state).maybe_follow()
199
        assert response.request.path == '/accounts/'
200
        assert 'The link with the FranceConnect account has been deleted' in response.text
201 201

  
202 202

  
203 203
def test_login_email_is_unique(app, fc_settings, caplog):
......
435 435
    response.form.set('new_password1', 'ikKL1234')
436 436
    response.form.set('new_password2', 'ikKL1234')
437 437
    response = response.form.submit(name='unlink')
438
    assert 'The link with the FranceConnect account has been deleted' in response.text
439 438
    assert models.FcAccount.objects.count() == 0
440
    continue_url = response.pyquery('a#a2-continue').attr['href']
439
    continue_url = response.location
441 440
    state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0]
442 441
    assert app.session['fc_states'][state]['next'] == '/accounts/'
443
    response = app.get(reverse('fc-logout') + '?state=' + state)
444
    assert path(response['Location']) == '/accounts/'
442
    response = app.get(reverse('fc-logout') + '?state=' + state).maybe_follow()
443
    assert response.request.path == '/accounts/'
444
    assert 'The link with the FranceConnect account has been deleted' in response.text
445 445

  
446 446

  
447 447
def test_registration2(app, fc_settings, caplog, hooks):
......
529 529
    response.form.set('new_password1', 'ikKL1234')
530 530
    response.form.set('new_password2', 'ikKL1234')
531 531
    response = response.form.submit(name='unlink')
532
    assert 'The link with the FranceConnect account has been deleted' in response.text
533 532
    assert models.FcAccount.objects.count() == 0
534
    continue_url = response.pyquery('a#a2-continue').attr['href']
533
    continue_url = response.location
535 534
    state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0]
536 535
    assert app.session['fc_states'][state]['next'] == '/accounts/'
537
    response = app.get(reverse('fc-logout') + '?state=' + state)
538
    assert path(response['Location']) == '/accounts/'
536
    response = app.get(reverse('fc-logout') + '?state=' + state).maybe_follow()
537
    assert response.request.path == '/accounts/'
538
    assert 'The link with the FranceConnect account has been deleted' in response.text
539 539

  
540 540

  
541 541
def test_can_change_password(app, fc_settings, caplog, hooks):
......
653 653
    response.form.set('new_password1', 'ikKL1234')
654 654
    response.form.set('new_password2', 'ikKL1234')
655 655
    response = response.form.submit(name='unlink')
656
    continue_url = response.pyquery('a#a2-continue').attr['href']
656
    assert models.FcAccount.objects.count() == 0
657
    continue_url = response.location
657 658
    state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0]
658 659
    assert app.session['fc_states'][state]['next'] == '/accounts/'
659
    response = app.get(reverse('fc-logout') + '?state=' + state)
660
    assert path(response['Location']) == '/accounts/'
661
    response = response.follow()
660
    response = app.get(reverse('fc-logout') + '?state=' + state).maybe_follow()
661
    assert response.request.path == '/accounts/'
662
    assert 'The link with the FranceConnect account has been deleted' in response.text
662 663
    assert len(response.pyquery('[href*="password/change"]')) > 0
663 664

  
664 665

  
tests/settings.py
56 56
A2_MAX_EMAILS_FOR_ADDRESS = None
57 57

  
58 58
A2_TOKEN_EXISTS_WARNING = False
59
A2_REDIRECT_WHITELIST = ['http://sp.org/']
tests/test_all.py
494 494
        # User side
495 495
        client = Client()
496 496
        activation_url = get_link_from_mail(mail.outbox[-1])
497
        response = client.get(activation_url)
497
        response = client.get(activation_url, follow=True)
498 498
        self.assertEqual(response.status_code, status.HTTP_200_OK)
499 499
        assert utils.make_url(return_url, params={'token': token}) in force_text(response.content)
500 500
        self.assertEqual(User.objects.count(), user_count + 1)
......
608 608
        # User side - user click on email
609 609
        client = Client()
610 610
        activation_url = get_link_from_mail(mail.outbox[0])
611
        response = client.get(activation_url)
611
        response = client.get(activation_url, follow=True)
612 612
        self.assertEqual(response.status_code, status.HTTP_200_OK)
613 613
        assert utils.make_url(return_url, params={'token': token}) in force_text(response.content)
614 614
        self.assertEqual(User.objects.count(), user_count + 1)
......
714 714
        # User side - user click on first email
715 715
        client = Client()
716 716
        activation_url = get_link_from_mail(activation_mail1)
717
        response = client.get(activation_url)
717
        response = client.get(activation_url, follow=True)
718 718
        self.assertEqual(response.status_code, status.HTTP_200_OK)
719 719
        assert utils.make_url(return_url, params={'token': token}) in force_text(response.content)
720 720
        self.assertEqual(User.objects.count(), user_count + 1)
tests/test_registration.py
74 74
    # set valid password
75 75
    response.form.set('password1', 'T0==toto')
76 76
    response.form.set('password2', 'T0==toto')
77
    response = response.form.submit()
77
    response = response.form.submit().maybe_follow()
78 78
    if good_next_url:
79 79
        assert 'You have just created an account.' in response.text
80 80
        assert next_url in response.text
81
        assert response.request.path == '/continue/'
81 82
    else:
82
        assert urlparse(response['Location']).path == '/'
83
        response = response.follow()
83
        assert response.request.path == '/'
84 84
        assert 'You have just created an account.' in response.text
85 85
    assert User.objects.count() == 1
86 86
    assert len(mailoutbox) == 2
......
132 132
    response.form.set('username', 'toto')
133 133
    response.form.set('password1', 'T0==toto')
134 134
    response.form.set('password2', 'T0==toto')
135
    response = response.form.submit()
135
    response = response.form.submit().follow()
136 136
    assert 'You have just created an account.' in response.text
137 137
    assert next_url in response.text
138 138
    assert len(mailoutbox) == 2
......
570 570
    response = app.get(link)
571 571
    response.form.set('password1', 'T0==toto')
572 572
    response.form.set('password2', 'T0==toto')
573
    response = response.form.submit()
573
    response = response.form.submit().follow()
574 574

  
575 575
    assert 'You have just created an account.' in response.text
576 576
    assert new_next_url in response.text
......
616 616
    response = app.get(link)
617 617
    response.form.set('password1', 'T0==toto')
618 618
    response.form.set('password2', 'T0==toto')
619
    response = response.form.submit()
619
    response = response.form.submit().follow()
620 620
    assert new_next_url in response.text
621 621

  
622 622

  
623
-