Projet

Général

Profil

0008-misc-clean-SessionIndex-during-logout-69740.patch

Benjamin Dauvergne, 04 octobre 2022 11:47

Télécharger (8,51 ko)

Voir les différences:

Subject: [PATCH 8/8] misc: clean SessionIndex during logout (#69740)

SessionIndex are deleted when the linked session does not exist anymore
and 5 minutes after the creation of the logout request.
 .../0008_add_timestamp_to_session_index.py    | 27 ++++++++++++++
 mellon/models.py                              | 35 +++++++++++++++----
 mellon/views.py                               | 10 ++++--
 tests/test_sso_slo.py                         |  8 ++++-
 4 files changed, 69 insertions(+), 11 deletions(-)
 create mode 100644 mellon/migrations/0008_add_timestamp_to_session_index.py
mellon/migrations/0008_add_timestamp_to_session_index.py
1
# Generated by Django 2.2.26 on 2022-10-04 09:10
2

  
3
import django.utils.timezone
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('mellon', '0007_sessionindex_transient_name_id'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='sessionindex',
16
            name='created',
17
            field=models.DateTimeField(
18
                auto_now_add=True, default=django.utils.timezone.now, verbose_name='created'
19
            ),
20
            preserve_default=False,
21
        ),
22
        migrations.AddField(
23
            model_name='sessionindex',
24
            name='logout_timestamp',
25
            field=models.DateTimeField(null=True, verbose_name='Timestamp of the last logout'),
26
        ),
27
    ]
mellon/models.py
14 14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 15

  
16 16

  
17
import datetime
17 18
from importlib import import_module
18 19

  
19 20
from django.conf import settings
20 21
from django.db import models
22
from django.utils.timezone import now
21 23
from django.utils.translation import gettext_lazy as _
22 24

  
23 25

  
......
50 52
    saml_identifier = models.ForeignKey(
51 53
        verbose_name=_('SAML identifier'), to=UserSAMLIdentifier, on_delete=models.CASCADE
52 54
    )
55
    created = models.DateTimeField(verbose_name=_('created'), auto_now_add=True)
56
    logout_timestamp = models.DateTimeField(verbose_name=_('Timestamp of the last logout'), null=True)
53 57

  
54
    @staticmethod
55
    def cleanup(cls):
58
    @classmethod
59
    def clean_session_indexes_after_logout(cls, delay_in_minutes=5, chunk_size=1000):
56 60
        session_engine = import_module(settings.SESSION_ENGINE)
57 61
        store = session_engine.SessionStore()
58 62

  
59
        ids = []
60
        for si in cls.objects.all():
61
            if not store.exists(si.session_key):
62
                ids.append(si.id)
63
        cls.objects.filter(id__in=ids).delete()
63
        try:
64
            Session = store.model
65
        except AttributeError:
66
            Session = None
67

  
68
        candidates = cls.objects.filter(
69
            models.Q(logout_timestamp__lt=now() - datetime.timedelta(minutes=delay_in_minutes))
70
            | models.Q(created__lt=now() - datetime.timedelta(days=1))
71
        )[:chunk_size]
72
        candidates_session_keys = candidates.values_list('session_key', flat=True)
73
        if Session is not None:
74
            # fast path
75
            existing_session_keys = Session.objects.filter(
76
                session_key__in=candidates_session_keys
77
            ).values_list('session_key', flat=True)
78
            dead_session_keys = candidates_session_keys.difference(existing_session_keys)
79
        else:
80
            dead_session_keys = []
81
            for session_key in candidates_session_keys:
82
                if not store.exists(session_key):
83
                    dead_session_keys.append(session_key)
84
        cls.objects.filter(session_key__in=dead_session_keys).delete()
64 85

  
65 86
    class Meta:
66 87
        verbose_name = _('SAML SessionIndex')
