Projet

Général

Profil

Scénario #31615

Gérer le changement de mot de passe des utilisateurs depuis EOLE SSO

Ajouté par Daniel Dehennin il y a environ 3 ans. Mis à jour il y a presque 3 ans.

Statut:
Terminé (Sprint)
Priorité:
Normal
Assigné à:
Catégorie:
-
Début:
22/02/2021
Echéance:
12/03/2021
% réalisé:

100%

Points de scénarios:
5.0
Restant à faire (heures):
0.00 heure
Estimation basée sur la vélocité:
Release:
Liens avec la release:
Auto

Description

Problème

Sur Scribe, nous avons deux sources de données qui ne peuvent pas être utilisées ensemble simplement :

  • L’annuaire OpenLDAP contient les attributs Envole nécessaires à leur fonctionnement
  • L’Active Directory contient le mot de passe et les informations de changement de mot de passe.

Actuellement, eole-SSO et LemonLDAP::NG ne permettent pas une utilisation totalement fonctionnelle des deux sources :

  • Soit nous utilisons OpenLDAP et nous n’avons pas les informations d’obligation de changement de mot de passe
  • Soit nous utilisons l’Active Directory et nous n’avons pas les attributs Envole

La détection de l’obligation de changement de mot de passe vient du mécanisme des politiques de mot de passe LDAP qui ne peut pas facilement être émulé en synchronisant des attributs de l’active directory vers OpenLDAP (impossible d’ajouter l’attribut pwdLastSet qui n’est défini par aucun schéma LDAP).

Proposition

Les solutions SSO devraient, dans le cadre d’un fonctionnement mixte AD / OpenLDAP :

  • Tenter d’authentifier l’utilisateur sur l’active directory et détecter le besoin de changement de mot de passe
  • Procéder au changement de mot de passe sur l’active directory
  • aller chercher les informations utilisateur dans l’OpenLDAP

EOLE SSO

Il semble possible de détecter le changement de mot de passe pour un coût assez faible :

  • Jouer l’authentification sur l’active directory et détecter l’exception ldap.INVALID_CREDENTIALS
    • En cas de mauvais mot de passe
      • l’exception contient un attribut chaîne de caractère info ayant pour valeur 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1
    • En cas de mot de passe à changer
      • l’exception contient un attribut chaîne de caractère info ayant pour valeur 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 773, v1db1
  • Le formulaire de changement de mot de passe doit procéder au changement de mot de passe (en utilisant changepasswrdeole.pl ?)

Pour tester, j’ai utilisé le petit script suivant :

#!/usr/bin/python3

import ldap
conn = ldap.initialize('ldaps://addc.domscribe.ac-test.fr')
conn.protocol_version = 3

ad_conn = ldap.initialize('ldaps://addc.domscribe.ac-test.fr')
ad_conn.protocol_version = 3

try:
    ad_conn.simple_bind_s('CN=admin,CN=Users,DC=domscribe,DC=ac-test,DC=fr', 'MauvaisMot2Passe!')
except Exception as err:
    print("AD BAD PASSWORD: {}".format(err))

try:
    ad_conn.simple_bind_s('CN=prof.6a,CN=Users,DC=domscribe,DC=ac-test,DC=fr', 'Eole12345!')
except Exception as err:
    print("AD MUST CHANGE:  {}".format(err))

openldap_conn = ldap.initialize('ldap://127.0.0.1')
openldap_conn.protocol_version = 3

try:
    openldap_conn.simple_bind_s('uid=admin,ou=local,ou=personnels,ou=utilisateurs,ou=0000000A,ou=ac-test,ou=education,o=gouv,c=fr', 'MauvaisMot2Passe!')
except Exception as err:
    print("OPENLDAP BAD PASSWORD: {}".format(err))  

try:
    openldap_conn.simple_bind_s('uid=prof.6b,ou=local,ou=personnels,ou=utilisateurs,ou=0000000A,ou=ac-test,ou=education,o=gouv,c=fr', 'Eole12345!')
except Exception as err:
    print("OPENLDAP MUST CHANGE:  {}".format(err))

Qui renvoit:

AD BAD PASSWORD: {'desc': 'Invalid credentials', 'info': '80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1'}
AD MUST CHANGE:  {'desc': 'Invalid credentials', 'info': '80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 773, v1db1'}
OPENLDAP BAD PASSWORD: {'desc': 'Invalid credentials'}
OPENLDAP MUST CHANGE:  {'desc': 'Invalid credentials'}

Le module LemonLDAP::NG fait ce genre de détection aussi.

