Projet

Général

Profil

0002-views-add-debug-login-view-55557.patch

Valentin Deniaud, 03 août 2021 12:05

Télécharger (6,9 ko)

Voir les différences:

Subject: [PATCH 2/2] views: add debug login view (#55557)

 mellon/templates/mellon/debug_login.html | 10 +++++
 mellon/urls.py                           |  1 +
 mellon/views.py                          | 55 +++++++++++++++++++++++-
 tests/test_sso_slo.py                    | 19 ++++++++
 4 files changed, 83 insertions(+), 2 deletions(-)
 create mode 100644 mellon/templates/mellon/debug_login.html
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
-