Project

General

Profile

Scénario #31615

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

Added by Daniel Dehennin 3 months ago. Updated 9 days ago.

Status:
Terminé (Sprint)
Priority:
Normal
Assigned To:
Category:
-
Start date:
02/22/2021
Due date:
03/12/2021
% Done:

100%

Story points:
5.0
Remaining (hours):
0.00 hour
Velocity based estimate:
Release:
Release relationship:
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).


Subtasks

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


Related issues

Related to SSO - Scénario #31512: Gérer le changement de mot de passe des utilisateurs depuis les SSO web Terminé (Sprint) 01/12/2021 02/19/2021
Related to Distribution EOLE - Scénario #31404: Synchronisation pwdLastSet pour gérer la demande de changement à la 1ere connexion Terminé (Sprint)
Related to 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é 02/24/2021
Related to EoleSSO - Scénario #31898: Documenter les nouvelles variables et les fonctionnalités EoleSSO 2.8 Terminé (Sprint) 03/12/2021 04/02/2021

History

#1 Updated by Daniel Dehennin 3 months ago

  • Project changed from Distribution EOLE to EoleSSO

#2 Updated by Daniel Dehennin 3 months ago

  • % Done changed from 100 to 0

#3 Updated by Daniel Dehennin 3 months ago

  • Release changed from EOLE 2.8.0 to EOLE 2.8.0.1

#4 Updated by Daniel Dehennin 3 months ago

  • Related to Scénario #31404: Synchronisation pwdLastSet pour gérer la demande de changement à la 1ere connexion added

#5 Updated by Gilles Grandgérard 3 months ago

  • Due date set to 03/12/2021
  • Target version set to sprint 2021 08-10 Equipe MENSR
  • Start date set to 02/22/2021
  • Story points set to 5.0

#6 Updated by Joël Cuissinat 3 months ago

  • Target version changed from sprint 2021 08-10 Equipe MENSR to Prestation Cadoles MEN 08-10

#7 Updated by Emmanuel GARETTE 3 months ago

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 Updated by Emmanuel GARETTE 3 months ago

  • Assigned To set to Matthieu Lamalle

#9 Updated by Joël Cuissinat 3 months ago

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

#10 Updated by Gilles Grandgérard 2 months ago

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 Updated by Matthieu Lamalle 2 months ago

  • Status changed from Nouveau to Résolu

#12 Updated by Joël Cuissinat 2 months ago

  • Related to Scénario #31898: Documenter les nouvelles variables et les fonctionnalités EoleSSO 2.8 added

#13 Updated by Joël Cuissinat about 1 month ago

  • Status changed from Résolu to En cours

#14 Updated by Emmanuel GARETTE 22 days ago

  • Status changed from En cours to Résolu

#15 Updated by Joël Cuissinat 22 days ago

  • Status changed from Résolu to Terminé (Sprint)

#16 Updated by Joël Cuissinat 19 days ago

  • Status changed from Terminé (Sprint) to En cours

#17 Updated by Joël Cuissinat 17 days ago

  • Status changed from En cours to Terminé (Sprint)

Also available in: Atom PDF