Projet

Général

Profil

0001-adapters-update-new-UserSAMLIdentifier-fields-on-eac.patch

Benjamin Dauvergne, 06 octobre 2022 15:30

Télécharger (7,13 ko)

Voir les différences:

Subject: [PATCH] adapters: update new UserSAMLIdentifier fields on each SSO
 (#69955)

On existing UserSAMLIdentifier missing values for nid_format especially,
will break the SLO code as the emitted LogoutRequest will have an
unknown NameID when analyzed by the identity provider (NameID content
and attributes must match exactly).
 mellon/adapters.py    | 27 +++++++++++++++++++++++----
 mellon/views.py       | 13 +++++++++----
 tests/test_sso_slo.py | 28 ++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 8 deletions(-)
mellon/adapters.py
331 331
            name_id = saml_attributes['name_id_content']
332 332
        entity_id = saml_attributes['issuer']
333 333
        try:
334
            to_update = {
335
                'nid_format': saml_attributes['name_id_format'],
336
                'nid_name_qualifier': saml_attributes.get('name_id_name_qualifier'),
337
                'nid_sp_name_qualifier': saml_attributes.get('name_id_sp_name_qualifier'),
338
                'nid_sp_provided_id': saml_attributes.get('name_id_sp_provided_id'),
339
            }
334 340
            saml_identifier = models.UserSAMLIdentifier.objects.select_related('user').get(
335 341
                name_id=name_id, issuer=models_utils.get_issuer(entity_id)
336 342
            )
343
            # nid_* attributes are new, we must update them if they are not initialized, eventually
344
            for key in to_update:
345
                if getattr(saml_identifier, key) != to_update[key]:
346
                    models.UserSAMLIdentifier.objects.filter(pk=saml_identifier.pk).update(**to_update)
347
                    break
337 348
            user = saml_identifier.user
338 349
            user.saml_identifier = saml_identifier
339 350
            logger.info('mellon: looked up user %s with name_id %s from issuer %s', user, name_id, entity_id)
......
463 474
        name_id_content = saml_attributes['name_id_content']
464 475
        if saml_attributes['name_id_format'] == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT:
465 476
            name_id_content = saml_attributes['transient_name_id_content']
477
        to_update = {
478
            'nid_format': saml_attributes['name_id_format'],
479
            'nid_name_qualifier': saml_attributes.get('name_id_name_qualifier'),
480
            'nid_sp_name_qualifier': saml_attributes.get('name_id_sp_name_qualifier'),
481
            'nid_sp_provided_id': saml_attributes.get('name_id_sp_provided_id'),
482
        }
466 483
        saml_id, created = models.UserSAMLIdentifier.objects.get_or_create(
467 484
            name_id=name_id_content,
468 485
            issuer=models_utils.get_issuer(saml_attributes['issuer']),
469 486
            defaults={
470 487
                'user': user,
471
                'nid_format': saml_attributes['name_id_format'],
472
                'nid_name_qualifier': saml_attributes.get('name_id_name_qualifier'),
473
                'nid_sp_name_qualifier': saml_attributes.get('name_id_sp_name_qualifier'),
474
                'nid_sp_provided_id': saml_attributes.get('name_id_sp_provided_id'),
488
                **to_update,
475 489
            },
476 490
        )
491
        # nid_* attributes are new, we must update them eventually
492
        for key in to_update:
493
            if getattr(saml_id, key) != to_update[key]:
494
                models.UserSAMLIdentifier.objects.filter(pk=saml_id.pk).update(**to_update)
495
                break
477 496
        if created:
478 497
            user.saml_identifier = saml_id
479 498
            return user
mellon/views.py
753 753
                        self.get_relay_state(create=True)
754 754
                        try:
755 755
                            session_indexes = models.SessionIndex.objects.filter(
756
                                saml_identifier__user=request.user, saml_identifier__issuer__entity_id=issuer
757
                            ).order_by('-id')
756
                                saml_identifier__user=request.user,
757
                                saml_identifier__issuer__entity_id=issuer,
758
                                session_key=request.session.session_key,
759
                            )
758 760
                            if not session_indexes:
759 761
                                self.log.error('unable to find lasso session dump')
760 762
                            else:
761
                                session_dump = utils.make_session_dump(session_indexes[:1])
763
                                session_dump = utils.make_session_dump(session_indexes)
762 764
                                logout.setSessionFromDump(session_dump)
763 765
                            session_indexes.update(logout_timestamp=now())
764 766
                            logout.initRequest(issuer, lasso.HTTP_METHOD_REDIRECT)
......
812 814
        token_content = signing.loads(token, salt=self.TOKEN_SALT)
813 815
        next_url = token_content['next_url'] or logout_next_url
814 816
        session_index_pk = token_content['session_index_pk']
815
        session_indexes = models.SessionIndex.objects.filter(pk=session_index_pk)
817
        session_index = models.SessionIndex.objects.filter(pk=session_index_pk).first()
818
        session_indexes = models.SessionIndex.objects.filter(
819
            saml_identifier=session_index.saml_identifier, session_key=session_index.session_key
820
        )
816 821
        if session_indexes:
817 822
            session_dump = utils.make_session_dump(session_indexes)
818 823
            logout = utils.create_logout(request)
tests/test_sso_slo.py
894 894
    assert len(caplog.records) == 0, 'logout failed'
895 895
    assert response.location == '/somepath/'
896 896
    assert models.SessionIndex.objects.count() == 0
897

  
898

  
899
def test_sso_slo_update_of_new_fields(db, app, idp, caplog, sp_settings):
900
    response = app.get('/login/')
901
    url, body, relay_state = idp.process_authn_request_redirect(response['Location'])
902
    response = app.post(
903
        reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state}
904
    ).follow()
905
    # violent logout
906
    app.session.flush()
907

  
908
    # remove existing fields
909
    models.UserSAMLIdentifier.objects.all().update(
910
        nid_format=None, nid_name_qualifier=None, nid_sp_name_qualifier=None, nid_sp_provided_id=None
911
    )
912

  
913
    response = app.get('/login/')
914
    url, body, relay_state = idp.process_authn_request_redirect(response['Location'])
915
    response = app.post(
916
        reverse('mellon_login'), params={'SAMLResponse': body, 'RelayState': relay_state}
917
    ).follow()
918

  
919
    # check logout works
920
    response = app.get('/logout/')
921
    url = idp.process_logout_request_redirect(response.location)
922
    caplog.clear()
923
    response = app.get(url)
924
    assert len(caplog.records) == 0, 'logout failed'
897
-