0001-keep-authentication-context-fixes-21908.patch
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 | |
9 | 9 | |
10 | 10 |
class FcFrontend(object): |
11 |
popup = False |
|
12 | ||
11 | 13 |
def enabled(self): |
12 | 14 |
return app_settings.enable |
13 | 15 | |
... | ... | |
20 | 22 |
def login(self, request, *args, **kwargs): |
21 | 23 |
if 'nofc' in request.GET: |
22 | 24 |
return |
25 |
fc_user_info = request.session.get('fc_user_info') |
|
23 | 26 |
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) |
|
27 |
params = {} |
|
28 |
if self.popup: |
|
29 |
params['popup'] = '' |
|
30 |
context.update({ |
|
31 |
'popup': self.popup, |
|
32 |
'about_url': app_settings.about_url, |
|
33 |
'fc_user_info': fc_user_info, |
|
34 |
}) |
|
35 |
if fc_user_info: |
|
36 |
context.update({ |
|
37 |
'registration_url': a2_utils.make_url('fc-registration', |
|
38 |
keep_params=True, |
|
39 |
request=request), |
|
40 |
'fc_user_info': fc_user_info, |
|
41 |
}) |
|
42 |
template = 'authentic2_auth_fc/login_registration.html' |
|
43 |
else: |
|
44 |
context['login_url'] = a2_utils.make_url('fc-login-or-link', |
|
45 |
keep_params=True, |
|
46 |
params=params, |
|
47 |
request=request) |
|
48 |
template = 'authentic2_auth_fc/login.html' |
|
49 |
return render(request, template, context) |
|
28 | 50 | |
29 | 51 |
def profile(self, request, *args, **kwargs): |
30 | 52 |
# We prevent unlinking if the user has no usable password and can't change it |
... | ... | |
34 | 56 | |
35 | 57 |
context = kwargs.pop('context', {}).copy() |
36 | 58 |
context.update({ |
37 |
'popup': True,
|
|
59 |
'popup': self.popup,
|
|
38 | 60 |
'unlink': unlink, |
39 | 61 |
'about_url': app_settings.about_url |
40 | 62 |
}) |
... | ... | |
46 | 68 | |
47 | 69 |
context = kwargs.get('context', {}).copy() |
48 | 70 |
context.update({ |
71 |
'login_url': a2_utils.make_url('fc-login-or-link', keep_params=True, params={'registration': ''}, request=request), |
|
72 |
'popup': self.popup, |
|
49 | 73 |
'about_url': app_settings.about_url, |
50 |
'registration': True, |
|
51 | 74 |
}) |
52 |
return render(request, 'authentic2_auth_fc/login.html', context) |
|
75 |
return render(request, 'authentic2_auth_fc/registration.html', context) |
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 %} |
|
11 |
</div> |
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-oauth-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" %} |
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"> |
|
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" %} |
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-oauth-popup{% endif %}"> |
|
10 |
<div> |
|
11 |
<img src="{% static "authentic2_auth_fc/img/FC-register-button.svg" %}"></img> |
|
12 |
</div> |
|
13 |
</a> |
|
14 |
</div> |
|
15 |
{% block fc-explanation %} |
|
16 |
<p> |
|
17 |
<a href="{{ about_url }}" target="_blank">{% trans "What is FranceConnect?" %}</a> |
|
18 |
</p> |
|
19 |
<p>{% blocktrans %} |
|
20 |
FranceConnect is the solution proposed by the French state to streamline |
|
21 |
logging in online services. You can use to connect to your account. |
|
22 |
{% endblocktrans %}</p> |
|
23 |
{% endblock %} |
|
24 |
</div> |
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)) |
|
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) |
|
434 | 440 | |
435 | 441 | |
436 | 442 |
class RegistrationView(LoggerMixin, View): |
... | ... | |
460 | 466 |
signing.dumps(data))) |
461 | 467 |
data['valid_email'] = False |
462 | 468 |
data['franceconnect'] = True |
469 |
data['authentication_method'] = 'france-connect' |
|
470 |
if constants.SERVICE_FIELD_NAME in request.GET: |
|
471 |
data[constants.SERVICE_FIELD_NAME] = request.GET[constants.SERVICE_FIELD_NAME] |
|
463 | 472 |
activation_url = a2_utils.build_activation_url(request, |
464 | 473 |
next_url=redirect_to, |
465 | 474 |
**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 |
- |