Bug #27697
gestion des doublons LDAP selon casse
0%
Description
Avec #27147 on ne prête plus attention à la casse dans la recherche d'un utilisateur LDAP.
Comme on y prêtait attention avant, on peut se trouver maintenant avec des doublons et des crashs
File "/usr/lib/python2.7/dist-packages/authentic2/backends/ldap_backend.py", line 849, in lookup_existing_user return self.lookup_by_external_id(block, attributes) File "/usr/lib/python2.7/dist-packages/authentic2/backends/ldap_backend.py", line 840, in lookup_by_external_id userexternalid__external_id__iexact=external_id, userexternalid__source=block['realm']) File "/usr/lib/python2.7/dist-packages/django/db/models/query.py", line 338, in get (self.model._meta.object_name, num) authentic2.backends.ldap_backend.MultipleObjectsReturned: get() returned more than one LDAPUser -- it returned 2!
Parfois ça concerne quelques utilisateurs mais parfois ça en concerne des centaines, quand il y a eu "nettoyage" de l'annuaire LDAP à un moment, pour retirer la casse partout, genre.
Fichiers
Révisions associées
Historique
Mis à jour par Frédéric Péters il y a plus de 5 ans
Je ne sais pas quelle approche on peut prendre ici, rendre le comportement optionnel ? faire un .filter(...).first() plutôt que le .get(...) ?
Mis à jour par Frédéric Péters il y a plus de 5 ans
Tester d'abord en prêtant attention à la casse, et seulement si ça ne donne rien tenter sans ?
Mis à jour par Frédéric Péters il y a plus de 5 ans
- Fichier 0001-ldap-lookup-users-with-exact-case-before-going-case-.patch 0001-ldap-lookup-users-with-exact-case-before-going-case-.patch ajouté
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
- Statut changé de Nouveau à Solution proposée
- Patch proposed changé de Non à Oui
Brouillons de patch de ces dernières idées.
Mis à jour par Thomas Noël il y a plus de 5 ans
Frédéric Péters a écrit :
Tester d'abord en prêtant attention à la casse, et seulement si ça ne donne rien tenter sans ?
J'aime bien cette idée là (donc 0001-ldap-lookup-users-with-exact-case-before-going-case-.patch a ma préférence)
Mis à jour par Thomas Noël il y a plus de 5 ans
Ceci dit l'erreur qu'on a c'est MultipleObjectsReturned : il faut éviter d'utiliser get, plutôt prendre l'élément le plus récent, selon la date de dernière connexion puis la date de création.
Mis à jour par Frédéric Péters il y a plus de 5 ans
- Statut changé de Solution proposée à En cours
- Priorité changé de Normal à Haut
- Patch proposed changé de Oui à Non
Je mets une priorité à ce ticket parce que je ne pense pas que la situation actuelle puisse passer en production, qu'on doit convenir d'une approche. (qui peut très bien être ne rien faire ici et préemptivement retirer les doublons de la prod).
Mis à jour par Christophe Siraut il y a plus de 5 ans
+1 pour faire une comparaison en 2 étapes (avec et sans casse).
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
- Statut changé de En cours à Solution proposée
- Patch proposed changé de Non à Oui
- on garde uniquement le plus récemment utilisé,
- on collecte le rôles de tous les users trouvés et on les affecte au premier,
- on supprime les autres.
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Assigné à
Benjamin Dauvergnesupprimé - Patch proposed changé de Oui à Non
if len(user) > 1:
if len(users) > 1:
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Assigné à mis à Benjamin Dauvergne
- Patch proposed changé de Non à Oui
Mis à jour par Thomas Noël il y a plus de 5 ans
Fonctionnellement je pense que ça passe (même si la magie me fait toujours un peu peur, mais c'est moi).
Passer le log.debug("found %d users ...") en log.info
Question sur le order_by(-last_login) : voir comment ça réagit quand il n'y a pas encore de last_login (parce que blank=True), typiquement pour les comptes qui viennent d'être synchronisés et sont donc ceux qu'il faut utiliser.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
https://www.postgresql.org/docs/8.3/static/queries-order.html :
The NULLS FIRST and NULLS LAST options can be used to determine whether nulls appear before or after non-null values in the sort ordering. By default, null values sort as if larger than any non-null value; that is, NULLS FIRST is the default for DESC order, and NULLS LAST otherwise.
Pas de nulls last dans Django, et je ne crois pas qu'on puisse trier par __isnull
avant, enfin j'ai rien vu qui le confirme sur le net, donc oui faudra trier à la paluche après.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
Bêtises corrigées, tri en prenant compte des NULLs.
Mis à jour par Serghei Mihai il y a plus de 5 ans
Il reste:
log.debug('found %d users, collectings roles into the first one and deleting the other ones.', len(users))
il faut un len(users)
.
Et:
E File "/home/serghei/dev/authentic/src/authentic2/backends/ldap_backend.py", line 844 E key=lambda u: u is not None, u.last_login) E SyntaxError: non-keyword arg after keyword arg
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
Corrections.
Mis à jour par Thomas Noël il y a plus de 5 ans
Je pense qu'il faut mettre les None en premier (utilisateur jamais connecté = compte tout nouveau, par ex. vient juste d'être synchronisé, et c'est celui ci qui est le bon pour l'avenir). Donc virer le reserved=True du sorted. IMveryveryHO seulement.
Mis à jour par Serghei Mihai il y a plus de 5 ans
Thomas Noël a écrit :
Je pense qu'il faut mettre les None en premier (utilisateur jamais connecté = compte tout nouveau, par ex. vient juste d'être synchronisé, et c'est celui ci qui est le bon pour l'avenir). Donc virer le reserved=True du sorted. IMveryveryHO seulement.
En local avec un a2 qui tape dans une base Postgres je crée un nouvel utilisateur sans me connecter avec.
Ça donne:
In [29]: for u in User.objects.all().order_by('-last_login'): ...: print u.username, u.last_login ...: NeverLogguedUser None neverloggueduser 2018-11-05 22:30:13.853561+00:00
J'ai bien l'impression qu'on aura celui qui ne s'est jamais connecté sans faire de tri manuel.
Mis à jour par Thomas Noël il y a plus de 5 ans
Serghei Mihai a écrit :
J'ai bien l'impression qu'on aura celui qui ne s'est jamais connecté sans faire de tri manuel.
Comme a dit Benjamin en note 14, on n'en sait rien, ça dépend du sgbd, la requête SQL générée par Django ne précise rien (pour pgsql pas de "NULLS FIRST" ou "NULLS LAST" par exemple).
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
Ok, tri avec le last_login à None
toujours en premier. Et le log passé en info
.
Mis à jour par Thomas Noël il y a plus de 5 ans
Serghei Mihai a écrit :
Ok, tri avec le last_login à
None
toujours en premier. Et le log passé eninfo
.
Ni l'un ni l'autre, sans doute un mauvais patch.
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
Voici le bon.
Mis à jour par Thomas Noël il y a plus de 5 ans
Arf, my bad, pour le coup le « users = sorted(users, key=lambda u: (u.last_login is not None, u.last_login)) » met les dates dans le mauvais sens. Il faut bien un reversed et donc remplacer is not par is.
Mis à jour par Thomas Noël il y a plus de 5 ans
(et à force je me dis qu'il faudrait s'embêter à faire un test je pense :/ )
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Thomas Noël a écrit :
Hmm, trié True vient après False (1 > 0), donc ça :Arf, my bad, pour le coup le « users = sorted(users, key=lambda u: (u.last_login is not None, u.last_login)) » met les dates dans le mauvais sens. Il faut bien un reversed et donc remplacer is not par is.
- True, 2018-01-01
- False, None
- True, 2018-01-02
se trie en "reversed" (le tri par défaut c'est croissant, donc là c'est décroissant):
- True, 2018-01-02
- True, 2018-01-01
- False, None
N'est-ce pas ce que l'on veut ?
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
In [1]: l = [ ...: (True, 2), ...: (False, None), ...: (True, 1)] In [2]: sorted(l, reverse=True) Out[2]: [(True, 2), (True, 1), (False, None)]
Mis à jour par Serghei Mihai il y a plus de 5 ans
Benjamin Dauvergne a écrit :
Thomas Noël a écrit :
Hmm, trié True vient après False (1 > 0), donc ça :Arf, my bad, pour le coup le « users = sorted(users, key=lambda u: (u.last_login is not None, u.last_login)) » met les dates dans le mauvais sens. Il faut bien un reversed et donc remplacer is not par is.
- True, 2018-01-01
- False, None
- True, 2018-01-02
Sauf si on change de u.last_login is not None
en u.last_login is None
.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Serghei Mihai a écrit :
Benjamin Dauvergne a écrit :
Thomas Noël a écrit :
Hmm, trié True vient après False (1 > 0), donc ça :Arf, my bad, pour le coup le « users = sorted(users, key=lambda u: (u.last_login is not None, u.last_login)) » met les dates dans le mauvais sens. Il faut bien un reversed et donc remplacer is not par is.
- True, 2018-01-01
- False, None
- True, 2018-01-02
Sauf si on change de
u.last_login is not None
enu.last_login is None
.
Les valeurs que je donne sont bien avec u.last_login is not None
il me semble.
Mis à jour par Serghei Mihai il y a plus de 5 ans
Si on modifie la fonction lambda
en u.last_login is None
, avec le tri inverse les comptes qui ne se sont jamais connectés remontent en premier.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Serghei Mihai a écrit :
Si on modifie la fonction
lambda
enu.last_login is None
, avec le tri inverse les comptes qui ne se sont jamais connectés remontent en premier.
Mais comme il y a is not None
dans mon code je ne vois pas le besoin de se poser la question.
Mis à jour par Frédéric Péters il y a plus de 5 ans
Au cas où je suis toujours mentionné comme auteur alors que je n'en suis responsable que des premières lignes au tout début.
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Fichier 0001-ldap-don-t-crash-on-duplicated-users-27697.patch 0001-ldap-don-t-crash-on-duplicated-users-27697.patch ajouté
Patch avec test.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
C'est is not None
et pas is None
ensuite le test ne semble pas tester le code en question vu qu'il n'y a pas de last_login de défini sur ces utilisateurs.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Benjamin Dauvergne a écrit :
C'est
is not None
et pasis None
ensuite le test ne semble pas tester le code en question vu qu'il n'y a pas de last_login de défini sur ces utilisateurs.
Erreur de ma part, il y a bien un test sur le last_login mais justement c'est le user avec last_login = None qui est conservé et on préfère conserver celui qui a éventuellement déjà été utilisé.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
- Assigné à
Benjamin Dauvergnesupprimé
Thomas Noël a écrit :
Je pense qu'il faut mettre les None en premier (utilisateur jamais connecté = compte tout nouveau, par ex. vient juste d'être synchronisé, et c'est celui ci qui est le bon pour l'avenir). Donc virer le reserved=True du sorted. IMveryveryHO seulement.
Pas d'accord le compte le plus ancien et le plus utile, la synchronisation sera refaite sur celui là, le code de synchronisation utilisant le même algorithme de recherche.
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Assigné à mis à Serghei Mihai
On a des cas inverses d'Alfortville: à Villeurbanne le compte le plus ancien est le bon (avec les bons rôles). Mais c'est une recette, on s'en fout.
Allons sur la conservation du compte le plus récent.
Je prends et je mets à jour la branche.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Serghei Mihai a écrit :
On a des cas inverses d'Alfortville: à Villeurbanne le compte le plus ancien est le bon (avec les bons rôles). Mais c'est une recette, on s'en fout.
Allons sur la conservation du compte le plus récent.
Il n'y a pas de problèmes sur les rôles puisqu'on les garde tous.
Mis à jour par Serghei Mihai il y a plus de 5 ans
Sur jenkins les tests échouent au niveau de RBAC:
> assert float(t) / 1000 < 0.008 E assert (8.29203200340271 / 1000) < 0.008 E + where 8.29203200340271 = float(8.29203200340271) tests_rbac/test_rbac.py:107: AssertionError
mais tout est vert chez moi en local.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
Il devait manquer le commit #27661 dans ta branche non ?
Mis à jour par Serghei Mihai il y a plus de 5 ans
C'était ça. J'ai refait la branche est poussé.
Jenkins bosse.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
- Statut changé de Solution proposée à Solution validée
Ack.
Mis à jour par Serghei Mihai il y a plus de 5 ans
- Statut changé de Solution validée à Résolu (à déployer)
commit 4db66981f007b16a2269fb1012a8b426e95ecaf4 (origin/master, origin/HEAD) Author: Serghei Mihai <smihai@entrouvert.com> Date: Tue Nov 6 15:15:26 2018 +0100 ldap: don't crash on duplicated users (#27697) Keep roles on the more recently used user, then delete the other ones'.
Mis à jour par Benjamin Dauvergne il y a plus de 5 ans
- Statut changé de Résolu (à déployer) à Fermé
ldap: don't crash on duplicated users (#27697)
Keep roles on the more recently used user, then delete the other ones'.