0001-keep-authentication-context-fixes-21908.patch
src/authentic2_auth_fc/app_settings.py | ||
---|---|---|
108 | 108 |
def scopes(self): |
109 | 109 |
return self._setting('SCOPES', []) |
110 | 110 | |
111 |
@property |
|
112 |
def popup(self): |
|
113 |
return self._setting('POPUP', False) |
|
114 | ||
111 | 115 | |
112 | 116 |
import sys |
113 | 117 |
src/authentic2_auth_fc/auth_frontends.py | ||
---|---|---|
2 | 2 |
from django.template.loader import render_to_string |
3 | 3 |
from django.shortcuts import render |
4 | 4 | |
5 |
from authentic2 import app_settings as a2_app_settings |
|
5 |
from authentic2 import app_settings as a2_app_settings, utils as a2_utils
|
|
6 | 6 | |
7 | 7 |
from . import app_settings |
8 | 8 | |
... | ... | |
17 | 17 |
def id(self): |
18 | 18 |
return 'fc' |
19 | 19 | |
20 |
@property |
|
21 |
def popup(self): |
|
22 |
return app_settings.popup |
|
23 | ||
20 | 24 |
def login(self, request, *args, **kwargs): |
21 | 25 |
if 'nofc' in request.GET: |
22 | 26 |
return |
27 |
fc_user_info = request.session.get('fc_user_info') |
|
23 | 28 |
context = kwargs.pop('context', {}).copy() |
24 |
context['about_url'] = app_settings.about_url |
|
25 |
if 'fc_user_info' in request.session: |
|
26 |
context['fc_user_info'] = request.session['fc_user_info'] |
|
27 |
return render(request, 'authentic2_auth_fc/login.html', context) |
|
29 |
params = {} |
|
30 |
if self.popup: |
|
31 |
params['popup'] = '' |
|
32 |
context.update({ |
|
33 |
'popup': self.popup, |
|
34 |
'about_url': app_settings.about_url, |
|
35 |
'fc_user_info': fc_user_info, |
|
36 |
}) |
|
37 |
if fc_user_info: |
|
38 |
context.update({ |
|
39 |
'registration_url': a2_utils.make_url('fc-registration', |
|
40 |
keep_params=True, |
|
41 |
params=params, |
|
42 |
request=request), |
|
43 |
'fc_user_info': fc_user_info, |
|
44 |
}) |
|
45 |
template = 'authentic2_auth_fc/login_registration.html' |
|
46 |
else: |
|
47 |
context['login_url'] = a2_utils.make_url('fc-login-or-link', |
|
48 |
keep_params=True, |
|
49 |
params=params, |
|
50 |
request=request) |
|
51 |
template = 'authentic2_auth_fc/login.html' |
|
52 |
return render(request, template, context) |
|
28 | 53 | |
29 | 54 |
def profile(self, request, *args, **kwargs): |
30 | 55 |
# We prevent unlinking if the user has no usable password and can't change it |
... | ... | |
32 | 57 |
# and unlinking would make the account unreachable. |
33 | 58 |
unlink = request.user.has_usable_password() or a2_app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD |
34 | 59 | |
60 |
account_path = a2_utils.reverse('account_management') |
|
61 |
params = { |
|
62 |
'next': account_path, |
|
63 |
} |
|
64 |
if self.popup: |
|
65 |
params['popup'] = '' |
|
66 |
link_url = a2_utils.make_url('fc-login-or-link', |
|
67 |
params=params) |
|
68 | ||
35 | 69 |
context = kwargs.pop('context', {}).copy() |
36 | 70 |
context.update({ |
37 |
'popup': True,
|
|
71 |
'popup': self.popup,
|
|
38 | 72 |
'unlink': unlink, |
39 |
'about_url': app_settings.about_url |
|
73 |
'about_url': app_settings.about_url, |
|
74 |
'link_url': link_url, |
|
40 | 75 |
}) |
41 | 76 |
return render_to_string('authentic2_auth_fc/linking.html', context, request=request) |
42 | 77 | |
... | ... | |
45 | 80 |
return [] |
46 | 81 | |
47 | 82 |
context = kwargs.get('context', {}).copy() |
83 |
params = { |
|
84 |
'registration': '', |
|
85 |
} |
|
86 |
if self.popup: |
|
87 |
params['popup'] = '' |
|
48 | 88 |
context.update({ |
89 |
'login_url': a2_utils.make_url('fc-login-or-link', |
|
90 |
keep_params=True, params=params, |
|
91 |
request=request), |
|
92 |
'popup': self.popup, |
|
49 | 93 |
'about_url': app_settings.about_url, |
50 |
'registration': True, |
|
51 | 94 |
}) |
52 |
return render(request, 'authentic2_auth_fc/login.html', context) |
|
95 |
return render(request, 'authentic2_auth_fc/registration.html', context) |
src/authentic2_auth_fc/static/authentic2_auth_fc/js/fc.js | ||
---|---|---|
1 |
/* Open FranceConnect in popup */ |
|
2 | ||
3 | ||
4 |
(function(undef) { |
|
5 |
function PopupCenter(url, title, w, h) { |
|
6 |
// Fixes dual-screen position Most browsers Firefox |
|
7 |
var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX; |
|
8 |
var dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY; |
|
9 | ||
10 |
var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; |
|
11 |
var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; |
|
12 | ||
13 |
var left = ((width / 2) - (w / 2)) + dualScreenLeft; |
|
14 |
var top = ((height / 2) - (h / 2)) + dualScreenTop; |
|
15 |
var newWindow = window.open(url, title, 'location=0,status=0,menubar=0,toolbar=0,scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); |
|
16 | ||
17 |
// Puts focus on the newWindow |
|
18 |
if (window.focus) { |
|
19 |
newWindow.focus(); |
|
20 |
} |
|
21 |
} |
|
22 |
var tags = document.getElementsByClassName('js-fc-popup'); |
|
23 |
for (var i = 0; i < tags.length; i++) { |
|
24 |
var tag = tags[i]; |
|
25 |
tag.onclick = function (ev) { |
|
26 |
PopupCenter(this.href, 'Authentification FranceConnect', 700, 500); |
|
27 |
return false; |
|
28 |
}; |
|
29 |
} |
|
30 |
})(); |
src/authentic2_auth_fc/templates/authentic2_auth_fc/connecting.html | ||
---|---|---|
1 |
{% load staticfiles %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% if 'nofc' not in request.GET %} |
|
5 |
<link rel="stylesheet" type="text/css" href="{% static 'authentic2_auth_fc/css/fc.css' %}"> |
|
6 |
<div id="fc-button-wrapper"> |
|
7 |
<div id="fc-button"> |
|
8 |
{% if not fc_user_info %} |
|
9 |
<a href="{% url 'fc-login-or-link' %}{% if request.GET.next or popup or registration %}?{% endif %}{% if next %}{{ next }}{% else %}{% if request.GET.next %}{{ request.GET.urlencode }}{% endif %}{% endif %}{% if popup %}&popup=1{% endif %}{% if registration %}®istration{% endif %}" title="{% trans 'Log in with FranceConnect' %}" class="button connexion{% if popup %} js-oauth-popup{% endif %}"><div><img src="{% if registration %}{% static "authentic2_auth_fc/img/FC-register-button.svg" %}{% else %}{% static "authentic2_auth_fc/img/FC-connect-button.svg" %}{% endif %}"></img></div></a> |
|
10 |
{% else %} |
|
11 |
<a class="button" href="{% url 'fc-registration' %}{% if request.GET.next or popup %}?{% endif %}{% if request.GET.next %}{{ request.GET.urlencode }}{% endif %}{% if popup %}&popup=1{% endif %}" title="{% trans 'Create your account with FranceConnect' %}" class="connexion{% if popup %} js-oauth-popup{% endif %}"> |
|
12 |
<div>{% trans "Create your account with FranceConnect" %}<br/><br/><span class="certified">{{ fc_user_info.given_name }} {{ fc_user_info.family_name }}{% if fc_user_info.email %}<br/>{{ fc_user_info.email }}{% endif %}</span><br/><br/><img src="{% static 'authentic2_auth_fc/img/FC-register-button.svg' %}"></img></div></a> |
|
13 |
{% endif %} |
|
14 |
</div> |
|
15 |
{% block fc-explanation %} |
|
16 |
<p><a href="{{ about_url }}" target="_blank">{% trans "What is FranceConnect?" %}</a></p> |
|
17 |
<p>{% blocktrans %} |
|
18 |
FranceConnect is the solution proposed by the French state to streamline |
|
19 |
logging in online services. You can use to connect to your account. |
|
20 |
{% endblocktrans %}</p> |
|
21 |
{% endblock %} |
|
22 |
</div> |
|
23 |
{% endif %} |
src/authentic2_auth_fc/templates/authentic2_auth_fc/explanation.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 |
{% block fc-explanation %} |
|
3 |
<p> |
|
4 |
<a href="{{ about_url }}" target="_blank">{% trans "What is FranceConnect?" %}</a> |
|
5 |
</p> |
|
6 |
<p>{% blocktrans %} |
|
7 |
FranceConnect is the solution proposed by the French state to streamline |
|
8 |
logging in online services. You can use to connect to your account. |
|
9 |
{% endblocktrans %}</p> |
|
10 |
{% endblock %} |
src/authentic2_auth_fc/templates/authentic2_auth_fc/linking.html | ||
---|---|---|
11 | 11 |
{% trans "Linked FranceConnect accounts" %} |
12 | 12 |
</p> |
13 | 13 |
<ul class="fond"> |
14 |
<li class="picto utilisateur"><p class="lien">{{ user.fc_accounts.all.0 }}{% if unlink %} <a href="{% url 'fc-unlink' %}">{% trans 'Delete link'%}</a>{% endif %}</p></li>
|
|
14 |
<li class="picto utilisateur"><p class="lien">{{ user.fc_accounts.all.0 }}{% if unlink %} <a href="{% url 'fc-unlink' %}">{% trans 'Delete link'%}</a>{% endif %}</p></li>
|
|
15 | 15 |
</ul> |
16 | 16 |
{% else %} |
17 | 17 |
<p> |
18 | 18 |
<div id="fc-button-wrapper"> |
19 | 19 |
<div id="fc-button"> |
20 |
<a href="{% url 'fc-login-or-link' %}?next={% url 'account_management' %}" title="{% trans 'Link with a FranceConnect account' %}" class="button connexion"><div>{% trans "Link with a FranceConnect account" %}<img src="{% static 'authentic2_auth_fc/img/FCboutons-10.svg' %}"></img></div></a>
|
|
20 |
<a href="{{ link_url }}" title="{% trans 'Link with a FranceConnect account' %}" class="button connexion{% if popup %} js-fc-popup{% endif %}"><div>{% trans "Link with a FranceConnect account" %}<img src="{% static 'authentic2_auth_fc/img/FCboutons-10.svg' %}"></img></div></a>
|
|
21 | 21 |
</div> |
22 | 22 |
</div> |
23 | 23 |
</p> |
... | ... | |
26 | 26 |
</div> |
27 | 27 |
<p><a href="{{ about_url }}" target="_blank">{% trans "What is FranceConnect?" %}</a></p> |
28 | 28 |
</div> |
29 |
{% if popup %}<script src="{% static 'authentic2_auth_fc/js/fc.js' %}" type="text/javascript"></script>{% endif %} |
src/authentic2_auth_fc/templates/authentic2_auth_fc/login.html | ||
---|---|---|
1 |
{% include "authentic2_auth_fc/connecting.html" %} |
|
1 |
{% load staticfiles %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
<link rel="stylesheet" type="text/css" href="{% static 'authentic2_auth_fc/css/fc.css' %}"> |
|
5 |
<div id="fc-button-wrapper"> |
|
6 |
<div id="fc-button"> |
|
7 |
<a href="{{ login_url }}" |
|
8 |
title="{% trans 'Log in with FranceConnect' %}" |
|
9 |
class="button connexion{% if popup %} js-fc-popup{% endif %}"> |
|
10 |
<div> |
|
11 |
<img src="{% static "authentic2_auth_fc/img/FC-connect-button.svg" %}"></img> |
|
12 |
</div> |
|
13 |
</a> |
|
14 |
</div> |
|
15 |
{% include "authentic2_auth_fc/explanation.html" %} |
|
16 |
{% if popup %}<script src="{% static 'authentic2_auth_fc/js/fc.js' %}" type="text/javascript"></script>{% endif %} |
src/authentic2_auth_fc/templates/authentic2_auth_fc/login_registration.html | ||
---|---|---|
1 |
{% load staticfiles %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
<link rel="stylesheet" type="text/css" href="{% static 'authentic2_auth_fc/css/fc.css' %}"> |
|
5 |
<div id="fc-button-wrapper"> |
|
6 |
<div id="fc-button"> |
|
7 |
<a href="{{ registration_url }}" |
|
8 |
title="{% trans 'Create your account with FranceConnect' %}" |
|
9 |
class="button connexion{% if popup %} js-fc-popup{% endif %}"> |
|
10 |
<div> |
|
11 |
{% trans "Create your account with FranceConnect" %} |
|
12 |
<br/> |
|
13 |
<br/> |
|
14 |
<span class="certified"> |
|
15 |
{{ fc_user_info.given_name }} {{ fc_user_info.family_name }} |
|
16 |
{% if fc_user_info.email %} |
|
17 |
<br/> |
|
18 |
{{ fc_user_info.email }} |
|
19 |
{% endif %} |
|
20 |
</span> |
|
21 |
<br/> |
|
22 |
<br/> |
|
23 |
<img src="{% static 'authentic2_auth_fc/img/FC-register-button.svg' %}"></img> |
|
24 |
</div> |
|
25 |
</a> |
|
26 |
</div> |
|
27 |
{% include "authentic2_auth_fc/explanation.html" %} |
|
28 |
{% if popup %}<script src="{% static 'authentic2_auth_fc/js/fc.js' %}" type="text/javascript"></script>{% endif %} |
src/authentic2_auth_fc/templates/authentic2_auth_fc/registration.html | ||
---|---|---|
1 |
{% load staticfiles %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
<link rel="stylesheet" type="text/css" href="{% static 'authentic2_auth_fc/css/fc.css' %}"> |
|
5 |
<div id="fc-button-wrapper"> |
|
6 |
<div id="fc-button"> |
|
7 |
<a href="{{ login_url }}" |
|
8 |
title="{% trans 'Register with FranceConnect' %}" |
|
9 |
class="button connexion{% if popup %} js-fc-popup{% endif %}"> |
|
10 |
<div> |
|
11 |
<img src="{% static "authentic2_auth_fc/img/FC-register-button.svg" %}"></img> |
|
12 |
</div> |
|
13 |
</a> |
|
14 |
</div> |
|
15 |
{% include "authentic2_auth_fc/explanation.html" %} |
|
16 |
{% if popup %}<script src="{% static 'authentic2_auth_fc/js/fc.js' %}" type="text/javascript"></script>{% endif %} |
src/authentic2_auth_fc/views.py | ||
---|---|---|
143 | 143 |
def get_in_popup(self): |
144 | 144 |
return self.in_popup |
145 | 145 | |
146 |
def redirect_to(self, request, *args, **kwargs):
|
|
146 |
def redirect_to(self, request): |
|
147 | 147 |
if request.method == 'POST': |
148 | 148 |
redirect_to = request.POST.get(self.redirect_field_name, |
149 | 149 |
request.GET.get(self.redirect_field_name, '')) |
... | ... | |
162 | 162 |
{'redirect_to': next_url}) |
163 | 163 | |
164 | 164 |
def simple_redirect(self, request, next_url, *args, **kwargs): |
165 |
return HttpResponseRedirect(next_url)
|
|
165 |
return a2_utils.redirect(request, next_url, *args, **kwargs)
|
|
166 | 166 | |
167 | 167 |
def redirect(self, request, *args, **kwargs): |
168 | 168 |
next_url = kwargs.pop('next_url', None) |
... | ... | |
175 | 175 |
return self.simple_redirect(request, next_url, *args, **kwargs) |
176 | 176 | |
177 | 177 |
def redirect_and_come_back(self, request, next_url, *args, **kwargs): |
178 |
old_next_url = self.redirect_to(request, *args, **kwargs) |
|
179 |
here = '{0}?{1}'.format( |
|
180 |
request.path, urlencode({REDIRECT_FIELD_NAME: old_next_url})) |
|
181 |
there = '{0}{2}{1}'.format( |
|
182 |
next_url, urlencode({REDIRECT_FIELD_NAME: here}), |
|
183 |
'&' if '?' in next_url else '?') |
|
178 |
old_next_url = self.redirect_to(request) |
|
179 |
here = a2_utils.make_url(request.path, params={REDIRECT_FIELD_NAME: old_next_url}) |
|
180 |
here = a2_utils.make_url(here, **kwargs) |
|
181 |
there = a2_utils.make_url(next_url, params={REDIRECT_FIELD_NAME: here}) |
|
184 | 182 |
return self.redirect(request, next_url=there, *args, **kwargs) |
185 | 183 | |
186 | 184 |
def get_scopes(self): |
... | ... | |
421 | 419 |
self.logger.info('logged in using fc sub %s', self.sub) |
422 | 420 |
return self.redirect(request) |
423 | 421 |
else: |
422 |
params = {} |
|
423 |
if self.service_slug: |
|
424 |
params[constants.SERVICE_FIELD_NAME] = self.service_slug |
|
424 | 425 |
if registration: |
425 |
return self.redirect_and_come_back(request, reverse('fc-registration')) |
|
426 |
return self.redirect_and_come_back(request, |
|
427 |
a2_utils.make_url('fc-registration', |
|
428 |
params=params), |
|
429 |
params=params) |
|
426 | 430 |
else: |
427 | 431 |
messages.info(request, _('If you already have an account, please log in, else ' |
428 | 432 |
'create your account.')) |
429 |
if app_settings.show_button_quick_account_creation: |
|
430 |
return self.redirect_and_come_back(request, settings.LOGIN_URL) |
|
431 |
else: |
|
432 |
return self.redirect_and_come_back(request, |
|
433 |
'{0}?nofc=1'.format(settings.LOGIN_URL)) |
|
434 | 433 | |
434 |
login_params = params.copy() |
|
435 |
if not app_settings.show_button_quick_account_creation: |
|
436 |
login_params['nofc'] = 1 |
|
437 | ||
438 |
login_url = a2_utils.make_url(settings.LOGIN_URL, params=login_params) |
|
439 |
return self.redirect_and_come_back(request, login_url, params=params) |
|
435 | 440 | |
436 |
class RegistrationView(LoggerMixin, View): |
|
441 | ||
442 |
class RegistrationView(PopupViewMixin, LoggerMixin, View): |
|
437 | 443 |
def get(self, request, *args, **kwargs): |
438 | 444 |
data = utils.get_mapped_attributes_flat(request) |
439 | 445 |
data['no_password'] = True |
... | ... | |
447 | 453 | |
448 | 454 |
# Prevent errors when redirect_to does not contain fc-login-or-link view |
449 | 455 |
parsed_redirect_to = urlparse.urlparse(redirect_to) |
450 |
if parsed_redirect_to.path != reverse('fc-login-or-link'): |
|
451 |
redirect_to = '%s?%s=%s' % ( |
|
452 |
reverse('fc-login-or-link'), |
|
453 |
REDIRECT_FIELD_NAME, |
|
454 |
urllib.quote(redirect_to)) |
|
456 |
if parsed_redirect_to.path == reverse('fc-login-or-link'): |
|
457 |
redirect_to = urlparse.parse_qs(parsed_redirect_to.query) \ |
|
458 |
.get(REDIRECT_FIELD_NAME, [a2_utils.make_url('auth_homepage')])[0] |
|
459 |
params = { |
|
460 |
REDIRECT_FIELD_NAME: redirect_to, |
|
461 |
} |
|
462 |
if self.get_in_popup(): |
|
463 |
params['popup'] = '' |
|
464 |
redirect_to = a2_utils.make_url('fc-login-or-link', params=params) |
|
455 | 465 |
if not 'email' in data: |
456 | 466 |
data[REDIRECT_FIELD_NAME] = redirect_to |
457 | 467 |
messages.warning(request, |
... | ... | |
460 | 470 |
signing.dumps(data))) |
461 | 471 |
data['valid_email'] = False |
462 | 472 |
data['franceconnect'] = True |
473 |
data['authentication_method'] = 'france-connect' |
|
474 |
if constants.SERVICE_FIELD_NAME in request.GET: |
|
475 |
data[constants.SERVICE_FIELD_NAME] = request.GET[constants.SERVICE_FIELD_NAME] |
|
463 | 476 |
activation_url = a2_utils.build_activation_url(request, |
464 | 477 |
next_url=redirect_to, |
465 | 478 |
**data) |
tests/conftest.py | ||
---|---|---|
84 | 84 |
def user_cartman(db, ou_southpark): |
85 | 85 |
return create_user(username='ecartman', first_name='eric', last_name='cartman', |
86 | 86 |
email='ecartman@southpark.org', ou=ou_southpark, federation=CARTMAN_FC_INFO) |
87 | ||
88 | ||
89 |
class AllHook(object): |
|
90 |
def __init__(self): |
|
91 |
self.calls = {} |
|
92 |
from authentic2 import hooks |
|
93 |
hooks.get_hooks.cache.clear() |
|
94 | ||
95 |
def __call__(self, hook_name, *args, **kwargs): |
|
96 |
calls = self.calls.setdefault(hook_name, []) |
|
97 |
calls.append({'args': args, 'kwargs': kwargs}) |
|
98 | ||
99 |
def __getattr__(self, name): |
|
100 |
return self.calls.get(name, []) |
|
101 | ||
102 |
def clear(self): |
|
103 |
self.calls = {} |
|
104 | ||
105 | ||
106 |
@pytest.fixture |
|
107 |
def hooks(settings): |
|
108 |
if hasattr(settings, 'A2_HOOKS'): |
|
109 |
hooks = settings.A2_HOOKS |
|
110 |
else: |
|
111 |
hooks = settings.A2_HOOKS = {} |
|
112 |
hook = hooks['__all__'] = AllHook() |
|
113 |
yield hook |
|
114 |
hook.clear() |
|
115 |
del settings.A2_HOOKS['__all__'] |
tests/test_auth_fc.py | ||
---|---|---|
64 | 64 | |
65 | 65 |
@pytest.mark.parametrize('exp', [timestamp_from_datetime(now() + datetime.timedelta(seconds=1000)), |
66 | 66 |
timestamp_from_datetime(now() - datetime.timedelta(seconds=1000))]) |
67 |
def test_login(app, fc_settings, caplog, exp):
|
|
68 |
callback = reverse('fc-login-or-link')
|
|
69 |
response = app.get(callback, status=302)
|
|
67 |
def test_login_simple(app, fc_settings, caplog, hooks, exp):
|
|
68 |
response = app.get('/login/?service=portail&next=/idp/')
|
|
69 |
response = response.click(href='callback')
|
|
70 | 70 |
location = response['Location'] |
71 | 71 |
state = check_authorization_url(location) |
72 | 72 | |
... | ... | |
101 | 101 |
'given_name': u'Ÿuñe', |
102 | 102 |
}) |
103 | 103 | |
104 |
callback = reverse('fc-login-or-link') |
|
104 | 105 |
with httmock.HTTMock(access_token_response, user_info_response): |
105 |
response = app.get(callback + '?code=zzz&state=%s' % state, status=302) |
|
106 |
response = app.get(callback + '?service=portail&next=/idp/&code=zzz&state=%s' % state, status=302)
|
|
106 | 107 |
assert User.objects.count() == 0 |
107 | 108 |
fc_settings.A2_FC_CREATE = True |
108 | 109 |
with httmock.HTTMock(access_token_response, user_info_response): |
109 |
response = app.get(callback + '?code=zzz&state=%s' % state, status=302) |
|
110 |
response = app.get(callback + '?service=portail&next=/idp/&code=zzz&state=%s' % state, status=302)
|
|
110 | 111 |
if exp < timestamp_from_datetime(now()): |
111 | 112 |
assert User.objects.count() == 0 |
112 | 113 |
else: |
113 | 114 |
assert User.objects.count() == 1 |
114 | 115 |
if User.objects.count(): |
116 |
assert response['Location'] == 'http://testserver/idp/' |
|
117 |
assert hooks.event[1]['kwargs']['name'] == 'login' |
|
118 |
assert hooks.event[1]['kwargs']['service'] == 'portail' |
|
115 | 119 |
# we must be connected |
116 | 120 |
assert app.session['_auth_user_id'] |
117 | 121 |
assert models.FcAccount.objects.count() == 1 |
... | ... | |
273 | 277 |
models.FcAccount.objects.create(user=user, sub='xxx', token='aaa') |
274 | 278 |
response = app.get(url) |
275 | 279 |
assert 'new_password1' in response.form.fields |
280 | ||
281 | ||
282 |
def test_registration1(app, fc_settings, caplog, hooks): |
|
283 |
exp = timestamp_from_datetime(now() + datetime.timedelta(seconds=1000)) |
|
284 |
response = app.get('/login/?service=portail&next=/idp/') |
|
285 |
response = response.click(href="callback") |
|
286 |
# 1. Try a login |
|
287 |
# 2. Verify we come back to login page |
|
288 |
# 3. Check presence of registration link |
|
289 |
# 4. Follow it |
|
290 |
location = response['Location'] |
|
291 |
state = check_authorization_url(location) |
|
292 | ||
293 |
@httmock.urlmatch(path=r'.*/token$') |
|
294 |
def access_token_response(url, request): |
|
295 |
parsed = {x: y[0] for x, y in urlparse.parse_qs(request.body).items()} |
|
296 |
assert set(parsed.keys()) == set(['code', 'client_id', 'client_secret', 'redirect_uri', |
|
297 |
'grant_type']) |
|
298 |
assert parsed['code'] == 'zzz' |
|
299 |
assert parsed['client_id'] == 'xxx' |
|
300 |
assert parsed['client_secret'] == 'yyy' |
|
301 |
assert parsed['grant_type'] == 'authorization_code' |
|
302 |
assert callback in parsed['redirect_uri'] |
|
303 |
id_token = { |
|
304 |
'sub': '1234', |
|
305 |
'aud': 'xxx', |
|
306 |
'nonce': state, |
|
307 |
'exp': exp, |
|
308 |
'iss': 'https://fcp.integ01.dev-franceconnect.fr/', |
|
309 |
'email': 'john.doe@example.com', |
|
310 |
} |
|
311 |
return json.dumps({ |
|
312 |
'access_token': 'uuu', |
|
313 |
'id_token': hmac_jwt(id_token, 'yyy') |
|
314 |
}) |
|
315 | ||
316 |
@httmock.urlmatch(path=r'.*userinfo$') |
|
317 |
def user_info_response(url, request): |
|
318 |
assert request.headers['Authorization'] == 'Bearer uuu' |
|
319 |
return json.dumps({ |
|
320 |
'sub': '1234', |
|
321 |
'family_name': u'Frédérique', |
|
322 |
'given_name': u'Ÿuñe', |
|
323 |
'email': 'john.doe@example.com', |
|
324 |
}) |
|
325 | ||
326 |
callback = urlparse.parse_qs(urlparse.urlparse(location).query)['redirect_uri'][0] |
|
327 |
with httmock.HTTMock(access_token_response, user_info_response): |
|
328 |
response = app.get(callback + '&code=zzz&state=%s' % state, status=302) |
|
329 |
assert User.objects.count() == 0 |
|
330 |
assert response['Location'].startswith('http://testserver/login/') |
|
331 |
response = response.follow() |
|
332 |
response = response.click('Create your account with FranceConnect') |
|
333 |
location = response['Location'] |
|
334 |
location.startswith('http://testserver/accounts/activate/') |
|
335 |
response = response.follow() |
|
336 |
assert hooks.calls['event'][0]['kwargs']['service'] == 'portail' |
|
337 |
# we must be connected |
|
338 |
assert app.session['_auth_user_id'] |
|
339 |
assert response['Location'].startswith(callback) |
|
340 |
response = response.follow() |
|
341 |
location = response['Location'] |
|
342 |
state = check_authorization_url(location) |
|
343 |
with httmock.HTTMock(access_token_response, user_info_response): |
|
344 |
response = app.get(callback + '&code=zzz&state=%s' % state, status=302) |
|
345 |
assert models.FcAccount.objects.count() == 1 |
|
346 |
response = app.get('/accounts/') |
|
347 |
response = response.click('Delete link') |
|
348 |
response.form.set('new_password1', 'ikKL1234') |
|
349 |
response.form.set('new_password2', 'ikKL1234') |
|
350 |
response = response.form.submit(name='unlink') |
|
351 |
assert 'The link with the FranceConnect account has been deleted' in response.content |
|
352 |
assert models.FcAccount.objects.count() == 0 |
|
353 |
continue_url = response.pyquery('a#a2-continue').attr['href'] |
|
354 |
state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0] |
|
355 |
assert app.session['fc_states'][state]['next'] == '/accounts/' |
|
356 |
response = app.get(reverse('fc-logout') + '?state=' + state) |
|
357 |
assert response['Location'] == 'http://testserver/accounts/' |
|
358 | ||
359 | ||
360 |
def test_registration2(app, fc_settings, caplog, hooks): |
|
361 |
exp = timestamp_from_datetime(now() + datetime.timedelta(seconds=1000)) |
|
362 |
response = app.get('/login/?service=portail&next=/idp/') |
|
363 |
response = response.click("Register") |
|
364 |
response = response.click(href='callback') |
|
365 |
# 1. Try a login |
|
366 |
# 2. Verify we come back to login page |
|
367 |
# 3. Check presence of registration link |
|
368 |
# 4. Follow it |
|
369 |
location = response['Location'] |
|
370 |
state = check_authorization_url(location) |
|
371 | ||
372 |
@httmock.urlmatch(path=r'.*/token$') |
|
373 |
def access_token_response(url, request): |
|
374 |
parsed = {x: y[0] for x, y in urlparse.parse_qs(request.body).items()} |
|
375 |
assert set(parsed.keys()) == set(['code', 'client_id', 'client_secret', 'redirect_uri', |
|
376 |
'grant_type']) |
|
377 |
assert parsed['code'] == 'zzz' |
|
378 |
assert parsed['client_id'] == 'xxx' |
|
379 |
assert parsed['client_secret'] == 'yyy' |
|
380 |
assert parsed['grant_type'] == 'authorization_code' |
|
381 |
assert callback in parsed['redirect_uri'] |
|
382 |
id_token = { |
|
383 |
'sub': '1234', |
|
384 |
'aud': 'xxx', |
|
385 |
'nonce': state, |
|
386 |
'exp': exp, |
|
387 |
'iss': 'https://fcp.integ01.dev-franceconnect.fr/', |
|
388 |
'email': 'john.doe@example.com', |
|
389 |
} |
|
390 |
return json.dumps({ |
|
391 |
'access_token': 'uuu', |
|
392 |
'id_token': hmac_jwt(id_token, 'yyy') |
|
393 |
}) |
|
394 | ||
395 |
@httmock.urlmatch(path=r'.*userinfo$') |
|
396 |
def user_info_response(url, request): |
|
397 |
assert request.headers['Authorization'] == 'Bearer uuu' |
|
398 |
return json.dumps({ |
|
399 |
'sub': '1234', |
|
400 |
'family_name': u'Frédérique', |
|
401 |
'given_name': u'Ÿuñe', |
|
402 |
'email': 'john.doe@example.com', |
|
403 |
}) |
|
404 | ||
405 |
callback = urlparse.parse_qs(urlparse.urlparse(location).query)['redirect_uri'][0] |
|
406 |
with httmock.HTTMock(access_token_response, user_info_response): |
|
407 |
response = app.get(callback + '&code=zzz&state=%s' % state, status=302) |
|
408 |
assert User.objects.count() == 0 |
|
409 |
assert response['Location'].startswith('http://testserver/accounts/fc/register/') |
|
410 |
response = response.follow() |
|
411 |
location = response['Location'] |
|
412 |
location.startswith('http://testserver/accounts/activate/') |
|
413 |
response = response.follow() |
|
414 |
assert hooks.calls['event'][0]['kwargs']['service'] == 'portail' |
|
415 |
assert hooks.calls['event'][1]['kwargs']['service'] == 'portail' |
|
416 |
# we must be connected |
|
417 |
assert app.session['_auth_user_id'] |
|
418 |
# remove the registration parameter |
|
419 |
callback = callback.replace('®istration=', '') |
|
420 |
callback = callback.replace('?registration=', '?') |
|
421 |
callback = callback.replace('?&', '?') |
|
422 |
assert response['Location'].startswith(callback) |
|
423 |
response = response.follow() |
|
424 |
location = response['Location'] |
|
425 |
state = check_authorization_url(location) |
|
426 |
with httmock.HTTMock(access_token_response, user_info_response): |
|
427 |
response = app.get(callback + '&code=zzz&state=%s' % state, status=302) |
|
428 |
assert models.FcAccount.objects.count() == 1 |
|
429 |
response = app.get('/accounts/') |
|
430 |
response = response.click('Delete link') |
|
431 |
response.form.set('new_password1', 'ikKL1234') |
|
432 |
response.form.set('new_password2', 'ikKL1234') |
|
433 |
response = response.form.submit(name='unlink') |
|
434 |
assert 'The link with the FranceConnect account has been deleted' in response.content |
|
435 |
assert models.FcAccount.objects.count() == 0 |
|
436 |
continue_url = response.pyquery('a#a2-continue').attr['href'] |
|
437 |
state = urlparse.parse_qs(urlparse.urlparse(continue_url).query)['state'][0] |
|
438 |
assert app.session['fc_states'][state]['next'] == '/accounts/' |
|
439 |
response = app.get(reverse('fc-logout') + '?state=' + state) |
|
440 |
assert response['Location'] == 'http://testserver/accounts/' |
|
276 |
- |