Proposition technique

  • dans les variables de configuration des annuaires LDAP d'eoleSSO, permettre de déclarer un annuaire AD. Dans ce cas, il faudrait à première vue ajouter les informations suivantes :
    • adresse du serveur AD
    • branche de recherche des utilisateurs dans AD (ex: CN=Users,DC=domscribe,DC=ac-test,DC=fr)
  • dans python-eoleldaptor, créer par exmple une classe eoleadproxy héritant de eoleldapproxy.
    • la méthode authenticate de cette classe ferait un premier test de connexion sur l'annuaire AD (recherche DN + bind) afin de remonter l'exception AD en cas d'échec d'authentification.
    • Si pas d'erreur, enchainer sur la méthode authenticate d'eoleldapproxy pour reprendre le fonctionnement classique sur l'annuaire ldap.
  • au niveau d'eoleSSO, prévoir 3 résultats possibles au niveau de l'authentification (ok/echec/changement password requis) et faire remonter l'info jusqu'au frontend pour afficher le message voulu.

Le travail impacterait principalement les fichiers ssoshare/authserver.py, eolesso/dataproxy.py et authform.py pour le côté frontend. Le plus difficile étant de gérer l'aspect callback hell du code :-)

Cette solution peut fonctionner sur un module de type scribe AD, ça parait plus compliqué dans le cas d'annuaires multi-établissement ou répliqués (je ne sais pas si il y a des cas d'usage en mode AD).


Sous-tâches

Tâche #31773: Récupérer l'information de changement de mot de passe depuis l'adFerméMatthieu Lamalle

Tâche #31839: Gérer le formulaire de changement de mot de passeFerméMatthieu Lamalle

Tâche #31856: Gérer la configuration via un dictionnaireFerméEmmanuel GARETTE

Tâche #31977: Ajouter les traductions manquantesFerméMatthieu Lamalle

Tâche #31980: La fonctionnalité de changement de mot de passe expiré devrait conserver l'URL du service initialement demandé par l'utilisateur (service=)FerméMatthieu Lamalle

Tâche #31981: Ajouter un message lorsque le changement de mot de passe échoueFerméMatthieu Lamalle

Tâche #32006: Authentification EoleSSO impossible sur AmonEcoleFerméEmmanuel GARETTE

Tâche #32068: "Forcer la modification du mot de passe à la 1ère connexion" ne semble plus fonctionnel sur AmonEcoleFerméEmmanuel GARETTE

Tâche #32230: Il n'est plus possible de saisir le mot de passe admin d'AmonEcole dans un terminalFerméEmmanuel GARETTE


Demandes liées

Lié à SSO - Scénario #31512: Gérer le changement de mot de passe des utilisateurs depuis les SSO web Terminé (Sprint) 12/01/2021 19/02/2021
Lié à Distribution EOLE - Proposition Scénario #31404: Synchronisation pwdLastSet pour gérer la demande de changement à la 1ere connexion Fermé
Lié à Distribution EOLE - Tâche #31738: Valider le scénario Gérer le changement de mot de passe des utilisateurs depuis EOLE SSO (08-10) Fermé 24/02/2021
Lié à EoleSSO - Scénario #31898: Documenter les nouvelles variables et les fonctionnalités EoleSSO 2.8 Terminé (Sprint) 12/03/2021 02/04/2021

Historique

#1 Mis à jour par Daniel Dehennin il y a environ 3 ans

  • Projet changé de Distribution EOLE à EoleSSO

#2 Mis à jour par Daniel Dehennin il y a environ 3 ans

  • % réalisé changé de 100 à 0

#3 Mis à jour par Daniel Dehennin il y a environ 3 ans

  • Release changé de EOLE 2.8.0 à EOLE 2.8.0.1

#4 Mis à jour par Daniel Dehennin il y a environ 3 ans

#5 Mis à jour par Gilles Grandgérard il y a environ 3 ans

  • Echéance mis à 12/03/2021
  • Version cible mis à sprint 2021 08-10 Equipe MENSR
  • Début mis à 22/02/2021
  • Points de scénarios mis à 5.0

#6 Mis à jour par Joël Cuissinat il y a environ 3 ans

  • Version cible changé de sprint 2021 08-10 Equipe MENSR à Prestation Cadoles MEN 08-10

#7 Mis à jour par Emmanuel GARETTE il y a environ 3 ans

Les tests ont été réalisé avec la lib LDAP mais EOLESSO est développé avec ldaptor (module asynchrone de connexion à LDAP spécifique à twisted).

Des tests effectués il nous semblait que la proposition n'est pas faisable avec ldaptor.

