0002-views-add-debug-login-view-55557.patch
mellon/templates/mellon/debug_login.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 | ||
3 |
{% block content %} |
|
4 |
<p><a class="button" href="{% url 'mellon_debug_login' %}">{% trans "Try again" %}</a></p> |
|
5 |
<p><strong>{% trans "Attributes:" %}</strong> <pre>{{ attributes|pprint }}</pre></p> |
|
6 |
<p><strong>{% trans "SAML assertion:" %}</strong> <pre>{{ assertion_dump }}</pre></p> |
|
7 |
<p><strong>{% trans "SAML response:" %}</strong> <pre>{{ response_dump }}</pre></p> |
|
8 |
<p><strong>{% trans "SAML artifact:" %}</strong> <pre>{{ login.msgBody }}</pre></p> |
|
9 |
<p><strong>{% trans "Logs:" %}</strong> <pre>{{ logs }}</pre></p> |
|
10 |
{% endblock %} |
mellon/urls.py | ||
---|---|---|
8 | 8 | |
9 | 9 |
urlpatterns = [ |
10 | 10 |
url('login/$', views.login, name='mellon_login'), |
11 |
url('login/debug/$', views.debug_login, name='mellon_debug_login'), |
|
11 | 12 |
url('logout/$', views.logout, name='mellon_logout'), |
12 | 13 |
url('metadata/$', views.metadata, name='mellon_metadata'), |
13 | 14 |
] |
mellon/views.py | ||
---|---|---|
15 | 15 | |
16 | 16 |
from __future__ import unicode_literals |
17 | 17 | |
18 |
from contextlib import contextmanager, nullcontext |
|
18 | 19 |
from importlib import import_module |
20 |
from io import StringIO |
|
19 | 21 |
import logging |
20 | 22 |
import requests |
21 | 23 |
import lasso |
... | ... | |
26 | 28 | |
27 | 29 |
import django.http |
28 | 30 |
from django.views.generic import View |
29 |
from django.http import HttpResponseRedirect, HttpResponse |
|
31 |
from django.views.generic.base import RedirectView |
|
32 |
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden |
|
30 | 33 |
from django.contrib import auth |
31 | 34 |
from django.contrib.auth import get_user_model |
32 | 35 |
from django.conf import settings |
... | ... | |
142 | 145 | |
143 | 146 | |
144 | 147 |
class LoginView(ProfileMixin, LogMixin, View): |
148 |
def dispatch(self, request, *args, **kwargs): |
|
149 |
self.debug_login = request.session.get('mellon_debug_login') |
|
150 |
with self.capture_logs() if self.debug_login else nullcontext(): |
|
151 |
return super().dispatch(request, *args, **kwargs) |
|
152 | ||
145 | 153 |
@property |
146 | 154 |
def template_base(self): |
147 | 155 |
return self.kwargs.get('template_base', 'base.html') |
... | ... | |
290 | 298 |
return self.render(request, 'mellon/user_not_found.html', {'saml_attributes': attributes}) |
291 | 299 |
request.session['lasso_session_dump'] = login.session.dump() |
292 | 300 | |
293 |
return HttpResponseRedirect(next_url) |
|
301 |
if self.debug_login: |
|
302 |
return self.render_debug_template(request, login, attributes) |
|
303 |
else: |
|
304 |
return HttpResponseRedirect(next_url) |
|
305 | ||
306 |
def render_debug_template(self, request, login, attributes): |
|
307 |
request.session['mellon_debug_login'] = False |
|
308 |
context = { |
|
309 |
'logs': self.stream.getvalue(), |
|
310 |
'attributes': attributes, |
|
311 |
'login': login, |
|
312 |
'response_dump': login.response and login.response.debug(4), |
|
313 |
'assertion_dump': login.assertion and login.assertion.debug(4), |
|
314 |
} |
|
315 |
return self.render(request, 'mellon/debug_login.html', context) |
|
294 | 316 | |
295 | 317 |
def login(self, user, attributes): |
318 |
if self.debug_login: |
|
319 |
self.log.info('mellon: would login user %s (username %s)', user.get_full_name(), user) |
|
320 |
return |
|
321 | ||
296 | 322 |
utils.login(self.request, user) |
297 | 323 |
session_index = attributes['session_index'] |
298 | 324 |
if session_index: |
... | ... | |
540 | 566 |
node.text = hint |
541 | 567 |
self.add_extension_node(authn_request, node) |
542 | 568 | |
569 |
@contextmanager |
|
570 |
def capture_logs(self): |
|
571 |
self.stream = StringIO() |
|
572 |
handler = logging.StreamHandler(self.stream) |
|
573 |
handler.setLevel(logging.DEBUG) |
|
574 |
self.log.root.addHandler(handler) |
|
575 |
try: |
|
576 |
yield |
|
577 |
finally: |
|
578 |
self.log.root.removeHandler(handler) |
|
579 | ||
543 | 580 | |
544 | 581 |
# we need fine control of transactions to prevent double user creations |
545 | 582 |
login = transaction.non_atomic_requests(csrf_exempt(LoginView.as_view())) |
... | ... | |
718 | 755 |
def metadata(request, **kwargs): |
719 | 756 |
metadata = utils.create_metadata(request) |
720 | 757 |
return HttpResponse(metadata, content_type='text/xml') |
758 | ||
759 | ||
760 |
class DebugLoginView(RedirectView): |
|
761 |
pattern_name = 'mellon_login' |
|
762 |
query_string = True |
|
763 | ||
764 |
def dispatch(self, request, *args, **kwargs): |
|
765 |
if not settings.DEBUG: |
|
766 |
return HttpResponseForbidden() |
|
767 |
request.session['mellon_debug_login'] = True |
|
768 |
return super().dispatch(request, *args, **kwargs) |
|
769 | ||
770 | ||
771 |
debug_login = csrf_exempt(DebugLoginView.as_view()) |
tests/test_sso_slo.py | ||
---|---|---|
16 | 16 |
from __future__ import unicode_literals |
17 | 17 | |
18 | 18 |
import datetime |
19 |
from html import unescape |
|
19 | 20 |
import re |
20 | 21 |
import base64 |
21 | 22 |
import zlib |
... | ... | |
713 | 714 |
reverse('mellon_login'), params={'SAMLResponse': other_body, 'RelayState': other_relay_state} |
714 | 715 |
) |
715 | 716 |
assert 'created new user' in caplog.text |
717 | ||
718 | ||
719 |
def test_debug_sso(db, app, idp, caplog, sp_settings, settings): |
|
720 |
response = app.get(reverse('mellon_debug_login') + '?next=/whatever/', status=403) |
|
721 | ||
722 |
settings.DEBUG = True |
|
723 |
response = app.get(reverse('mellon_debug_login') + '?next=/whatever/') |
|
724 |
assert urlparse.urlparse(response['Location']).path == '/login/' |
|
725 |
response = response.follow() |
|
726 |
url, body, relay_state = idp.process_authn_request_redirect(response['Location']) |
|
727 |
assert url.endswith(reverse('mellon_login')) |
|
728 |
response = app.post(reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state}) |
|
729 |
response_text = unescape(response.text) |
|
730 |
assert 'Attributes' in response.text |
|
731 |
assert "'email': ['john.doe@gmail.com']" in response_text |
|
732 |
assert '<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"' in response_text |
|
733 |
assert '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"' in response_text |
|
734 |
assert 'mellon: created new user _' in response_text |
|
716 |
- |