Scénario #31615
Gérer le changement de mot de passe des utilisateurs depuis EOLE SSO
100%
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 valeur80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1
- l’exception contient un attribut chaîne de caractère
- En cas de mot de passe à changer
- l’exception contient un attribut chaîne de caractère
info
ayant pour valeur80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 773, v1db1
- l’exception contient un attribut chaîne de caractère
- En cas de mauvais mot de passe
- 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 deeoleldapproxy
.- 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
Related issues
History
#1 Updated by Daniel Dehennin over 2 years ago
- Project changed from Distribution EOLE to EoleSSO
#2 Updated by Daniel Dehennin over 2 years ago
- % Done changed from 100 to 0
#3 Updated by Daniel Dehennin over 2 years ago
- Release changed from EOLE 2.8.0 to EOLE 2.8.0.1
#4 Updated by Daniel Dehennin over 2 years ago
- Related to Proposition Scénario #31404: Synchronisation pwdLastSet pour gérer la demande de changement à la 1ere connexion added
#5 Updated by Gilles Grandgérard over 2 years 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 over 2 years ago
- Target version changed from sprint 2021 08-10 Equipe MENSR to Prestation Cadoles MEN 08-10
#7 Updated by Emmanuel GARETTE over 2 years 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 over 2 years ago
- Assigned To set to Matthieu Lamalle
#9 Updated by Joël Cuissinat over 2 years 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 over 2 years 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 over 2 years ago
- Status changed from Nouveau to Résolu
#12 Updated by Joël Cuissinat about 2 years 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 2 years ago
- Status changed from Résolu to En cours
#14 Updated by Emmanuel GARETTE about 2 years ago
- Status changed from En cours to Résolu
#15 Updated by Joël Cuissinat about 2 years ago
- Status changed from Résolu to Terminé (Sprint)
#16 Updated by Joël Cuissinat about 2 years ago
- Status changed from Terminé (Sprint) to En cours
#17 Updated by Joël Cuissinat about 2 years ago
- Status changed from En cours to Terminé (Sprint)