scripts/gestion/chgpass.py
2014-09-26 21:00:31 +02:00

260 lines
9.5 KiB
Python
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
"""
Script de changement de mots de passe LDAP
* Change le mot de passe de l'utilisateur donné en
argument.
Auteur : Pierre-Elliott Bécue <becue@crans.org>
Licence : GPLv3
"""
import sys
import os
if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts')
import gestion.config as config
import config.password
import getpass
import argparse
import gestion.affich_tools as affich_tools
import lc_ldap.shortcuts
import lc_ldap.attributs
import lc_ldap.objets
import smtplib
encoding = sys.stdout.encoding or "UTF-8"
current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()
def check_password(password, no_cracklib=False, dialog=False):
"""
Teste le mot de passe.
* Tests custom + cracklib (sauf si no_cracklib)
"""
problem = False
msg = ""
try:
password.decode('ascii')
except UnicodeDecodeError:
problem = True
if not dialog:
affich_tools.cprint(u'Le mot de passe ne doit contenir que des caractères ascii.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe ne doit contenir que des caractères ascii.\n', "rouge", dialog=dialog)
# Nounou mode
if no_cracklib:
if len(password) >= config.password.root_min_len:
return True
else:
upp = 0
low = 0
oth = 0
cif = 0
# Comptage des caractères
for char in password:
if char.isdigit():
cif += 1
elif char.isupper():
upp += 1
elif char.islower():
low += 1
else:
oth += 1
# Recherche de manque de caractères
if cif < config.password.min_cif:
if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de chiffres.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de chiffres.\n', "rouge", dialog=dialog)
problem = True
if upp < config.password.min_upp:
if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de majuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de majuscules.\n', "rouge", dialog=dialog)
problem = True
if low < config.password.min_low:
if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de minuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de minuscules.\n', "rouge", dialog=dialog)
problem = True
if oth < config.password.min_oth:
if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n', "rouge", dialog=dialog)
problem = True
# Scores sur la longueur
longueur = config.password.upp_value*upp + config.password.low_value*low + config.password.cif_value*cif + config.password.oth_value*oth
if longueur < config.password.min_len:
if not dialog:
affich_tools.cprint(u'Le mot de passe devrait être plus long, ou plus difficile.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe devrait être plus long, ou plus difficile.\n', "rouge", dialog=dialog)
problem = True
if not problem:
try:
import cracklib
except ImportError:
affich_tools.cprint("Attention : la librairie python cracklib n'est pas accessible sur ce serveur.", "rouge")
if sys.modules.get('cracklib', ''):
try:
# Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser
# la rigueur du test) ?
password = cracklib.VeryFascistCheck(password)
return True, msg
except ValueError as e:
if not dialog:
affich_tools.cprint(e.message, "rouge")
else:
msg += affich_tools.coul(str(e).decode(), "rouge", dialog=dialog)
return False, msg
else:
return True, msg
else:
return False, msg
return False, msg
@lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, constructor=lc_ldap.shortcuts.lc_ldap_admin)
def change_password(ldap, login=None, verbose=False, no_cracklib=False, **args):
"""
Change le mot de passe en fonction des arguments
"""
if login is None:
login = current_user
if type(login) == str:
login = login.decode(encoding)
login = lc_ldap.crans_utils.escape(login)
query = ldap.search(u"(uid=%s)" % login, mode="w")
if not query:
affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge")
sys.exit(2)
with query[0] as user:
# Test pour vérifier que l'utilisateur courant peut modifier le mdp de user
try:
user['userPassword'] = [lc_ldap.crans_utils.hash_password("test").decode('ascii')]
user.cancel()
except EnvironmentError as e:
affich_tools.cprint(str(e).decode(encoding), "rouge")
# Génération d'un mail
From = 'roots@crans.org'
To = 'roots@crans.org'
mail = """From: Root <%s>
To: %s
Subject: Tentative de changement de mot de passe !
Tentative de changement du mot de passe de %s par %s.
""" % (From, To , login.encode(encoding), current_user)
# Envoi mail
conn = smtplib.SMTP('localhost')
conn.sendmail(From, To , mail )
conn.quit()
sys.exit(1)
# On peut modifier le MDP
if isinstance(user, lc_ldap.objets.club):
prenom = "Club"
else:
prenom = user['prenom'][0]
affich_tools.cprint("Changement du mot de passe de %s %s." %
(prenom, user['nom'][0]),
"vert")
# Règles du jeu
# (J'ai perdu)
if verbose:
affich_tools.cprint(u"""Règles :
Longueur standard : %s, root : %s,
Minimums : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
Scores de longueur : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
Cracklib : %s.""" % (
config.password.min_len,
config.password.root_min_len,
config.password.min_cif,
config.password.min_low,
config.password.min_upp,
config.password.min_oth,
config.password.cif_value,
config.password.low_value,
config.password.upp_value,
config.password.oth_value,
"Oui" * (not no_cracklib) + "Non" * (no_cracklib)
),
'jaune')
else:
affich_tools.cprint(u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
Il ne doit pas être basé sur un mot du dictionnaire.
Il doit contenir au moins %s chiffre(s), %s minuscule(s),
%s majuscule(s) et au moins %s autre(s) caractère(s).
CTRL+D ou CTRL+C provoquent un abandon.""" %
(
config.password.min_len,
config.password.min_cif,
config.password.min_low,
config.password.min_upp,
config.password.min_oth
), 'jaune')
try:
while True:
mdp = getpass.getpass("Nouveau mot de passe: ")
if check_password(mdp, no_cracklib)[0]:
mdp2 = getpass.getpass("Retaper le mot de passe: ")
if mdp != mdp2:
affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge")
else:
break
except KeyboardInterrupt:
affich_tools.cprint(u'\nAbandon', 'rouge')
sys.exit(1)
except EOFError:
# Un Ctrl-D
affich_tools.cprint(u'\nAbandon', 'rouge')
sys.exit(1)
hashedPassword = lc_ldap.crans_utils.hash_password(mdp)
user['userPassword'] = [hashedPassword.decode('ascii')]
user.save()
affich_tools.cprint(u"Mot de passe de %s changé." % (user['uid'][0]), "vert")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Recherche dans la base des adhérents",
add_help=False)
parser.add_argument('-h', '--help',
help="Affiche ce message et quitte.",
action="store_true")
parser.add_argument('-n', '--no-cracklib',
help="Permet de contourner les règles de choix du mot de passe" +
"(réservé aux nounous).",
action="store_true")
parser.add_argument('-v', '--verbose',
help="Permet de contourner les règles de choix du mot de passe" +
"(réservé aux nounous).",
action="store_true")
parser.add_argument('login', type=str, nargs="?",
help="L'utilisateur dont on veut changer le mot de passe.")
args = parser.parse_args()
if args.help:
parser.print_help()
sys.exit(0)
if args.no_cracklib:
if not lc_ldap.attributs.nounou in ldap.droits:
args.no_cracklib = False
change_password(**vars(args))