Voici un extrait du code : /usr/lib/python2.7/dist-packages/ldaptor/protocols/ldap/ldapserver.py, qui est, sauf erreur de notre part, utilisé :

            def _gotEntry(entry, auth):
                if entry is None:
                    raise ldaperrors.LDAPInvalidCredentials

Comme on peut le voir il y a bien un raise en cas de non connexion mais sans message d'erreur explicite.

Est-ce que la proposition consisterait a ne pas utiliser ldaptor pour cette partie du code et donc ne pas faire de l'asynchrone ?

GG :
Non on ne modifie pas ldaptor.
l'idée est de demander à l'AD quel est l'état de pwdLastSet avant de lancer 'raise LDAPInvalidCredentials'.
Si Mdp demandé, il faut rediriger vers l'URL de changement de mot de passe
Si pas de Mdp demandé ==> raise LDAPInvalidCredentials.

#8 Mis à jour par Emmanuel GARETTE il y a environ 3 ans

  • Assigné à mis à Matthieu Lamalle

#9 Mis à jour par Joël Cuissinat il y a environ 3 ans

  • Lié à Tâche #31738: Valider le scénario Gérer le changement de mot de passe des utilisateurs depuis EOLE SSO (08-10) ajouté

#10 Mis à jour par Gilles Grandgérard il y a environ 3 ans

exemple d'accès sans DN

#!/usr/bin/python3
# apt install python3-ldap3
# samba-tool ou create OU=OUTEST
# samba-tool ou create OU=OUTEST
# samba-tool user move Titi OU=OUTEST
#
import traceback
import ldap3
from ldap3.utils.log import log, set_library_log_detail_level, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED, format_ldap_message
from ldap3 import Server, Connection, ALL
from ldap3.core.exceptions import LDAPException

SERVER='ldaps://192.168.0.5'
BASEDN="DC=domseth,DC=ac-test,DC=fr" 
USER="Titi@domseth.ac-test.fr" 
CURREENTPWD="Eole12345!" 

SEARCHFILTER='(&(userPrincipalName='+USER+')(objectClass=person))'
USER_DN="" 
PWD_LAST_SET=0

try:
    ldap_server = ldap3.Server(SERVER, get_info=ldap3.ALL)
    conn = ldap3.Connection(ldap_server, USER, CURREENTPWD, auto_bind=True)
    conn.start_tls(read_server_info=True)
    conn.search(search_base = BASEDN,
                search_filter = SEARCHFILTER,
                search_scope = ldap3.SUBTREE,
                attributes = [ 'userPrincipalName', 'pwdLastSet'],
                paged_size = 5)

    for entry in conn.response:
        if entry.get("dn") and entry.get("attributes") and entry.get("attributes").get("userPrincipalName") and entry.get("attributes").get("userPrincipalName") == USER: # to ignore others result (refs)!
            USER_DN=entry.get("dn")
            PWD_LAST_SET=entry.get("attributes").get("pwdLastSet")

    if USER_DN:
        print("DN = " + USER_DN)
        print("pwdLastSet = " + str(PWD_LAST_SET))
        #NEWPWD="new_password" 
        #print(ldap3.extend.microsoft.modifyPassword.ad_modify_password(conn, USER_DN, NEWPWD, CURREENTPWD,  controls=None))
    else:
        print("User DN is missing!")
except LDAPException as ldapException:
    print("ERREUR")
    print (vars(ldapException))
    traceback.print_exc()

Sortie:
DN = CN=Titi,OU=OUTEST,DC=domseth,DC=ac-test,DC=fr
pwdLastSet = 2021-03-02 07:27:28.084785+00:00

#11 Mis à jour par Matthieu Lamalle il y a environ 3 ans

  • Statut changé de Nouveau à Résolu

#12 Mis à jour par Joël Cuissinat il y a environ 3 ans

  • Lié à Scénario #31898: Documenter les nouvelles variables et les fonctionnalités EoleSSO 2.8 ajouté

#13 Mis à jour par Joël Cuissinat il y a presque 3 ans

  • Statut changé de Résolu à En cours

#14 Mis à jour par Emmanuel GARETTE il y a presque 3 ans

  • Statut changé de En cours à Résolu

#15 Mis à jour par Joël Cuissinat il y a presque 3 ans

  • Statut changé de Résolu à Terminé (Sprint)

#16 Mis à jour par Joël Cuissinat il y a presque 3 ans

  • Statut changé de Terminé (Sprint) à En cours

#17 Mis à jour par Joël Cuissinat il y a presque 3 ans

  • Statut changé de En cours à Terminé (Sprint)

Formats disponibles : Atom PDF