0003-misc-prevent-internal-URL-leak-in-browser-history-47.patch
src/authentic2/middleware.py | ||
---|---|---|
26 | 26 |
from django.utils.functional import SimpleLazyObject |
27 | 27 |
from django.utils.translation import ugettext as _ |
28 | 28 |
from django.utils.six.moves.urllib import parse as urlparse |
29 |
from django.shortcuts import render |
|
30 | 29 | |
31 | 30 |
from . import app_settings, utils, plugins |
32 | 31 |
from .utils.service import get_service_from_request, get_service_from_session |
... | ... | |
162 | 161 |
return response |
163 | 162 |
# Check if there is some messages to show |
164 | 163 |
storage = messages.get_messages(request) |
165 |
if not storage:
|
|
164 |
if not len(storage):
|
|
166 | 165 |
return response |
167 |
only_info = True |
|
168 |
some_message = False |
|
169 |
for message in storage: |
|
170 |
some_message = True |
|
171 |
if message.level != messages.INFO: |
|
172 |
# If there are warnin or error messages, the intermediate page must not redirect |
|
173 |
# automatically but should ask for an user confirmation |
|
174 |
only_info = False |
|
175 |
storage.used = False |
|
176 |
if not some_message: |
|
177 |
return response |
|
178 |
return render(request, 'authentic2/display_message_and_continue.html', |
|
179 |
{'url': url, 'only_info': only_info}) |
|
166 |
return utils.redirect(request, 'continue', resolve=True, params={'next': url}) |
|
180 | 167 | |
181 | 168 | |
182 | 169 |
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 | ||
---|---|---|
1363 | 1363 |
if message: |
1364 | 1364 |
messages.info(request, message) |
1365 | 1365 |
return utils.redirect(request, to=to) |
1366 | ||
1367 | ||
1368 |
class DisplayMessageAndContinueView(TemplateView): |
|
1369 |
template_name = 'authentic2/display_message_and_continue.html' |
|
1370 | ||
1371 |
def get(self, request, *args, **kwargs): |
|
1372 |
self.url = utils.select_next_url(self.request, reverse('account_management')) |
|
1373 |
self.only_info = True |
|
1374 | ||
1375 |
storage = messages.get_messages(request) |
|
1376 |
if not len(storage): |
|
1377 |
return utils.redirect(request, self.url, resolve=False) |
|
1378 | ||
1379 |
for message in storage: |
|
1380 |
if message.level != messages.INFO: |
|
1381 |
# If there are warning or error messages, the intermediate page must not redirect |
|
1382 |
# automatically but should ask for an user confirmation |
|
1383 |
self.only_info = False |
|
1384 |
storage.used = False |
|
1385 |
return super().get(request, *args, **kwargs) |
|
1386 | ||
1387 |
def get_context_data(self, **kwargs): |
|
1388 |
ctx = super().get_context_data(**kwargs) |
|
1389 |
ctx['url'] = self.url |
|
1390 |
ctx['only_info'] = self.only_info |
|
1391 |
return ctx |
|
1392 | ||
1393 |
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 | ||
---|---|---|
497 | 497 |
# User side |
498 | 498 |
client = Client() |
499 | 499 |
activation_url = get_link_from_mail(mail.outbox[-1]) |
500 |
response = client.get(activation_url) |
|
500 |
response = client.get(activation_url, follow=True)
|
|
501 | 501 |
self.assertEqual(response.status_code, status.HTTP_200_OK) |
502 | 502 |
assert utils.make_url(return_url, params={'token': token}) in force_text(response.content) |
503 | 503 |
self.assertEqual(User.objects.count(), user_count + 1) |
... | ... | |
611 | 611 |
# User side - user click on email |
612 | 612 |
client = Client() |
613 | 613 |
activation_url = get_link_from_mail(mail.outbox[0]) |
614 |
response = client.get(activation_url) |
|
614 |
response = client.get(activation_url, follow=True)
|
|
615 | 615 |
self.assertEqual(response.status_code, status.HTTP_200_OK) |
616 | 616 |
assert utils.make_url(return_url, params={'token': token}) in force_text(response.content) |
617 | 617 |
self.assertEqual(User.objects.count(), user_count + 1) |
... | ... | |
717 | 717 |
# User side - user click on first email |
718 | 718 |
client = Client() |
719 | 719 |
activation_url = get_link_from_mail(activation_mail1) |
720 |
response = client.get(activation_url) |
|
720 |
response = client.get(activation_url, follow=True)
|
|
721 | 721 |
self.assertEqual(response.status_code, status.HTTP_200_OK) |
722 | 722 |
assert utils.make_url(return_url, params={'token': token}) in force_text(response.content) |
723 | 723 |
self.assertEqual(User.objects.count(), user_count + 1) |
tests/test_registration.py | ||
---|---|---|
75 | 75 |
# set valid password |
76 | 76 |
response.form.set('password1', 'T0==toto') |
77 | 77 |
response.form.set('password2', 'T0==toto') |
78 |
response = response.form.submit() |
|
78 |
response = response.form.submit().maybe_follow()
|
|
79 | 79 |
if good_next_url: |
80 | 80 |
assert 'You have just created an account.' in response.text |
81 | 81 |
assert next_url in response.text |
82 |
assert response.request.path == '/continue/' |
|
82 | 83 |
else: |
83 |
assert urlparse(response['Location']).path == '/' |
|
84 |
response = response.follow() |
|
84 |
assert response.request.path == '/' |
|
85 | 85 |
assert 'You have just created an account.' in response.text |
86 | 86 |
assert User.objects.count() == 1 |
87 | 87 |
assert len(mailoutbox) == 2 |
... | ... | |
134 | 134 |
response.form.set('username', 'toto') |
135 | 135 |
response.form.set('password1', 'T0==toto') |
136 | 136 |
response.form.set('password2', 'T0==toto') |
137 |
response = response.form.submit() |
|
137 |
response = response.form.submit().follow()
|
|
138 | 138 |
assert 'You have just created an account.' in response.text |
139 | 139 |
assert next_url in response.text |
140 | 140 |
assert len(mailoutbox) == 2 |
... | ... | |
572 | 572 |
response = app.get(link) |
573 | 573 |
response.form.set('password1', 'T0==toto') |
574 | 574 |
response.form.set('password2', 'T0==toto') |
575 |
response = response.form.submit() |
|
575 |
response = response.form.submit().follow()
|
|
576 | 576 | |
577 | 577 |
assert 'You have just created an account.' in response.text |
578 | 578 |
assert new_next_url in response.text |
... | ... | |
618 | 618 |
response = app.get(link) |
619 | 619 |
response.form.set('password1', 'T0==toto') |
620 | 620 |
response.form.set('password2', 'T0==toto') |
621 |
response = response.form.submit() |
|
621 |
response = response.form.submit().follow()
|
|
622 | 622 |
assert new_next_url in response.text |
623 | 623 | |
624 | 624 | |
625 |
- |