mellon/views.py
35 35
from django.urls import reverse
36 36
from django.utils.encoding import force_str
37 37
from django.utils.http import urlencode
38
from django.utils.timezone import now
38 39
from django.utils.translation import gettext as _
39 40
from django.views.decorators.csrf import csrf_exempt
40 41
from django.views.generic import View
......
753 754
                        try:
754 755
                            session_indexes = models.SessionIndex.objects.filter(
755 756
                                saml_identifier__user=request.user, saml_identifier__issuer__entity_id=issuer
756
                            ).order_by('-id')[:1]
757
                            ).order_by('-id')
757 758
                            if not session_indexes:
758 759
                                self.log.error('unable to find lasso session dump')
759 760
                            else:
760
                                session_dump = utils.make_session_dump(session_indexes)
761
                                session_dump = utils.make_session_dump(session_indexes[:1])
761 762
                                logout.setSessionFromDump(session_dump)
763
                            session_indexes.update(logout_timestamp=now())
762 764
                            logout.initRequest(issuer, lasso.HTTP_METHOD_REDIRECT)
763 765
                            logout.buildRequestMsg()
764 766
                        except lasso.Error as e:
......
801 803
        response = HttpResponseRedirect(next_url)
802 804
        if cookie_name in request.COOKIES:
803 805
            response.delete_cookie(cookie_name)
806
        models.SessionIndex.clean_session_indexes_after_logout()
804 807
        return response
805 808

  
806 809
    TOKEN_SALT = 'mellon-logout-token'
......
845 848
            return None
846 849
        session_indexes = models.SessionIndex.objects.filter(
847 850
            saml_identifier__user=request.user, saml_identifier__issuer__entity_id=issuer
848
        ).order_by('-id')[:1]
851
        ).order_by('-id')
849 852
        if not session_indexes:
850 853
            return None
851 854

  
......
854 857
            'session_index_pk': session_indexes[0].pk,
855 858
        }
856 859
        token = signing.dumps(token_content, salt=cls.TOKEN_SALT)
860
        session_indexes.update(logout_timestamp=now())
857 861
        return reverse('mellon_logout') + '?' + urlencode({'token': token})
858 862

  
859 863

  
tests/test_sso_slo.py
866 866
    assert response.location == '/'
867 867

  
868 868

  
869
def test_sso_slo_token(db, app, rf, idp, caplog, django_user_model):
869
def test_sso_slo_token(db, app, rf, idp, caplog, django_user_model, freezer):
870 870
    from mellon.views import LogoutView
871 871

  
872 872
    caplog.set_level(logging.WARNING)
......
874 874
    url, body, relay_state = idp.process_authn_request_redirect(response['Location'])
875 875
    response = app.post('/login/', params={'SAMLResponse': body, 'RelayState': relay_state})
876 876

  
877
    assert models.SessionIndex.objects.count() == 1
878
    assert models.SessionIndex.objects.filter(logout_timestamp__isnull=True).count() == 1
877 879
    request = rf.get('/whatever/')
878 880
    request.session = app.session
879 881
    request.user = django_user_model.objects.get()
880 882
    token_logout_url = LogoutView.make_logout_token_url(request, next_url='/somepath/')
883
    assert models.SessionIndex.objects.count() == 1
884
    assert models.SessionIndex.objects.filter(logout_timestamp__isnull=False).count() == 1
881 885
    assert token_logout_url
882 886
    app.session.flush()
883 887
    assert '_auth_user_id' not in app.session
......
885 889
    assert urlparse.urlparse(response['Location']).path == '/singleLogout'
886 890
    url = idp.process_logout_request_redirect(response.location)
887 891
    caplog.clear()
892
    freezer.move_to(datetime.timedelta(minutes=6))
888 893
    response = app.get(url)
889 894
    assert len(caplog.records) == 0, 'logout failed'
890 895
    assert response.location == '/somepath/'
896
    assert models.SessionIndex.objects.count() == 0
891
-