From 2d04dedf7be783b9b0a0ab0ea958256a8e0924ef Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sat, 29 Nov 2014 16:53:02 +0100 Subject: [PATCH] =?UTF-8?q?[gest=5Fcrans=5Flc]=20D=C3=A9coupage=20dans=20p?= =?UTF-8?q?lusieurs=20fichiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Héritage multiple banzai \o/ --- gestion/dialog/CPS.py | 387 +++++ gestion/dialog/__init__.py | 2 + gestion/dialog/adherent.py | 662 ++++++++ gestion/dialog/blacklist.py | 226 +++ gestion/dialog/certificat.py | 534 ++++++ gestion/dialog/club.py | 33 + gestion/dialog/lc.py | 465 ++++++ gestion/dialog/machine.py | 359 ++++ gestion/dialog/proprio.py | 534 ++++++ gestion/gest_crans_lc.py | 3021 +--------------------------------- 10 files changed, 3209 insertions(+), 3014 deletions(-) create mode 100644 gestion/dialog/CPS.py create mode 100644 gestion/dialog/__init__.py create mode 100644 gestion/dialog/adherent.py create mode 100644 gestion/dialog/blacklist.py create mode 100644 gestion/dialog/certificat.py create mode 100644 gestion/dialog/club.py create mode 100644 gestion/dialog/lc.py create mode 100644 gestion/dialog/machine.py create mode 100644 gestion/dialog/proprio.py diff --git a/gestion/dialog/CPS.py b/gestion/dialog/CPS.py new file mode 100644 index 00000000..8252374c --- /dev/null +++ b/gestion/dialog/CPS.py @@ -0,0 +1,387 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import os +import sys +import time +import ldap +import signal +import inspect +import traceback + +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') +from pythondialog import Dialog as PythonDialog +from pythondialog import DialogError, DialogTerminatedBySignal +from gestion.affich_tools import get_screen_size, coul + +debug_enable = False +debugf = None + +def mydebug(txt): + """Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log""" + global debugf, debug_enable + if debug_enable: + if debugf is None: + debugf = open('/tmp/gest_crans_lc.log', 'w') + if isinstance(txt, list): + for v in txt: + mydebug(' ' + str(v)) + else: + debugf.write(str(txt)+'\n') + debugf.flush() + os.fsync(debugf) + +# Implémentation "à la main" de la tail récursion en python +# voir http://kylem.net/programming/tailcall.html +# je trouve ça assez sioux +# En plus, ça nous permet de gérer plus facilement le +# code en CPS (Continuation Passing Style) +class TailCaller(object) : + """ + Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion + faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) + """ + other_callers = {} + def __init__(self, f) : + self.f = f + self.func_name = f.func_name + TailCaller.other_callers[id(self)]=f.func_name + def __del__(self): + del(TailCaller.other_callers[id(self)]) + + def __call__(self, *args, **kwargs) : + mydebug("***%s calling" % self.func_name) + stacklvl = len(inspect.stack()) + try: + ret = self.f(*args, **kwargs) + except Continue as c: + ret = c.tailCall + while isinstance(ret, TailCall): + # Si la continuation a été créer par un TailCaller précédent, on propage + # Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall + # Crée après lui. + if stacklvl>=ret.stacklvl: + mydebug("***%s terminate" % self.func_name) + raise Continue(ret) + mydebug("***%s doing %s" % (self.func_name, ret)) + try: + ret = ret.handle() + except Continue as c: + ret = c.tailCall + mydebug("***%s returning %s" % (self.func_name, ret)) + return ret + + +def tailcaller(f): + """ + Décorateur retardant la décoration par TailCaller d'une fonction + À utiliser sur les fonctions faisant de la tail récursion + et devant retourner une valeur. On l'utilise de toute + façon sur la fonction d'entrée dans l'application + Une fonction de devrait pas instancier de TailCall sans + être décoré par ce décorteur de façon générale + """ + + f.tailCaller = True + return f + +class Continue(Exception): + """Exception pour envoyer des TailCall en les raisant""" + def __init__(self, tailCall): + self.tailCall = tailCall + +class TailCall(object) : + """ + Appel tail récursif à une fonction et ses argument + On peut voir un TailCall comme le fait de retarder + l'appel à une fonction (avec ses listes d'arguements) + à un moment futur. La fonction sera appelée dans les + cas suivant : + * Le TailCall est retourné par une fonction qui est un TailCaller + * L'exception Continue(TailCall(...)) est levée et traverse + une fonction qui est un TailCaller + """ + + def __init__(self, call, *args, **kwargs) : + self.stacklvl = len(inspect.stack()) + if isinstance(call, TailCall): + call.kwargs.update(**kwargs) + kwargs = call.kwargs + args = call.args + args + call = call.call + + self.call = call + self.args = args + self.kwargs = kwargs + + self.check(self.args, self.kwargs) + + def check(self, args, kwargs): + call = self.call + if isinstance(call, TailCaller): + call = call.f + targs = inspect.getargspec(call) + if targs.varargs is not None: + if len(args) + len(kwargs) > len(targs.args): + raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs))) + for key in kwargs: + if key not in targs.args: + raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key)) + + def __str__(self): + return "TailCall<%s(%s%s%s)>" % ( + self.call.func_name, + ', '.join(repr(a) for a in self.args), + ', ' if self.args and self.kwargs else '', + ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items()) + ) + + def copy(self): + ''' + Renvois une copie de l'objet courant + attention les elements et args ou kwargs sont juste linké + ça n'est pas gennant dans la mesure où ils ne sont normalement pas + éditer mais remplacé par d'autres éléments + ''' + result = TailCall(self.call, *list(self.args), **dict(self.kwargs)) + result.stacklvl = self.stacklvl + return result + + def __call__(self, *args, **kwargs): + tmpkwargs={} + tmpkwargs.update(self.kwargs) + tmpkwargs.update(kwargs) + self.check(self.args + args, tmpkwargs) + self.kwargs.update(kwargs) + self.args = self.args + args + return self + + def handle(self) : + """ + Exécute la fonction call sur sa liste d'argument. + on déréférence les TailCaller le plus possible pour réduire + la taille de la stack + """ + caller = None + call = self.call + while isinstance(call, TailCaller) : + caller = call + call = self.call.f + return call(*self.args, **self.kwargs) + +def unicode_of_Error(x): + """Formatte des exception""" + return u"\n".join(unicode(i, 'utf-8') if type(i) == str + else repr(i) for i in x.args) + +def raiseKeyboardInterrupt(x, y): + """fonction utilisée pour réactiver les Ctrl-C""" + raise KeyboardInterrupt() + + +class Dialog(object): + """Interface de gestion des machines et des adhérents du crans, version lc_ldap""" + def __getattribute__(self, attr): + ret = super(Dialog, self).__getattribute__(attr) + # Petit hack pour que ça soit les methodes de l'objet instancié qui soient + # décorée par TailCaller et pas les méthodes statiques de la classe Dialog + if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller): + ret = TailCaller(ret) + setattr(self, attr, ret) + return ret + + def __init__(self, debug_enable=False): + signal.signal(signal.SIGINT, signal.SIG_IGN) + + self.debug_enable = debug_enable + + # On met un timeout à 10min d'innactivité sur dialog + self.timeout = 600 + self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN) + + _dialog = None + @property + def dialog(self): + """ + Renvois l'objet dialog. + """ + if self._dialog is None: + self._dialog = PythonDialog() + self.dialog_last_access = time.time() + return self._dialog + + def nyan(self, cont, *args, **kwargs): + """ + Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan + """ + (lines, cols) = get_screen_size() + print "\033[48;5;17m" + print " "*(lines * cols) + cols = int(min(cols/2, 65)) + lines = int(lines) -1 + cmd = "/usr/bin/nyancat -W %s -H %s" % (cols, lines) + os.system(cmd) + raise Continue(cont) + + @tailcaller + def handle_dialog(self, cancel_cont, box, *args): + """ + Gère les fonctions appelant une fênetre dialog : + gestion de l'appuie sur Ctrl-C et rattrapage des exception / segfault de dialog. + cancel_cont représente l'endroit où retourner si Ctrl-C + """ + ctrlC=False + ret=None + try: + signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C + ret = box(*args) + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + except KeyboardInterrupt: + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + raise Continue(cancel_cont) + except DialogTerminatedBySignal as e: + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + if e[1] == 11: + self.dialog.msgbox( + "La fenêtre dialog à été fermée par une erreur de segmentation", + timeout=self.timeout, title="Erreur rencontrée", width=73, height=10 + ) + raise Continue(cancel_cont) + else: + raise + finally: + if ret: + return ret + else: + EnvironmentError("Pas de ret ?!? c'est pas possible ") + + + @tailcaller + def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]): + """ + Gère les fonctions traitant les résultat d'appels à dialog. + s'occupe de gérer les exceptions, Ctrl-C, propagation de certaine exceptions, l'appuis sur annuler. + Le code à exécuté lui ai passé via la liste codes_todo, qui doit contenir une liste de triple : + (code de retour dialog, fonction à exécuter, liste des arguements de la fonction) + la fonction est appelée sur ses arguements si le code retourné par dialog correspond. + codes_todo = [(code, todo, todo_args)] + """ + # Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cancel_cont) + # Sinon, c'est OK + else: + for (codes, todo, todo_args) in codes_todo: + if code in codes: + try: + signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C + # On effectue ce qu'il y a a faire dans todo + ret = todo(*todo_args) + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + return ret + # On propage les Continue + except self.error_to_raise: + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + raise + # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + self.dialog.msgbox(traceback.format_exc() if self.debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout, + title="Erreur rencontrée", width=73, height=10) + raise Continue(error_cont) + except KeyboardInterrupt: + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + raise Continue(cancel_cont) + + # En cas de code de retour dialog non attendu, on prévient et on retourne au menu d'édition + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, + timeout=self.timeout, title="Erreur rencontrée", width=73, height=10) + raise Continue(error_cont) + + + @tailcaller + def get_comment(self, title, text, cont, init='', force=False): + """ + Fait entrer à l'utilisateur un commentaire et le retourne. + Si force est à True, on oblige le commentaire à être non vide + """ + (code, output) = self.dialog.inputbox(text=text, title=title, timeout=self.timeout, init=init) + retry_cont = TailCall(self.get_comment, title=title, text=text, cont=cont, force=force) + def todo(output, force, title, retry_cont): + if force and not output: + self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose", timeout=self.timeout, title=title) + raise Continue(retry_cont) + else: + return unicode(output, 'utf-8') + + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [output, force, title, retry_cont])] + ) + + @tailcaller + def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs): + """ + Affiche une série d'inpuxbox pour faire entrer un mot de passe puis le retourne, + si confirm=True, il y a une confirmation du mot de passe de demandée + """ + def todo(self_cont, cont): + (code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs) + if code != self.dialog.DIALOG_OK: + raise Continue(cont) + elif not pass1: + raise ValueError("Mot de pass vide !") + if confirm: + (code, pass2) = self.dialog.passwordbox("Comfirmer le mot de passe", timeout=self.timeout, title=title, **kwargs) + if code != self.dialog.DIALOG_OK: + raise Continue(self_cont) + if pass1 != pass2: + raise ValueError("Les deux mots de passe ne concordent pas") + return pass1 + self_cont = TailCall(self.get_password, cont=cont) + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [self_cont, cont])] + ) + + @tailcaller + def get_timestamp(self, title, text, cont, hour=-1, minute=-1, second=-1, day=0, month=0, year=0): + """Fait choisir une date et une heure et retourne le tuple (year, month, day, hour, minute, second)""" + retry_cont = TailCall(self.get_timestamp, title=title, text=text, cont=cont, hour=hour, + minute=minute, second=second, day=day, month=month, year=year) + def get_date(day, month, year): + (code, output) = self.dialog.calendar(text, day=day, month=month, year=year, + timeout=self.timeout, title=title) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif output: + (day, month, year) = output + return (year, month, day) + else: + raise EnvironmentError("Pourquoi je n'ai pas de date ?") + def get_time(hour, minute, second, day, month, year): + (code, output) = self.dialog.timebox(text, timeout=self.timeout, hour=hour, + minute=minute, second=second) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(retry_cont(day=day, month=month, year=year)) + elif output: + (hour, minute, second) = output + return (hour, minute, second) + else: + raise EnvironmentError("Pourquoi je n'ai pas d'horaire ?") + (year, month, day) = get_date(day, month, year) + (hour, minute, second) = get_time(hour, minute, second, day, month, year) + return (year, month, day) + (hour, minute, second) + diff --git a/gestion/dialog/__init__.py b/gestion/dialog/__init__.py new file mode 100644 index 00000000..f281d290 --- /dev/null +++ b/gestion/dialog/__init__.py @@ -0,0 +1,2 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- diff --git a/gestion/dialog/adherent.py b/gestion/dialog/adherent.py new file mode 100644 index 00000000..d07df914 --- /dev/null +++ b/gestion/dialog/adherent.py @@ -0,0 +1,662 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import sys +import time +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs +from lc_ldap.attributs import UniquenessError + +import proprio +from CPS import TailCall, tailcaller, Continue + +class Dialog(proprio.Dialog): + def modif_adherent_blacklist(self, adherent, cont): + """Raccourci vers edit_blacklist spécifique aux adherent""" + return self.edit_blacklist(obj=adherent, title="Éditions des blacklist de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont) + + + def modif_adherent(self, cont, adherent=None, tag=None): + """Menu d'édition d'un adhérent""" + if adherent is None: + adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) + a = attributs + menu_droits = { + 'Administratif' : [a.cableur, a.nounou], + 'Personnel':[a.cableur, a.nounou, a.soi], + 'Études':[a.nounou, a.soi, a.cableur], + 'Chambre':[a.cableur, a.nounou], + 'Compte':[a.cableur, a.nounou], + 'GPGFingerprint' : [a.nounou, a.soi], + 'Remarques' : [a.cableur, a.nounou], + 'Droits':[a.nounou, a.bureau], + 'Blackliste':[a.cableur, a.nounou], + 'Vente':[a.cableur, a.nounou], + 'Supprimer':[a.nounou, a.bureau], + } + menu = { + 'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif}, + 'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel}, + 'Études' : {'text' : "Étude en cours (perso, je pense que c'est à supprimer)", "callback":self.adherent_etudes}, + 'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre}, + 'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":self.proprio_compte, 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"}, + 'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint}, + 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, + 'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits}, + 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_adherent_blacklist}, + 'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente}, + 'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))}, + } + menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte'] + menu_compte_crans = ['Droits'] + medu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Supprimer'] + + if "cransAccount" in adherent['objectClass']: + menu_order.extend(menu_compte_crans) + menu_order.extend(medu_end) + def box(default_item=None): + choices = [] + for key in menu_order: + if self.has_right(menu_droits[key], adherent): + choices.append((key, menu[key]['text'], menu[key].get('help', ""))) + return self.dialog.menu( + "Que souhaitez vous modifier ?", + width=0, + height=0, + menu_height=0, + timeout=self.timeout, + item_help=1, + default_item=str(default_item), + title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=choices) + + def todo(tag, menu, adherent, cont_ret): + if not tag in menu_order: + raise Continue(cont_ret) + else: + if 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], cont=cont_ret, **{menu[tag].get('adherent', 'adherent'):adherent})) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_adherent_attributs, adherent=adherent, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) + + (code, tag) = self.handle_dialog(cont, box, tag) + cont_ret = TailCall(self.modif_adherent, cont=cont, adherent=adherent, tag=tag) + + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont(proprio=adherent), + error_cont=cont_ret, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])] + ) + + def adherent_administratif(self, cont, adherent, default_item=None): + """Menu de gestion du compte crans d'un proprio""" + + a = attributs + menu_droits = { + "Adhésion": [a.cableur, a.nounou], + 'Connexion': [a.cableur, a.nounou], + "Charte MA" : [a.nounou, a.bureau], + "Carte Étudiant" : [a.nounou, a.cableur, a.tresorier], + } + menu = { + "Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion}, + 'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion}, + "Carte Étudiant" : {"text" : "Validation de la carte étudiant", "help":"", "callback":self.adherent_carte_etudiant}, + "Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte}, + } + menu_order = ["Adhésion", 'Connexion'] + if self.has_right(a.tresorier, adherent) or not adherent.carte_controle(): + menu_order.append("Carte Étudiant") + menu_order.append("Charte MA") + def box(default_item=None): + return self.dialog.menu( + "Quelle action administrative effectuer", + width=0, + height=0, + timeout=self.timeout, + item_help=1, + default_item=str(default_item), + title="Gestion administrative de %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]), + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) + + def todo(tag, menu, adherent, self_cont): + if not tag in menu_order: + raise Continue(self_cont) + elif 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, adherent=adherent)) + else: + raise EnvironmentError("Il n'y a pas de champs 'callback' pour le tag %s" % tag) + + + (code, tag) = self.handle_dialog(cont, box, default_item) + self_cont = TailCall(self.adherent_administratif, adherent=adherent, cont=cont, default_item=tag) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont])] + ) + + def adherent_adhesion(self, cont, adherent): + self.dialog.msgbox("todo", width=0, height=0) + return cont + def adherent_connexion(self, cont, adherent, cancel_cont=None): + self.dialog.msgbox("todo", width=0, height=0) + return cont + def adherent_carte_etudiant(self, cont, adherent, values={}, cancel_cont=None): + # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut + a = attributs + choices = [] + if self.has_right(a.tresorier, adherent) or not adherent.carte_controle(): + choices.append((a.carteEtudiant.ldap_name, "Carte étudiant présentée", 1 if adherent[a.carteEtudiant.ldap_name] or values.get(a.carteEtudiant.ldap_name, False) else 0)) + if self.has_right(a.tresorier, adherent): + choices.append(("controleCarte", "La carte a-t-elle été controlée", 1 if adherent.carte_controle() or values.get("controleCarte", False) else 0)) + + if not choices: + self.dialog.msgbox("Carte d'étudiant déjà validée et non modifiable", title="Gestion de la carte étudiant", width=0, height=0) + if cancel_cont: + cancel_cont(cont=cont) + try: + cont(cancel_cont=cancel_cont) + except TypeError: + pass + raise Continue(cont) + + def box(): + return self.dialog.checklist("Gestion de la carte étudiant", + height=0, + width=0, + timeout=self.timeout, + list_height=7, + choices=choices, + title="Gestion de la carte étudiant") + + def todo(values, adherent, cont): + # On met à jour chaque attribut si sa valeur à changé + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + # Si on est trésorier et que controleCarte a changer on enregistre le changement + if self.has_right(a.tresorier, adherent) and values["controleCarte"] and not adherent.carte_controle(): + if adherent["controle"]: + adherent["controle"] = u"c%s" % adherent["controle"][0] + else: + adherent["controle"] = u"c" + elif self.has_right(a.tresorier, adherent) and not values["controleCarte"] and adherent.carte_controle(): + adherent["controle"]=unicode(adherent["controle"][0]).replace('c','') + if not adherent["controle"][0]: + adherent["controle"] = [] + # Si la carte n'est pas validé ou qu'on est trésorier, on sauvegarde les changements + if values[a.carteEtudiant.ldap_name] and not adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)): + adherent[a.carteEtudiant.ldap_name] = u"TRUE" + elif not values[a.carteEtudiant.ldap_name] and adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)): + adherent[a.carteEtudiant.ldap_name] = [] + if adherent["controle"]: + adherent["controle"]=unicode(adherent["controle"][0]).replace('c','') + if not adherent["controle"][0]: + adherent["controle"] = [] + adherent.history_gen() + adherent.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(adherent=adherent)) + + (code, output) = self.handle_dialog(cont, box) + # On transforme la liste des cases dialog cochée en dictionnnaire + values = dict((a[0], a[0] in output) for a in choices) + + # Une continuation que l'on suivra si quelque chose se passe mal + retry_cont = TailCall(self.adherent_carte_etudiant, adherent=adherent, cont=cont, values=values) + + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cancel_cont if cancel_cont else cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [values, adherent, cont])] + ) + def adherent_charte(self, cont, adherent): + a = attributs + attribs = [a.charteMA] + return self.edit_boolean_attributs( + obj=adherent, + attribs=attribs, + title="Signature de la charte membre actif de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), + update_obj='adherent', + cont=cont) + + def adherent_personnel(self, cont, adherent=None, fields_attrs={}, make_compte_crans=None, force_create=False): + """ + Permet d'éditer les nom, prénom et téléphone d'un adhérent, ou de créer un adhérent. + Il faut encore trouver un moyen de récupérer des valeurs pour les attributs mail et chbre + """ + a = attributs + # Quel sont les attributs ldap dont on veut afficher et + # la taille du champs d'édition correspondant + to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)] + non_empty = [a.nom, a.prenom, a.tel] + input_type = {'default':0} + + # Quel séparateur on utilise pour les champs multivalué + separateur = ' ' + + def box(make_compte_crans): + if force_create and adherent is None and fields_attrs and make_compte_crans is not None: + return (self.dialog.DIALOG_OK, [fields_attrs[a] for a,l in to_display], make_compte_crans) + if adherent: + attrs = dict((k,[str(a) for a in at]) for k,at in adherent.items()) + if 'cransAccount' in adherent['objectClass']: + input_type[attributs.mail] = 2 + to_display.append((attributs.mailExt, 30)) + else: + attrs = {} + if make_compte_crans is None: + if self.dialog.yesno("Crééra-t-on un compte crans à l'utilisateur ?", timeout=self.timeout, title="Création d'un adhérent", width=50) == self.dialog.DIALOG_OK: + input_type[attributs.mail] = 2 + make_compte_crans = True + to_display.append((attributs.mailExt, 30)) + else: + make_compte_crans = False + + fields = [( + "%s %s:" % (a.legend, '(optionnel) ' if a.optional else ''), + fields_attrs.get(a, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else []))), + l+1, l, + input_type.get(a, input_type['default']) + ) for a,l in to_display] + + (code, tags) = self.dialog.form( + text="", + timeout=self.timeout, + height=0, width=0, form_height=0, + fields=fields, + title="Création d'un adhérent" if adherent is None else "Édition des informations de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), + backtitle="Gestion des adhérents du Crans") + return (code, tags, make_compte_crans) + + def modif_adherent(adherent, attrs): + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + for (key, values) in attrs.items(): + adherent[key]=values + adherent.validate_changes() + adherent.history_gen() + adherent.save() + return adherent + + def create_adherent(attrs, make_compte_crans, force_create, self_cont, cont): + if not force_create: + items = self.conn.search("(&(prenom=%s)(nom=%s))" % (attrs['prenom'], attrs['nom'])) + if items: + newadherent = self.select_one(items, title="Choisir un adhérant existant", text="Des adhérent avec les même noms et prénoms existent déjà, en utiliser un ?\n(Annuler pour continuer la création)", cont=self_cont(make_compte_crans=make_compte_crans, force_create=True)) + raise Continue(cont(adherent=newadherent)) + with self.conn.newAdherent({}) as adherent: + delay={} + for (key, values) in attrs.items(): + try: + adherent[key]=values + # En cas d'erreur, on a peut être besoin du compte crans + except ValueError: + delay[key]=values + print delay + # on récupère la chambre + adherent = self.adherent_chambre_campus(success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) + # Si c'est EXT, on demande une adresse complète + if 'EXT' in adherent['chbre']: + adherent = self.adherent_chambre_ext(keep_machine=True, keep_compte=True, success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) + # Si compte crans à créer, on le crée. + # On le met en dernier pour éviter de faire entrez plusieurs fois son mdp à l'adhérent + # en cas d'erreur de la part du cableur + if make_compte_crans: + adherent = self.proprio_compte_create(proprio=adherent, cont=self_cont(make_compte_crans=None, force_create=False, adherent=None), update_obj='adherent', return_obj=True) + # On réeaffecte les attributs de tout à l'heure + for (key, values) in delay.items(): + adherent[key]=values + # On confirme la création + if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"): + adherent.validate_changes() + adherent.create() + else: + adherent = None + return adherent + + def todo(to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, self_cont, cont): + attrs = {} + # On traite les valeurs reçues + for ((a,l),values) in zip(to_display, tags): + if not values and a in non_empty: + raise ValueError("%s ne devrait pas être vide" % a.legend) + values = unicode(values, 'utf-8') + # Si le champs n'est pas single value, on utilise separateur pour découper + # et on ne garde que les valeurs non vides + if not a.singlevalue: + values = [v for v in values.split(separateur) if v] + attrs[a.ldap_name]=values + if adherent: + adherent = modif_adherent(adherent, attrs) + else: + adherent = create_adherent(attrs, make_compte_crans, force_create, self_cont, cont) + raise Continue(cont(adherent=adherent)) + + + (code, tags, make_compte_crans) = self.handle_dialog(cont, box, make_compte_crans) + + # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu + # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré + # c'est au cableur de corriger ou d'annuler + fields_attrs = dict((a, values) for ((a,l),values) in zip(to_display, tags)) + retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, fields_attrs=fields_attrs) + + return self.handle_dialog_result( + code=code, + output=tags, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, retry_cont, cont])] + ) + + def adherent_etudes(self, adherent, cont, cancel_cont=None): + """Gestion des études de l'adhérent""" + self.dialog.msgbox("todo", width=0, height=0) + return cont + + @tailcaller + def adherent_chambre_campus(self, success_cont, cont, adherent, create=False): + """Permet de faire déménager d'adhérent sur le campus""" + def box(): + return self.dialog.inputbox( + "chambre ?", + title="%s de %s %s" % ("Création" if create else "Déménagement", adherent['prenom'][0], adherent["nom"][0]), + cancel_label="Retour", + width=50, timeout=self.timeout, + ) + + def expulse_squatteur(adherent, chbre): + with self.conn.search(u"chbre=%s" % chbre, mode='rw')[0] as squatteur: + if self.confirm_item( + item=squatteur, + title="Chambre occupée", + defaultno=True, + text=u"L'adhérent ci-dessous occupé déjà la chambre %s :\n" % output, + text_bottom=u"\nPasser la chambre de cet adhérent en chambre inconnue ?" + ): + squatteur['chbre']=u'????' + squatteur.history_gen() + squatteur.save() + return True + else: + return False + + def set_chambre(adherent, chbre): + try: + adherent['postalAddress']=[] + adherent['chbre']=unicode(output, 'utf-8') + except UniquenessError: + if expulse_squatteur(adherent, chbre): + # La chambre est maintenant normalement libre + adherent['chbre']=unicode(output, 'utf-8') + else: + raise Continue(self_cont) + return adherent + + def todo(chbre, adherent, self_cont, success_cont): + if not output: + raise Continue(self_cont) + if output == "????": + raise ValueError("Chambre ???? invalide") + if create: + return set_chambre(adherent, chbre) + else: + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + adherent = set_chambre(adherent, chbre) + adherent.history_gen() + adherent.save() + self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output) + raise Continue(success_cont(adherent=adherent)) + + (code, output) = self.handle_dialog(cont, box) + self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont, create=create) + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, self_cont, success_cont])] + ) + + @tailcaller + def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False): + """Permet de faire déménager l'adhérent hors campus""" + if keep_machine and not keep_compte: + raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines") + elif keep_machine and keep_compte: + def box(values={}): + form = [("Adresse", 40), ("Compl. adr.", 40), ("Code postal", 7), ("Ville", 16)] + fields = [("%s :" % k, values.get(k, ""), l, 50) for k,l in form] + return self.dialog.form( + text="", + timeout=self.timeout, + height=0, width=0, form_height=0, + fields=fields, + title="Paramètres machine", + backtitle="Gestion des machines du Crans") + def todo(output, adherent, success_cont, cont): + if not create: + if self.dialog.yesno("changer l'adresse de l'adhérent pour %s ?" % ", ".join([o for o in output if o]), + title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + defaultno=True, timeout=self.timeout) == self.dialog.DIALOG_OK: + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output] + adherent['chbre']=u'EXT' + adherent.history_gen() + adherent.save() + self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines conservées") + raise Continue(success_cont(adherent=adherent)) + else: + raise Continue(cont) + else: + adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output] + adherent['chbre']=u'EXT' + return adherent + elif not keep_machine and keep_compte: + if create: + raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines") + def box(values={}): + return (self.dialog.DIALOG_OK, "") + def todo(output, adherent, success_cont, cont): + if self.confirm_item( + item=adherent, + text=u"Supprimer toutes les machines de l'adhérent ci-dessous ?\n\n", + text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", + title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + defaultno=True): + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + for machine in adherent.machines(): + with machine: + machine.delete() + adherent['chbre']=u'EXT' + adherent.history_gen() + adherent.save() + self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées") + raise Continue(success_cont(adherent=adherent)) + else: + raise Continue(cont) + elif not keep_machine and not keep_compte: + if create: + raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines et un compte") + def box(values={}): + return (self.dialog.DIALOG_OK, "") + def todo(output, adherent, success_cont, cont): + if adherent.get('solde', [0])[0] > 0: + self.dialog.msgbox("Solde de l'adhérent %s€ strictement positif, impossible de supprimer le compte\nRepasser le solde à 0€ pour supprimer le compte." % adherent.get('solde', [0])[0], + title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + width=50, timeout=self.timeout) + raise Continue(cont) + elif self.confirm_item( + item=adherent, + text=u"Supprimer toutes les machines et du compte crans de l'adhérent ci-dessous ?\n\n", + text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", + title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + defaultno=True): + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + for machine in adherent.machines(): + with machine: + machine.delete() + adherent['chbre']=u'EXT' + adherent.history_gen() + adherent.save() + # On supprime le compte crans (on essaye) + def post_deletion(proprio, cont): + if not "cransAccount" in proprio['objectClass']: + self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées") + else: + self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées et compte crans concervé") + raise Continue(cont(adherent=proprio)) + self.proprio_compte_delete(proprio=adherent, cont=TailCall(post_deletion, proprio=adherent, cont=success_cont(adherent=adherent)), force=True) + else: + raise Continue(cont) + else: + raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué") + + (code, output) = self.handle_dialog(cont, box) + self_cont = TailCall(self.adherent_chambre_ext, adherent=adherent, keep_machine=keep_machine, keep_compte=keep_compte, success_cont=success_cont, cont=cont) + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, success_cont, cont])] + ) + + def adherent_chambre(self, adherent, cont, default_item=None): + """Permet de faire déménager d'adhérent""" + a = attributs + menu_droits = { + '0' : [a.cableur, a.nounou], + '1' : [a.cableur, a.nounou], + '2' : [a.cableur, a.nounou], + '3' : [a.cableur, a.nounou], + } + menu = { + "0": {'text':"Déménagement sur le campus", 'callback':self.adherent_chambre_campus, 'help': "Déménagement vers une chambre sur le campus, on ne demande que le bâtiment et la chambre"}, + "1": {'text':"Déménagement à l'extérieur en conservant les machines", "callback": TailCall(self.adherent_chambre_ext, keep_machine=True, keep_compte=True), "help": "Pour concerver ses machines, il faut donner un adresse postale complète"}, + "2": {'text':"Départ du campus en conservant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=True), "help":"Supprime les machines mais concerve le compte crans, l'adresse passe en EXT sans plus d'information"}, + "3": {'text':"Départ du campus en supprimant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=False), "help":"Supprime les machines et le compte crans, l'adhérent reste dans la base de donnée, il est possible de mettre une redirection du mail crans vers une autre adresse dans bcfg2"}, + } + menu_order = [str(i) for i in range(4)] + def box(default_item=None): + return self.dialog.menu( + "Quel est le type du déménagement ?", + width=0, + height=0, + menu_height=0, + timeout=self.timeout, + item_help=1, + default_item=str(default_item), + title="Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) + + def todo(tag, menu, adherent, self_cont, cont): + if not tag in menu_order: + raise Continue(self_cont) + else: + raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent)) + + (code, tag) = self.handle_dialog(cont, box, default_item) + self_cont = TailCall(self.adherent_chambre, adherent=adherent, cont=cont, default_item=tag) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont, cont])] + ) + + def create_adherent(self, cont): + """Crée un adhérent et potentiellement son compte crans avec lui""" + def mycont(adherent=None, **kwargs): + if adherent: + # Une fois l'adhérent créé, on vois s'il donne sa carte étudiant et s'il adhére/prend la connexion internet + adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent) + conn_cont = TailCall(self.adherent_connexion, cont=adh_cont, adherent=adherent) + carte_cont = TailCall(self.adherent_carte_etudiant, cont=conn_cont, adherent=adherent) + etude_cont = TailCall(self.adherent_etudes, cont=carte_cont, adherent=adherent) + etude_cont(cancel_cont=etude_cont) + carte_cont(cancel_cont=etude_cont) + conn_cont(cancel_cont=carte_cont) + raise Continue(etude_cont) + else: + raise Continue(cont) + return self.adherent_personnel(cont=TailCall(mycont)) + + def delete_adherent(self, cont, del_cont, adherent=None): + """Permet la suppression d'un adhérent de la base ldap""" + if adherent is None: + adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) + + def todo(adherent): + if self.confirm_item(item=adherent, title="Voulez vous vraiement supprimer l'adhérent ?", defaultno=True): + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + adherent.delete() + self.dialog.msgbox("L'adherent a bien été supprimée", timeout=self.timeout, title="Suppression d'un adherent") + raise Continue(del_cont(proprio=None)) + else: + raise Continue(cont) + + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont(proprio=adherent), + error_cont=cont(proprio=adherent), + codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent])] + ) + + def adherent_droits(self, adherent, cont, choices_values=None): + """Gestion des droits d'un adhérent""" + def box(): + return self.dialog.checklist( + text="", + height=0, width=0, list_height=0, + choices=choices_values if choices_values else [(droit, "", 1 if droit in adherent["droits"] else 0) for droit in attributs.TOUS_DROITS], + title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), + timeout=self.timeout + ) + def todo(droits, adherent, self_cont, cont): + # Les vérifications de sécurité sont faites dans lc_ldap + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + adherent['droits']=[unicode(d) for d in droits] + adherent.history_gen() + adherent.save() + if adherent["uid"] and adherent["uid"][0] == self.conn.current_login: + self.check_ldap() + raise Continue(cont(adherent=adherent)) + + (code, droits) = self.handle_dialog(cont, box) + self_cont = TailCall(self.adherent_droits, adherent=adherent, cont=cont, choices_values=[(d, "", 1 if d in droits else 0) for d in attributs.TOUS_DROITS]) + return self.handle_dialog_result( + code=code, + output=droits, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, cont])] + ) + + diff --git a/gestion/dialog/blacklist.py b/gestion/dialog/blacklist.py new file mode 100644 index 00000000..9c73d0d9 --- /dev/null +++ b/gestion/dialog/blacklist.py @@ -0,0 +1,226 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import sys +import time +import ldap +import traceback +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') + +from gestion.affich_tools import coul +import gestion.config as config + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs + +import lc +from CPS import TailCall, tailcaller, Continue + +class Dialog(lc.Dialog): + + @tailcaller + def edit_blacklist_select(self, obj, title, cont): + """ + Permet de choisir une blackliste parmis celle de obj oubien une nouvelle blacklist. + Retourne (index, bl) où bl est un dictionnaire représentant la blackliste et index + l'index de la blackliste dans obj['blacklist'] ou new pour une nouvelle blacklist + """ + def box(): + choices = [('new', 'Ajouter une nouvelle blackliste')] + index = 0 + for bl in obj['blacklist']: + choices.append( + (str(index), + coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None, + dialog=True) + ) + ) + index+=1 + return self.dialog.menu( + "Éditer une blacklist ou en ajouter une nouvelle ?\n(les blacklistes actives apparaissent en rouge)", + width=0, + timeout=self.timeout, + height=0, + menu_height=0, + item_help=0, + title=title, + scrollbar=True, + colors=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=choices) + + def todo(tag): + if tag == 'new': + return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''} + else: + bl = {} + bl.update(obj['blacklist'][int(tag)].value) + return tag, bl + + + (code, tag) = self.handle_dialog(cont, box) + retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])] + ) + + @tailcaller + def edit_blacklist_type(self, title, cont): + """Permet de choisir un type de blackliste pour les nouvelles blacklistes""" + retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont) + + def box(): + return self.dialog.menu( + "Choisissez un type de blacklist", + width=0, + height=0, + menu_height=0, + item_help=0, + timeout=self.timeout, + title=title, + scrollbar=True, + colors=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(k,v) for (k,v) in config.blacklist_items.items()]) + + def todo(tag, retry_cont): + if tag in config.blacklist_items: + return tag + else: + self.dialog.msgbox("%s n'est pas une blacklist" % tag, timeout=self.timeout, + title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + + (code, tag) = self.handle_dialog(cont, box) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont(bl=None), + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, retry_cont])] + ) + + + def edit_blacklist(self, obj, title, update_obj, cont, bl=None, tag=None, bl_type=None, + debut=None, fin=None, comm=None): + """Pour éditer les blacklistes d'un objet lc_ldap""" + self_cont = TailCall(self.edit_blacklist, obj=obj, title=title, update_obj=update_obj, + cont=cont, bl=bl, tag=tag, bl_type=bl_type, debut=debut, fin=fin, comm=comm) + # Si bl ou tag ne sont pas définit on les demande + if bl is None or tag is None: + bl_type = None + debut = None + fin = None + comm = None + tag, bl = self.edit_blacklist_select(obj, title, cont(**{update_obj:obj})) + if bl_type is None and tag == 'new': + bl['type'] = self.edit_blacklist_type(title, self_cont(obj=obj)) + elif tag == 'new': + bl['type'] = bl_type + + # Cas de l'ajout d'un blacklist + if tag == 'new': + # Si debut n'est pas encore spécifié, on le demande + if debut is None: + debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", + cont=self_cont(bl=bl, tag=tag, bl_type=None, debut=None, fin=None, comm=None)) + debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + # Idem pour fin + if fin is None: + if self.dialog.yesno("Mettre une date de fin ?", title=title, + timeout=self.timeout) == self.dialog.DIALOG_OK: + fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", + cont=self_cont(bl=bl, tag=tag, bl_type=bl_type, + debut=None, fin=None, comm=None)) + fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + else: + fin = '-' + bl['debut']=debut + bl['fin']=fin + bl['comm']=self.get_comment(title=title, text="Commentaire ?", + cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) + if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), + title="Ajouter la blacklist ?"): + try: + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj['blacklist'].append(bl) + obj.history_gen() + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(self_cont(bl=None, obj=obj)) + # On propage les Continue + except self.error_to_raise: + raise + # En cas d'une autre erreur, on l'affiche et on retourne + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox(traceback.format_exc() if self.debug_enable else ("%s" % unicode_of_Error(e)), timeout=self.timeout, + title="Erreur rencontrée", width=73) + raise Continue(self_cont(obj=obj)) + else: + raise Continue(self_cont(bl=None, obj=obj)) + + # Cas de l'édition d'une blacklist + else: + if debut is None: + # Mettre un warning pour éditer (seulement quand debut vaut None pour ne pas le répéter à chaque fois que l'on revient en arrière par la suite + if not self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Éditer la blackliste ?"): + raise Continue(self_cont(bl=None, obj=obj)) + debut = time.localtime(bl['debut']) + debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, debut=None, fin=None, comm=None), + day=debut.tm_mday, + month=debut.tm_mon, + year=debut.tm_year, + hour=debut.tm_hour, + minute=debut.tm_min, + second=debut.tm_sec + ) + debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + bl['debut'] = debut + if fin is None: + if self.dialog.yesno("Mettre une date de fin ?", timeout=self.timeout, title=title) == self.dialog.DIALOG_OK: + if bl['fin'] == '-': + fin = time.localtime() + else: + fin = time.localtime(bl['fin']) + fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, debut=debut, fin=None, comm=None), + day=fin.tm_mday, + month=fin.tm_mon, + year=fin.tm_year, + hour=fin.tm_hour, + minute=fin.tm_min, + second=fin.tm_sec + ) + fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + else: + fin = '-' + bl['fin'] = fin + bl['comm']=self.get_comment(title=title, text="Commentaire ?", init=bl['comm'], cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) + if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Modifier la blacklist ?"): + try: + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj['blacklist'][int(tag)]=bl + obj.history_gen() + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(self_cont(bl=None, obj=obj)) + # On propage les Continue + except self.error_to_raise: + raise + # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox(traceback.format_exc() if self.debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout, title="Erreur rencontrée", width=73) + raise Continue(self_cont) + else: + raise Continue(self_cont(bl=None, obj=obj)) diff --git a/gestion/dialog/certificat.py b/gestion/dialog/certificat.py new file mode 100644 index 00000000..15b11317 --- /dev/null +++ b/gestion/dialog/certificat.py @@ -0,0 +1,534 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import os +import sys +import ssl +import time +import inspect +import tempfile +import traceback +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') +from OpenSSL import crypto, SSL + +from gestion.cert_utils import createCertRequest + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs + +import lc +from CPS import TailCall, tailcaller, Continue + +class Dialog(lc.Dialog): + def certificat_tlsa(self, certificat, cont, values=None): + """Menu d'éditions des paramètres TLSA d'un certificat""" + separateur = ' ' + form = { + 'Type de certificat' : {'ldap_name' : 'certificatUsage', 'text':"".join(str(s) for s in certificat.get('certificatUsage', [])), 'len':1}, + 'Type de correspondance' : {'ldap_name' : 'matchingType', 'text':"".join(str(s) for s in certificat.get('matchingType', [])), 'len':1}, + 'Ports TCP' : {'ldap_name' : 'portTCPin', 'text':separateur.join(str(p) for p in certificat.get('portTCPin', [])), 'len':30}, + 'Ports UDP' : {'ldap_name' : 'portUDPin', 'text':separateur.join(str(p) for p in certificat.get('portUDPin', [])), 'len':30}, + } + form_order = ['Type de certificat', 'Type de correspondance', 'Ports TCP', 'Ports UDP'] + def box(fields_values=None): + fields = [("%s : " % k, form[k]['text'], form[k]['len'] + 1, form[k]['len']) for k in form_order] + return self.dialog.form( + text="""Type de certificat : Type de correspondance : + * 0 - CA pinning * 0 - certificat entier + * 1 - Cert pinning * 1 - sha256 + * 2 - CA auto signé * 2 - sha512 + * 3 - Cert autosigné""", + no_collapse=True, + height=0, width=0, form_height=0, + timeout=self.timeout, + fields=fields_values if fields_values else fields, + title="Paramètres TLS d'un certificat de la machine %s" % certificat.machine()['host'][0], + backtitle="Gestion des certificats des machines du Crans") + + def todo(form, values, certificat, cont): + if not values['certificatUsage'] in ['0', '1', '2', '3']: + raise ValueError("""Type de certificat invalide : +les valeurs valident sont : + * 0 pour du CA pinning + (le certificat doit être une autorité de certification valide) + * 1 pour du certificat pinning + (le certificat doit déjà être validé par les navigateur) + * 2 pour ajouter un CA + (pour les autorité de certification autosigné) + * 3 pour les certificats autosigné""" +) + if not values['matchingType'] in ['0', '1', '2']: + raise ValueError("""Type de correspondance invalide : +les valeurs valident sont : + * 0 le certificat sera mis entièrement dans le dns + * 1 le sha256 du certificat sera mis dans le dns + * 2 le sha512 du certificat sera mis dans le dns""" +) + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + if "TLSACert" in certificat['objectClass']: + certificat['certificatUsage'] = unicode(values['certificatUsage']) + certificat['matchingType'] = unicode(values['matchingType']) + else: + certificat.tlsa(values['certificatUsage'], values['matchingType']) + certificat['portTCPin'] = [unicode(s, 'utf-8') for s in values['portTCPin'].split(separateur) if s] + certificat['portUDPin'] = [unicode(s, 'utf-8') for s in values['portUDPin'].split(separateur) if s] + certificat.history_gen() + certificat.save() + raise Continue(cont(certificat=certificat)) + + (code, output) = self.handle_dialog(cont, box, values) + values = dict(zip([form[k]['ldap_name'] for k in form_order], output)) + fields_values = [("%s : " % k, values.get(form[k]['ldap_name'], ""), form[k]['len'] + 1, form[k]['len']) for k in form_order] + self_cont=TailCall(self.certificat_tlsa, certificat=certificat, cont=cont, values=fields_values) + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [form, values, certificat, cont])] + ) + + def create_certificat(self, cont, machine=None, certificat=None): + """Permet d'ajouter un certificat à une machine à partir du PEM du certificat""" + if machine is None and certificat is None: + raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") + # input multiline en utilisant un editbox + def box(): + fp, path = tempfile.mkstemp() + os.close(fp) + cmd = ['--editbox', path, "0", "0"] + (code, output) = self.dialog._perform(*(cmd,), + no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic + backtitle="Appuyez sur CTRL+MAJ+V pour coller", + timeout=self.timeout, + title="Création d'un certificat, entrez le PEM du certificat") + os.remove(path) + if code == self.dialog.DIALOG_OK: + return code, output + else: + return code, None + + def todo(machine, certificat, cont): + if certificat: + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat['certificat'] = unicode(pem.strip(), 'utf-8') + certificat.history_gen() + certificat.save() + else: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['certificat'] = unicode(pem.strip(), 'utf-8') + certificat.create() + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + + (code, pem) = self.handle_dialog(cont, box) + self_cont = TailCall(self.create_certificat, machine=machine, certificat=certificat, cont=cont) + return self.handle_dialog_result( + code=code, + output=pem, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, cont])] + ) + + + + + def create_privatekey(self, cont, machine=None, certificat=None, imp=False, size=4096): + """Permet de générer ou importer une clef privée à une machine""" + if machine is None and certificat is None: + raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") + # input multiline en utilisant un editbox + def box(): + fp, path = tempfile.mkstemp() + os.close(fp) + cmd = ['--editbox', path, "0", "0"] + (code, output) = self.dialog._perform(*(cmd,), + no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic + backtitle="Appuyez sur CTRL+MAJ+V pour coller", + timeout=self.timeout, + title="Création d'un certificat, entrez le PEM du certificat") + os.remove(path) + if code == self.dialog.DIALOG_OK: + return code, output + else: + return code, None + + def todo(machine, certificat, pem, imp, size, cont): + if not imp: + if not machine: + machine=certificat.machine() + if "machineCrans" in machine['objectClass']: + passphrase = secrets.get('privatekey_passphrase') + else: + self.dialog.msgbox("Vous aller être inviter à entrez un mot de passe. Ce mot de passe est utilisé pour chiffrer la clef privée qui va être générée dans la base de donnée du crans.\n\nCe mot de passe n'est pas conservé, sous quelque forme que se soit par le crans.\nAussi, en cas de perte, la clef privée deviendrait inutilisable.\n Pensez à le sauvegarder quelque part", + title="Génération d'une clée privée", + width=70, timeout=self.timeout, + height=12) + passphrase = self.get_password(cont) + self.dialog.infobox("Génération d'une clef privée RSA de taille %s en cours.\nMerci de patienter" % size) + pem = crypto.PKey() + pem.generate_key(crypto.TYPE_RSA, size) + pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, pem, "des3", passphrase) + elif not pem.startswith("-----BEGIN ENCRYPTED PRIVATE KEY-----"): + raise ValueError("On n'accepte que des clef chiffrée PKCS#8 en PEM. Donc la clef doit commencer par -----BEGIN ENCRYPTED PRIVATE KEY-----") + if certificat: + if "privatekey" in certificat: + raise ValueError("Il y a déjà une clef privée, merci d'annuler") + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat.private(pem, encrypted=True) + certificat.history_gen() + certificat.save() + self.dialog.msgbox("Clef privée bien ajouté", timeout=self.timeout, title="Ajout d'une clef privée") + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + else: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['hostCert']=unicode(machine['host'][0]) + certificat.private(pem, encrypted=True) + certificat.create() + self.dialog.msgbox("Clef privée créée avec succès", timeout=self.timeout, title="Création d'une clef privée") + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + + if imp: + (code, pem) = self.handle_dialog(cont, box) + else: + (code, pem) = (self.dialog.DIALOG_OK, "") + self_cont = TailCall(self.create_privatekey, machine=machine, certificat=certificat, cont=cont, imp=imp, size=size) + return self.handle_dialog_result( + code=code, + output=pem, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, imp, size, cont])] + ) + + def delete_certificat(self, certificat, cont): + """Supprime un certificat""" + def todo(certificat, cont): + if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"): + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat.delete() + self.dialog.msgbox("Le certificat a bien été supprimé", timeout=self.timeout, title="Suppression d'un certificat") + raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True))) + else: + raise Continue(cont(certificat=certificat)) + + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont, + error_cont=TailCall(self.delete_certificat, certificat=certificat, cont=cont), + codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, cont])] + ) + + + def gen_csr(self, certificat, cont): + """Permet de générer un csr à partir de la clef privée du certificat""" + def todo(certificat, self_cont, cont): + if certificat['encrypted']: + if "machineCrans" in certificat.machine()["objectClass"]: + passphrase = secrets.get('privatekey_passphrase') + else: + self.dialog.msgbox("Mercie de fournir le mot de passe chiffrant la clef privée.\nIl a été choisis lors de la création de la clef.", + title="Génération d'un CSR", + width=70, + height=10, timeout=self.timeout) + passphrase = self.get_password(cont, confirm=False) + else: + passphrase = None + + try: + if passphrase: + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0]), passphrase) + else: + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0])) + except crypto.Error as e: + if len(e.message) > 2 and len(e.message[2]) > 2 and e.message[2][2] == 'bad password read': + self.dialog.msgbox("Mauvais mot de passe", timeout=self.timeout) + raise Continue(self_cont) + else: + raise + + req = createCertRequest(pkey, + digest="sha1", + subjectAltName=[str(host) for host in certificat['hostCert'][1:]], + C=u"FR", + ST=u"Ile de France", + L=u"Cachan", + O=u"Association Cachan Réseaux A Normal SUP (C.R.A.N.S)", + OU=u"Crans", + CN=unicode(certificat['hostCert'][0]), + ) + csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat['csr']=unicode(csr) + certificat.history_gen() + certificat.save() + self.handle_dialog(cont, box, csr) + if self.dialog.yesno("Remplacer le certificat actuel ?", timeout=self.timeout) == self.dialog.DIALOG_OK: + return self.create_certificat(certificat=certificat, cont=cont(certificat=certificat)) + else: + raise Continue(cont(certificat=certificat)) + + self_cont = TailCall(self.gen_csr, certificat=certificat, cont=cont) + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, self_cont, cont])] + ) + + def get_certificat(self, certificat, cont, privatekey=False, csr=False): + """Permet d'afficher le certificat courant""" + def box(text): + fp, path = tempfile.mkstemp() + os.write(fp, text) + os.close(fp) + self.dialog.textbox(filename=path, height=0, width=0, + backtitle="Appuyez sur CTRL+MAJ+V pour coller", + title="Récupération d'un certificat", + no_mouse=True, timeout=self.timeout,) + os.remove(path) + return + if privatekey: + self.handle_dialog(cont, box, unicode(certificat['privatekey'][0])) + elif csr: + self.handle_dialog(cont, box, unicode(certificat['csr'][0])) + else: + self.handle_dialog(cont, box, unicode(ssl.DER_cert_to_PEM_cert(str(certificat['certificat'][0])))) + raise Continue(cont) + + def create_csr(self, cont, machine=None, certificat=None): + """Permet d'ajouter un csr à une machine à partir du PEM du csr""" + if machine is None and certificat is None: + raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") + # input multiline en utilisant un editbox + def box(): + fp, path = tempfile.mkstemp() + os.close(fp) + cmd = ['--editbox', path, "0", "0"] + (code, output) = self.dialog._perform(*(cmd,), + no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic + backtitle="Appuyez sur CTRL+MAJ+V pour coller", + timeout=self.timeout, + title="Création d'un certificat, entrez le PEM du certificat") + os.remove(path) + if code == self.dialog.DIALOG_OK: + return code, output + else: + return code, None + + def todo(machine, certificat, pem, cont): + if certificat: + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat['csr'] = unicode(pem.strip(), 'utf-8') + certificat.history_gen() + certificat.save() + else: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['hostCert']=unicode(machine['host'][0]) + certificat['csr'] = unicode(pem.strip(), 'utf-8') + certificat.create() + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + + (code, pem) = self.handle_dialog(cont, box) + self_cont = TailCall(self.create_csr, machine=machine, certificat=certificat, cont=cont) + return self.handle_dialog_result( + code=code, + output=pem, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, cont])] + ) + + def modif_machine_certificat(self, machine, cont, tag=None, certificat=None): + """Permet l'édition d'un certificat d'une machine""" + self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat) + if certificat is None: + certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont) + if certificat_index == 'new': + raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont)) + elif certificat_index == 'priv': + raise Continue(TailCall(self.create_privatekey, machine=machine, cont=self_cont)) + elif certificat_index == 'csr': + raise Continue(TailCall(self.create_csr, machine=machine, cont=self_cont)) + certificat = machine.certificats()[certificat_index] + a = attributs + menu_droits = { + 'Hostname':[a.parent, a.nounou], + 'AddPrivateKey':[a.parent, a.nounou], + 'AddCertificate':[a.parent, a.nounou], + 'SetCertificate':[a.parent, a.nounou], + 'TLSA':[a.parent, a.nounou], + 'Autre':[a.nounou], + 'GetPriv':[a.parent, a.nounou], + 'GetCert':[a.parent, a.nounou, a.cableur], + 'GetCSR':[a.parent, a.nounou, a.cableur], + 'GenCSR':[a.parent, a.nounou], + 'Remarque':[a.parent, a.nounou, a.cableur], + 'Supprimer':[a.parent, a.nounou], + } + menu = { + 'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "help":'Il doivent être inclus dans les host et hostAlias de la machine parente', "attribut":attributs.hostCert}, + 'AddPrivateKey' : {'text': 'Ajouter la clef privée', 'help':'La clef doit être obligatoirement chiffrée avant envoi', "callback":TailCall(self.create_privatekey, imp=True)}, + 'AddCertificate' : {'text': 'Ajouter un certificat X509', 'help':'', "callback":self.create_certificat}, + 'SetCertificate' : {'text': 'Remplacer le certificat X509', 'help':'', "callback":self.create_certificat}, + 'TLSA' : {'text':"Paramètres pour les champs dns TLSA", 'help':'Permet de configurer DANE pour le certificat X509', "callback":self.certificat_tlsa}, + 'Autre': {'text' : "Modifier les attribut booléen comme revocked", 'help':'', "callback":self.modif_certificat_boolean}, + 'GetPriv' : {'text' : 'Récupérer la clef privée', 'help':"Affiche la clef privée telle qu'elle est dans la base de donnée, c'est à dire chiffrée", 'callback':TailCall(self.get_certificat, privatekey=True)}, + 'GetCert' : {'text' : 'Récupérer le certificat', 'help':"Affiche le certificat au format PEM", 'callback':self.get_certificat}, + 'GetCSR' : {'text' : 'Récupérer la requête de signature de certificat', 'help':"Affiche le CSR au format PEM", 'callback':TailCall(self.get_certificat, csr=True)}, + 'GenCSR' : {'text' : 'Générer un CSR, puis remplacer le certificat', 'help':'Généré à partir de la clef privée. Les noms (CN et subjectAltName) sont pris à partir de Hostname (attribut hostCert)', "callback":self.gen_csr}, + 'Remarque' : {'text': 'Mettre des remarques', 'help':'La première apparait dans la liste des certificats', 'attribut':attributs.info}, + 'Supprimer' : {'text' : "Supprimer le certificat", 'help':'', "callback":self.delete_certificat}, + } + if "privateKey" in certificat["objectClass"]: + menu + menu_order = ['Hostname'] + if not "privateKey" in certificat['objectClass']: + menu_order.extend(['AddPrivateKey', 'SetCertificate']) + if not "x509Cert" in certificat['objectClass']: + menu_order.extend([ 'AddCertificate']) + if "x509Cert" in certificat['objectClass']: + menu_order.extend(['TLSA', 'Autre', 'GetCert']) + if certificat['csr']: + menu_order.extend(['GetCSR']) + if "privateKey" in certificat['objectClass']: + if attributs.nounou in self.conn.droits or machine.dn.startswith(self.conn.dn): + menu_order.extend(['GetPriv']) + menu_order.extend(['GenCSR']) + menu_order.extend(['Remarque', 'Supprimer']) + def box(default_item=None): + text="Certificat de %s, xid=%s :\n" % (certificat['hostCert'][0], certificat['xid'][0]) + if "x509Cert" in certificat['objectClass']: + text += " * Certificat N°0x%X émis par %s, valable du %s au %s\n" % ( + int(str(certificat['serialNumber'][0])), + certificat['issuerCN'][0], + time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))), + time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0]))) + ) + if "privateKey" in certificat['objectClass']: + text += " * Clef privée\n" + if certificat['csr']: + text += " * Requête de signature de certificat\n" + if certificat['info']: + text += str(certificat['info'][0]) + return self.dialog.menu( + text, + width=0, + height=0, + menu_height=0, + item_help=1, + timeout=self.timeout, + default_item=str(default_item), + title="Modification des certificats de %s" % certificat.machine()['host'][0], + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(key, menu[key]['text'], menu[key]['help']) for key in menu_order if self.has_right(menu_droits[key], certificat)]) + + def todo(tag, menu, certificat, self_cont): + if not tag in menu_order: + raise Continue(self_cont(certificat=certificat)) + else: + if 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], certificat=certificat, cont=self_cont(certificat=certificat, tag=tag))) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_certificat_attributs, certificat=certificat, cont=self_cont(certificat=certificat, tag=tag), attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) + cancel_cont = cont(machine=machine) if certificat is None else self_cont(machine=certificat.machine(), certificat=None, tag=tag) + (code, tag) = self.handle_dialog(cancel_cont, box, tag) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cancel_cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, certificat, self_cont])] + ) + + @tailcaller + def edit_certificat_select(self, machine, title, cont): + """Permet de choisir un certificat existant ou nouveau d'une machine""" + a = attributs + menu_droits = { + 'new':[a.parent, a.nounou], + 'priv':[a.parent, a.nounou], + 'csr':[a.parent, a.nounou], + } + menu = { + 'new':'Ajouter un nouveau certificat', + 'priv':'Générer une nouvelle clef privée', + 'csr':'Ajouter une nouvelle requête de certificat', + } + menu_order = ['new', 'priv', 'csr'] + menu_special = ['new', 'priv', 'csr'] + def box(default_item=None): + index=0 + choices = [] + for key in menu_order: + if self.has_right(menu_droits[key], machine): + choices.append((key, menu[key])) + for cert in machine.certificats(): + if cert['info']: + item = str(cert['info'][0]) + elif "x509Cert" in cert['objectClass']: + item = "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0])))) + elif "privateKey" in cert['objectClass']: + item = "Clef privée de %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0]) + elif cert['csr']: + item = "Requête de signature de certificat pour %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0]) + choices.append((str(index), item)) + index+=1 + return self.dialog.menu( + "Modifier ou ajouter un certificat ?", + width=0, + height=0, + timeout=self.timeout, + menu_height=0, + item_help=0, + title="Modification des certificats de %s" % machine['host'][0], + scrollbar=True, + default_item=str(default_item), + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=choices) + + def todo(tag): + if tag in ['new', 'priv', 'csr']: + return tag + else: + return int(tag) + + (code, tag) = self.handle_dialog(cont, box) + retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont(machine=machine), + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])] + ) + + + def modif_certificat_boolean(self, certificat, cont): + """Juste un raccourci vers edit_boolean_attributs spécifique aux certificats""" + a = attributs + attribs = [a.revocked] + return self.edit_boolean_attributs( + obj=certificat, + attribs=attribs, + title="Édition des attributs booléen d'un certificat de la machine %s" % certificat.machine()['host'][0], + update_obj='certificat', + cont=cont) + + def modif_certificat_attributs(self, certificat, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux certificats""" + return self.edit_attributs(obj=certificat, update_obj='certificat', attr=attr, title="Modification d'un certificat de la machine %s" % certificat.machine()['host'][0], cont=cont) + diff --git a/gestion/dialog/club.py b/gestion/dialog/club.py new file mode 100644 index 00000000..efca21af --- /dev/null +++ b/gestion/dialog/club.py @@ -0,0 +1,33 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import sys +import time +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs + +import proprio +from CPS import TailCall, tailcaller, Continue + +class Dialog(proprio.Dialog): + def create_club(self, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def delete_club(self, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def modif_club(self, cont, club=None): + if club is None: + club = self.select(["club"], "Recherche d'un club pour modification", disable_field=["Prénom", "Téléphone"], cont=cont) + self.dialog.msgbox("todo", width=0, height=0) + return cont(proprio=club) diff --git a/gestion/dialog/lc.py b/gestion/dialog/lc.py new file mode 100644 index 00000000..34f61ad9 --- /dev/null +++ b/gestion/dialog/lc.py @@ -0,0 +1,465 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import sys +import time +import ldap +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') +from pythondialog import Dialog +from pythondialog import DialogError, DialogTerminatedBySignal + +from gestion.affich_tools import get_screen_size, coul + +import lc_ldap.shortcuts +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs +import lc_ldap.printing as printing + +import CPS +from CPS import TailCall, tailcaller, Continue, TailCaller + + +class Dialog(CPS.Dialog): + def __init__(self, debug_enable=False, ldap_test=False): + super(Dialog, self).__init__(debug_enable=debug_enable) + # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog + printing.template(dialog=True) + self.ldap_test = ldap_test + self.check_ldap() + + def has_right(self, liste, obj=None): + """Vérifie que l'un des droits de l'utilisateur courant est inclus dans list""" + if not isinstance(liste, list): + liste = [liste] + if obj: + droits = obj.rights() + else: + droits = self.conn.droits + for d in liste: + if d in droits: + return True + return False + + def check_ldap(self): + """Se connecte à la base ldap et vérifie les droits de l'utilisateur courant""" + self.check_ldap_last = time.time() + + # S'il y a --test dans les argument, on utilise la base de test + if self.ldap_test: + self.conn = lc_ldap.shortcuts.lc_ldap_test() + else: + # On ouvre une connexion lc_ldap + self.conn = lc_ldap.shortcuts.lc_ldap_admin() + + # On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits) + luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % self.conn.current_login) + if not luser: + sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % self.conn.current_login) + sys.exit(1) + self.conn.droits = [str(d) for d in luser[0]['droits']] + self.conn.dn = luser[0].dn + + # Si un nom d'utilisateur est donné sur la ligne de commande + # et qu'on a les droits nounou, on l'utilise + if sys.argv[1:] and attributs.nounou in self.conn.droits: + for u in sys.argv[1:]: + luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % u) + if luser: + self.conn.current_login = u + self.conn.droits = [str(d) for d in luser[0]['droits']] + self.conn.dn = luser[0].dn + break + a = attributs + allowed_right = [a.cableur, a.tresorier, a.bureau, a.nounou, a.imprimeur] + for droit in allowed_right: + if droit in self.conn.droits: + break + else: + sys.stderr.write( + u"%s ne possède aucun des droits :\n * %s\nnécessaire à utiliser ce programme\n" % ( + self.conn.current_login, + '\n * '.join(allowed_right) + ) + ) + sys.exit(1) + + @property + def dialog(self): + """ + Renvois l'objet dialog. De plus renouvelle régulièrement la connexion à la base ldap + """ + # Tous les self.timeout, on refraichie la connexion ldap + if time.time() - self.check_ldap_last > self.timeout: + self.check_ldap() + return super(Dialog, self).dialog + + + + def edit_boolean_attributs(self, obj, attribs, title, update_obj, cont, values={}): + """ + Permet d'éditer des attributs booléen de l'objet obj listé dans attribs. + update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié + """ + # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut + missing = { + 'default' : False, # par défaut, on dit que c'est False + attributs.dnsIpv6 : True # pour dnsIpv6, c'est True + } + choices = [(a.ldap_name, a.legend, 1 if values.get(a.ldap_name, obj[a.ldap_name][0] if obj[a.ldap_name] else missing.get(a, missing['default'])) else 0) for a in attribs] + def box(): + return self.dialog.checklist("Activier ou désactiver les propriétés suivantes", + height=0, + width=0, + timeout=self.timeout, + list_height=7, + choices=choices, + title=title) + + def todo(values, obj, attribs, cont): + # On met à jour chaque attribut si sa valeur à changé + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + for a in attribs: + if obj[a.ldap_name] and obj[a.ldap_name][0] != values[a.ldap_name]: + obj[a.ldap_name]=values[a.ldap_name] + elif not obj[a.ldap_name] and missing.get(a, missing['default']) != values[a.ldap_name]: + obj[a.ldap_name]=values[a.ldap_name] + obj.history_gen() + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(**{update_obj:obj})) + + (code, output) = self.handle_dialog(cont, box) + + # On transforme la liste des cases dialog cochée en dictionnnaire + values = dict((a.ldap_name, a.ldap_name in output) for a in attribs) + + # Une continuation que l'on suivra si quelque chose se passe mal + retry_cont = TailCall(self.edit_boolean_attributs, obj=obj, update_obj=update_obj, attribs=attribs, title=title, cont=cont, values=values) + + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [values, obj, attribs, cont])] + ) + + + def edit_attributs(self, obj, attr, title, update_obj, cont, tag=None, values=None): + """ + Permet d'éditer la liste d'attribut attr de l'objet obj. + update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié + """ + + # Il n'y a pas inputmenu dans la lib dialog, du coup, on traite les arguments à la main. + # Ça reste relativement acceptable comme on utilise la fonction de la lib pour appeler dialog + def box(values, default_tag): + cmd = ['--inputmenu', "Édition de l'attribut %s :" % attr, "0", "0", "20"] + index=0 + for value in values: + cmd.extend([str(index), str(value)]) + index+=1 + cmd.extend(['new', '']) + (code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, default_item=str(default_tag)) + if code == self.dialog.DIALOG_EXTRA: + output = output.split(' ', 2)[1:] + else: + output = '' + return (code, output) + + def todo_extra(output, values, retry_cont): + tag, value = output + if tag == 'new': + if value: + values.append(value) + elif value == '': + values.pop(int(tag)) + else: + values[int(tag)] = value + raise Continue(retry_cont(values=values, tag=tag)) + + def todo(obj, values, cont): + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj[attr] = [unicode(value, 'utf-8') for value in values] + obj.history_gen() + obj.save() + raise Continue(cont(**{update_obj:obj})) + + if values is None: + values = [str(a) for a in obj[attr]] + retry_cont = TailCall(self.edit_attributs, obj=obj, attr=attr, title=title, update_obj=update_obj, cont=cont, values=values) + (code, output) = self.handle_dialog(cont, box, values, tag) + + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[ + ([self.dialog.DIALOG_OK], todo, [obj, values, cont]), + ([self.dialog.DIALOG_EXTRA], todo_extra, [output, values, retry_cont]), + ] + ) + + + def search(self, objectClassS, title, values={}, cont=None, disable_field=[]): + """ + Rechercher des adhérents ou des machines dans la base ldap + retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés) + La fonction est découpé en trois partie : + * affichage dialog et récupération du résultat + * construction de filtres de recherche ldap et recherches ldap + * filtre sur les résultats des recherches ldap + """ + select_dict = { + #label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars + 'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]}, + 'Prénom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]}, + 'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]}, + 'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]}, + 'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]}, + 'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 40, 00]}, + # seconde colone + 'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]}, + 'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]}, + 'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]}, + 'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]}, + 'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]}, + } + # On a besoin de l'ordre pour récupérer les valeurs ensuite + select_adherent = ['Nom', 'Prénom', 'Téléphone', 'Chambre', 'aid', 'mail'] + select_machine = ['Host', 'Mac', 'IP', 'mid'] + if 'club' in objectClassS and not 'adherent' in objectClassS: + select_dict['cid']=select_dict['aid'] + select_dict['cid']['ldap']='cid' + select_dict['cid']['params'][2]=values.get('cid', "") + select_adherent[select_adherent.index('aid')]='cid' + def box(): + # On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone + cmd = ["--mixedform", "Entrez vos paramètres de recherche", '0', '0', '0'] + for key in select_adherent: + cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0']) + cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']] + ['2']) + for key in select_machine: + cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0']) + cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0', '2' ]) + # On utilise quand même la fonction de la bibliothèques pour passer les arguments + (code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, backtitle="Entrez vos paramètres de recherche") + if output: + return (code, output.split('\n')[:-1]) + else: # empty selection + return (code, []) + + (code, dialog_values) = self.handle_dialog(cont, box) + # Si il a appuyé sur annuler ou sur escape, on saute sur la continuation + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + else: + # Transformation de la liste des valeures entrée en dictionnnaire + dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) + ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()]) + + # Construction des filtres ldap pour les adhérents et les machines + filter_adherent = [] + filter_machine = [] + for (key, value) in dialog_values.items(): + if value: + if key in select_adherent: + filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + elif key in select_machine: + filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + if filter_adherent: + filter_adherent=u"(&%s)" % "".join(filter_adherent) + if filter_machine: + filter_machine=u"(&%s)" % "".join(filter_machine) + + # Récupération des adhérents et des machines + adherents=self.conn.search(filter_adherent) if filter_adherent else [] + machines=self.conn.search(filter_machine) if filter_machine else [] + + # Filtrage des machines en fonction des adhérents + if filter_adherent: + if filter_machine: + # Si on filtre sur des adhérent et des machines, on calcule l'intersection + adherents_dn = set([a.dn for a in adherents]) + machines_f = [m for m in machines if m.parent_dn in adherents_dn] + else: + # Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés + machines_f = [m for a in adherents for m in a.machines()] + else: + # Sinon si on filtre seulement sur des machines + machines_f = machines + + # Filtrage des adhérents en fonction des machines + if filter_machine: + if filter_adherent: + # Si on filtre sur des adhérents et des machines, on calcule l'intersection + machines_dn = set([m.parent_dn for m in machines]) + adherents_f = [a for a in adherents if a.dn in machines_dn] + else: + # Sinon on récupères les proprios des machines trouvées + adherents_f = [m.proprio() for m in machines] + else: + # Sinon si on filtre seulement sur des adhérents + adherents_f = adherents + + # On filtre sur les objectClassS + return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] + + @tailcaller + def select_one(self, items, title, text="Que souhaitez vous faire ?", default_item=None, cont=None): + """Fait selectionner un item parmis une liste d'items à l'utisateur""" + + def box(items, default_item): + choices=[] + olist={} + count = 0 + + # On sépare les item d'items en fonction de leur type + for o in items: + olist[o.__class__] = olist.get(o.__class__, []) + [o] + classes = olist.keys() + classes.sort() + default_tag = items.index(default_item) if default_item in items else default_item + + # On se débrouille pour faire corresponde l'ordre d'affichache des objets + # et leur ordre dans la liste items. On donne la largeur de l'affichage à la main + # pour prendre en compte la largeur du widget dialog + del items[:] # On vide la liste pour la modifier en place + items_id = {} + (line, col) = get_screen_size() + for c in classes: + items.extend(olist[c]) + items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n') + choices.append(("", str(items_s[0]))) + next=1 + if items_s[next:]: + choices.append(("", str(items_s[next]))) + next+=1 + for i in items_s[next:]: + if i: # on zap les lignes vides + choices.append((str(count), str(i))) + count+=1 + # On laisse une ligne vide pour séparer les listes d'objets de type différent + choices.append(("", "")) + + return self.dialog.menu( + text, + width=0, + height=0, + menu_height=0, + timeout=self.timeout, + item_help=0, + default_item=str(default_tag), + title=title, + scrollbar=True, + choices=choices, + colors=True) + + + def todo(tag, items, title, cont, retry_cont): + # Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose + if not tag: + self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", timeout=self.timeout, title="Sélection", width=0, height=0) + raise Continue(retry_cont) + # Sinon on retourne l'item choisis + elif self.confirm_item(items[int(tag)], title): + return items[int(tag)] + else: + raise Continue(cont) + + (code, tag) = self.handle_dialog(cont, box, items, default_item) + retry_cont = TailCall(self.select_one, items=items, title=title, default_item=tag, cont=cont) + + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, items, title, cont, retry_cont])] + ) + + + def confirm_item(self, item, title, defaultno=False, text='', text_bottom="", **params): + """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" + return self.dialog.yesno( + text + printing.sprint(item, **params) + text_bottom, + no_collapse=True, + colors=True, + no_mouse=True, + timeout=self.timeout, + title=title, + defaultno=defaultno, + width=0, height=0, + backtitle="Appuyez sur MAJ pour selectionner du texte" + ) == self.dialog.DIALOG_OK + + def display_item(self, item, title, **params): + """Affiche un item""" + return self.dialog.msgbox( + printing.sprint(item, **params), + no_collapse=True, + colors=True, + timeout=self.timeout, + title=title, + width=0, height=0, + backtitle="Appuyez sur MAJ pour selectionner du texte" + ) + + # On a besoin du décorateur ici car select va retourner un item après avoir + # possblement traiter plusieurs tailcall + @tailcaller + def select(self, objectClassS, title, values={}, cont=None, disable_field=[]): + """Permet de choisir un objet adhérent ou machine dans la base ldap""" + try: + # On fait effectuer une recherche à l'utilisateur + values, items = self.search(objectClassS, title, values, cont=cont, disable_field=disable_field) + # S'il n'y a pas de résultas, on recommence + if not items: + self.dialog.msgbox("Aucun Résultat", timeout=self.timeout, title="Recherche", width=0, height=0) + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) + # S'il y a plusieurs résultats + elif len(items)>1: + # On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select + return self.select_one(items, title, cont=TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) + # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur + elif len(items) == 1: + item=items[0] + # On fait confirmer son choix à l'utilisateur + if self.confirm_item(item, title): + return item + else: + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) + except self.error_to_raise: + raise + except Exception as e: + self.dialog.msgbox(traceback.format_exc() if self.debug_enable else "%r" % e, timeout=self.timeout, title="Erreur rencontrée", width=0, height=0) + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) + + +@TailCaller +def main(gc, cont=None): + """ + Fonction principale gérant l'appel au menu principal, + le timeout des écrans dialog et les reconnexion a ldap en cas de perte de la connexion + """ + while True: + try: + # tant que le timeout est atteint on revient au menu principal + gc.menu_principal() + except DialogError as e: + # Si l'erreur n'est pas due à un timeout, on la propage + if time.time() - gc.dialog_last_access < gc.timeout: + raise + # Si on perd la connexion à ldap, on en ouvre une nouvelle + except ldap.SERVER_DOWN: + if gc.dialog.pause(title="Erreur", duration=10, height=9, width=50, text="La connection au serveur ldap à été perdue.\nTentative de reconnexion en cours…") == gc.dialog.DIALOG_OK: + gc.check_ldap() + else: + return diff --git a/gestion/dialog/machine.py b/gestion/dialog/machine.py new file mode 100644 index 00000000..e2df6d61 --- /dev/null +++ b/gestion/dialog/machine.py @@ -0,0 +1,359 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import sys +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs + +import certificat +import blacklist +from CPS import TailCall, tailcaller, Continue + +class Dialog(certificat.Dialog, blacklist.Dialog): + def machine_information(self, cont, machine=None, objectClass=None, proprio=None, realm=None, fields_values=None): + """ + Permet de modifier une machine si elle est fournit par le paramètre machine + sinon, crée une machine à partir de proprio, objectClass et realm. + Si on ne fait qu'éditer une machine, proprio, objectClass et realm sont ignoré + D'une machinère générale, il faudrait mettre ici tous les attributs single value + et les multivalué que l'on peut simplement représenter de façon textuelle avec + un séparateur. + Pour le moment il y a : + * host + * macAddress + * ipHostNumber + * port(TCP|UDP)(in|out) + """ + a = attributs + # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant + to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15), + (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50), + (a.portUDPin, 50) + ] + + # Quel séparateur on utilise pour les champs multivalué + separateur = ' ' + + def box(): + if machine: + attrs = dict((k,[str(a) for a in at]) for k,at in machine.items()) + else: + attrs = {} + + fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display] + + return self.dialog.form( + text="", + timeout=self.timeout, + height=0, width=0, form_height=0, + fields=fields_values if fields_values else fields, + title="Paramètres machine", + backtitle="Gestion des machines du Crans") + + def check_host(host, objectClass): + # Si c'est une machine wifi, host doit finir par wifi.crans.org + if "machineWifi" == objectClass or 'borneWifi' == objectClass: + hostend = ".wifi.crans.org" + # Si c'est une machine wifi, host doit finir par crans.org + elif "machineFixe" == objectClass: + hostend = ".crans.org" + # Si l'object class est machineCrans, pas de vérification + elif "machineCrans" == objectClass: + return host + # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer + # plus finement fonction des droits de self.conn.droits + else: + raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass) + + if not host.endswith(hostend) and not '.' in host: + host = "%s.wifi.crans.org" % host + elif host.endswith(hostend) and '.' in host[:-len(hostend)]: + raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) + elif not host.endswith(hostend) and '.' in host: + raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) + + return host + + def modif_machine(machine, attrs): + with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: + for (key, values) in attrs.items(): + machine[key]=values + machine.validate_changes() + machine.history_gen() + machine.save() + return machine + + def create_machine(proprio, realm, attrs): + # Dans ce cas, on a besoin d'un proprio et d'un realm pour déterminer le rid + if proprio is None or realm is None: + raise EnvironmentError("On essaye de créer une machine mais proprio ou realm vaut None") + ldif = { + 'macAddress': ['%s' % attrs['macAddress']], + 'host': ['%s' % attrs['host']] + } + with self.conn.newMachine(proprio.dn, realm, ldif) as machine: + for (key, values) in attrs.items(): + machine[key]=values + if attributs.ipsec in machine.attribs: + machine[attributs.ipsec.ldap_name]=attributs.ipsec.default + machine.validate_changes() + if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"): + machine.create() + self.display_item(machine, "La machine à bien été créée", ipsec=True) + return machine + else: + raise Continue(cont) + + def todo(to_display, tags, objectClass, machine, proprio, realm, separateur, cont): + attrs = {} + # On traite les valeurs reçues + for ((a,l),values) in zip(to_display, tags): + values = unicode(values, 'utf-8') + # Si le champs n'est pas single value, on utilise separateur pour découper + # et on ne garde que les valeurs non vides + if not a.singlevalue: + values = [v for v in values.split(separateur) if v] + # Pour host, on fait quelques vérification de syntaxe + if a.ldap_name == 'host': + attrs[a.ldap_name]=check_host(values, objectClass) + else: + attrs[a.ldap_name]=values + # Soit on édite une machine existante + if machine: + machine = modif_machine(machine, attrs) + # Soit on crée une nouvelle machine + else: + machine = create_machine(proprio, realm, attrs) + raise Continue(cont(machine=machine)) + + + if machine: + objectClass = machine["objectClass"][0] + + (code, tags) = self.handle_dialog(cont, box) + + # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu + # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré + # c'est au cableur de corriger ou d'annuler + fields_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)] + retry_cont = TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, proprio=proprio, realm=realm, fields_values=fields_values) + + return self.handle_dialog_result( + code=code, + output=tags, + cancel_cont=cont, + error_cont=retry_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, tags, objectClass, machine, proprio, realm, separateur, cont])] + ) + + def modif_machine_blacklist(self, machine, cont): + """Raccourci vers edit_blacklist spécifique aux machines""" + return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont) + + + + def modif_machine_attributs(self, machine, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux machines""" + return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont) + + def modif_adherent_attributs(self, adherent, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux adherents""" + return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont) + + + def modif_machine_boolean(self, machine, cont): + """Juste un raccourci vers edit_boolean_attributs spécifique aux machines""" + a = attributs + attribs = [a.dnsIpv6] + return self.edit_boolean_attributs( + obj=machine, + attribs=attribs, + title="Édition des attributs booléen de la machine %s" % machine['host'][0], + update_obj='machine', + cont=cont) + + + + def modif_machine(self, cont, machine=None, tag=None): + """ + Permet d'éditer une machine. Si fournie en paramètre on éditer en place, + sinon, on en cherche une dans la base ldap + """ + if machine is None: + machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont) + a = attributs + menu_droits = { + 'Information' : [a.parent, a.cableur, a.nounou], + 'Autre': [a.parent, a.cableur, a.nounou], + 'Blackliste':[a.cableur, a.nounou], + 'Certificat': [a.parent, a.cableur, a.nounou], + 'Exemption' : [a.nounou], + 'Alias' : [a.parent, a.cableur, a.nounou], + 'Remarques' : [a.cableur, a.nounou], + 'SshKey' : [a.parent, a.nounou], + 'Supprimer' : [a.parent, a.cableur, a.nounou], + } + menu = { + 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information}, + 'Autre' : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean}, + 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist}, + 'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat}, + 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt}, + 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias}, + 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, + 'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint}, + 'Supprimer' : {'text':'Supprimer la machine', 'callback':self.delete_machine}, + } + menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques', 'Supprimer'] + def box(default_item=None): + return self.dialog.menu( + "Que souhaitez vous modifier ?", + width=0, + height=0, + menu_height=0, + timeout=self.timeout, + item_help=0, + default_item=str(default_item), + title="Modification de %s" % machine['host'][0], + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], machine)]) + + def todo(tag, menu, machine, cont_ret): + if not tag in menu_order: + raise Continue(cont_ret) + else: + if 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret)) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) + + (code, tag) = self.handle_dialog(cont, box, tag) + cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) + + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont(machine=machine), + error_cont=cont_ret, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, machine, cont_ret])] + ) + + def create_machine_proprio(self, cont, proprio, tag=None): + """Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)""" + a = attributs + menu_droits = { + 'Fixe' : [a.soi, a.cableur, a.nounou], + 'Wifi' : [a.soi, a.cableur, a.nounou], + } + menu = { + 'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'}, + 'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'}, + } + menu_order = ['Fixe', 'Wifi'] + if isinstance(proprio, objets.AssociationCrans): + menu_droits.update({ + 'Fixe' : [a.nounou], + 'Wifi' : [a.nounou], + 'Adm' : [a.nounou], + }) + menu.update({ + 'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'}, + 'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'}, + 'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'}, + }) + menu_order.append('Adm') + def box(default_item=None): + return self.dialog.menu( + "Type de Machine ?", + width=0, + height=0, + menu_height=0, + item_help=0, + timeout=self.timeout, + default_item=str(default_item), + title="Création de machines", + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], proprio)]) + + def todo(tag, menu, proprio, self_cont, cont): + if not tag in menu_order: + raise Continue(self_cont) + else: + return self.machine_information( + cont=cont, + machine=None, + objectClass=menu[tag]['objectClass'], + proprio=proprio, + realm=menu[tag]['realm'] + ) + + (code, tag) = self.handle_dialog(cont, box, tag) + cont = cont(proprio=None) if isinstance(proprio, objets.AssociationCrans) else cont(proprio=proprio) + self_cont = TailCall(self.create_machine_proprio, cont=cont, proprio=proprio, tag=tag) + + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont, cont])] + ) + + def create_machine_adherent(self, cont, adherent=None): + """ + Permet d'ajouter une machine à un adhérent. + On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) + """ + if adherent is None: + adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont) + return self.create_machine_proprio(cont=cont, proprio=adherent) + + def create_machine_club(self, cont, club=None): + """ + Permet d'ajouter une machine à un club. + On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) + """ + if club is None: + club = self.select(["club"], "Recherche d'un club pour lui ajouter une machine", cont=cont) + return self.create_machine_proprio(cont=cont, proprio=club) + + def create_machine_crans(self, cont): + """Permet l'ajout d'une machine à l'association""" + associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0] + return self.create_machine_proprio(cont=cont, proprio=associationCrans) + def delete_machine(self, cont, machine=None): + """Permet la suppression d'une machine de la base ldap""" + if machine is None: + machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) + + def todo(machine): + if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True): + with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: + machine.delete() + self.dialog.msgbox("La machine a bien été supprimée", timeout=self.timeout, title="Suppression d'une machine") + raise Continue(cont(machine=None)) + else: + raise Continue(cont) + + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont, + error_cont=cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [machine])] + ) + diff --git a/gestion/dialog/proprio.py b/gestion/dialog/proprio.py new file mode 100644 index 00000000..73fbaf1d --- /dev/null +++ b/gestion/dialog/proprio.py @@ -0,0 +1,534 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import os +import sys +import copy +import ldap +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') + +from gestion.chgpass import check_password +import gestion.config as config +import gestion.config.factures + +import lc_ldap.objets as objets +import lc_ldap.attributs as attributs +import lc_ldap.crans_utils as lc_utils + +import machine +import blacklist +from CPS import TailCall, tailcaller, Continue + +class Dialog(machine.Dialog, blacklist.Dialog): + def modif_proprio_attributs(self, proprio, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux proprios""" + return self.edit_attributs(obj=proprio, update_obj='proprio', attr=attr, title="Modification de %s %s" % (proprio.get('prenom', [''])[0], proprio['nom'][0]), cont=cont) + @tailcaller + def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'): + """Permet de créer un compte crans à un proprio (club ou adhérent)""" + def box_warning(warning, proprio, cont): + # Affiche-t-on le warning sur la consutation de l'adresse crans + if warning: + if self.dialog.yesno( + text="\Zr\Z1AVERTISSEMENT :\Zn \nL'adhérent devra impérativement consulter l'adresse mail associée\n\n\n\ZnContinuer ?", + title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + defaultno=True, + width=70, + colors=True, timeout=self.timeout) != self.dialog.DIALOG_OK: + raise Continue(cont) + + def get_login(guess_login, guess_pass, proprio, self_cont, cont): + # Essaye-t-on de deviner le login à utiliser + if not guess_login: + (code, login) = self.dialog.inputbox( + text="Le login doit faire au maximum %s caractères\nIl ne doit pas être un pseudo ou prénom mais doit être relié au nom de famille\nSeuls les caractères alphabétiques et le trait d'union sont autorisés" % config.maxlen_login, + title="Choix du login pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + init=str(proprio['nom'][0]).lower(), + width=60, + height=10, timeout=self.timeout) + if code != self.dialog.DIALOG_OK: + raise Continue(cont) + else: + # Si oui, de quelle manière + if guess_pass == 0: + login = str(proprio['nom'][0]) + elif guess_pass == 1 and proprio.get('prenom', [''])[0]: + login = "%s%s" % (str(proprio['prenom'][0])[0], proprio['nom'][0]) + # Si toutes les manières ont échoués, la prochaine fois, ça on n'essaye pas de deviner + else: + raise Continue(self_cont(warning=False, guess_login=False, guess_pass=2)) + return login + + def create_compte(proprio, login, guess_login, self_cont, cont): + try: + proprio.compte(login=unicode(login, 'utf-8')) + except ValueError: + # Il y a eu une erreur, si on essaye de deviner, on essaye la manière suivante + if guess_login: + raise Continue(self_cont(warning=False, guess_login=True, guess_pass=guess_pass+1)) + # Sinon on propage l'erreur pour l'afficher à l'utilisateur + else: + raise + self.dialog.msgbox( + text="Le compte ne sera créé que lors de l'enregistrement des données\n\nL'adresse mail de l'adhérent est : %s\nL'adhérent possède également l'alias :\n%s\n" % (proprio['mail'][0], proprio['canonicalAlias'][0]), + title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + width=75, + height=12, timeout=self.timeout, + ) + return proprio + + @tailcaller + def set_password(proprio, update_obj, cont): + if self.dialog.yesno("Attribuer un mot de passe maintenant ?", + title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + timeout=self.timeout + ) == self.dialog.DIALOG_OK: + #return self.proprio_compte_password(proprio=proprio, return_obj=return_obj, cont=cont(**{update_obj:proprio})) + proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont)) + if return_obj: + return proprio + else: + raise Continue(cont(**{update_obj:proprio})) + elif return_obj: + return proprio + else: + raise Continue(cont(**{update_obj:proprio})) + + def todo(proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont): + box_warning(warning, proprio, cont) + login = get_login(guess_login, guess_pass, proprio, self_cont, cont) + if return_obj: + proprio = create_compte(proprio, login, guess_login, self_cont, cont) + return set_password(proprio, update_obj, cont) + else: + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + proprio = create_compte(proprio, login, guess_login, self_cont, cont) + if not self.confirm_item(item=proprio, title="Création du compte crans pour l'adhérent ?"): + raise Continue(cont) + else: + proprio.history_gen() + proprio.save() + self.dialog.msgbox( + text="Compte créé avec succès.", + title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + timeout=self.timeout + ) + return set_password(proprio, update_obj, cont) + + self_cont = TailCall(self.proprio_compte_create, proprio=proprio, cont=cont, warning=warning, guess_login=guess_login, guess_pass=guess_pass) + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont])] + ) + + @tailcaller + def proprio_compte_password(self, proprio, cont, return_obj=False): + """Permet de changer le mot de passe d'un compte crans""" + def test_password(password, self_cont): + (good, msg) = check_password(password, dialog=True) + if not good: + self.dialog.msgbox( + msg, + title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + colors=True, + width=70, timeout=self.timeout) + raise Continue(self_cont) + else: + return True + def todo(passwords, proprio, return_obj, self_cont, cont): + password = self.get_password(cont=cont, + title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + backtitle="Le mot de passe doit être assez difficile") + + if test_password(password, self_cont): + if return_obj: + proprio['userPassword']=unicode(lc_utils.hash_password(password)) + return proprio + else: + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + proprio['userPassword']=unicode(lc_utils.hash_password(password)) + proprio.history_gen() + proprio.save() + self.dialog.msgbox( + "Mot de passe changé avec succès", + title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + width=70, timeout=self.timeout + ) + raise Continue(cont(proprio=proprio)) + #(code, passwords) = self.handle_dialog(cont, box) + (code, passwords) = (self.dialog.DIALOG_OK, "") + self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont) + return self.handle_dialog_result( + code=code, + output=passwords, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [passwords, proprio, return_obj, self_cont, cont])] + ) + + @tailcaller + def proprio_compte_delete(self, proprio, cont, force=False): + """Permet la suppression du compte crans d'un proprio""" + def todo(proprio, self_cont, cont): + if force or self.confirm_item(item=proprio, title="Voulez vous vraiement supprimer le compte de %s %s ?" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), defaultno=True): + (code, mail) = self.dialog.inputbox( + text="Il faut choisir une nouvelle adresse de contact.\n(On regarde s'il y a une adresse optionnel)", + title="Choix d'une adresse de contact pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + init=str(proprio.get("mailExt", [""])[0]), + width=50, timeout=self.timeout) + if not code == self.dialog.DIALOG_OK: + raise Continue(cont) + elif not mail: + raise ValueError("Il faut entrer une adresse mail") + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + proprio.delete_compte(unicode(mail, 'utf-8')) + proprio.history_gen() + proprio.save() + self.dialog.msgbox("Le compte a bien été supprimée", timeout=self.timeout, title="Suppression du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0])) + raise Continue(cont(proprio=proprio)) + else: + raise Continue(cont) + + self_cont = TailCall(self.proprio_compte_delete, proprio=proprio, cont=cont, force=force) + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output="", + cancel_cont=cont(proprio=proprio), + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, self_cont, cont])] + ) + + def proprio_compte_etat(self, proprio, disable, cont): + """Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire""" + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + if disable: + proprio["shadowExpire"]=0 + else: + proprio["shadowExpire"]=[] + proprio.history_gen() + proprio.save() + raise Continue(cont(proprio=proprio)) + + def proprio_compte_shell(self, proprio, cont, choices_values=None): + """Permet de modifier le shell d'un compte crans""" + a = attributs + # Seul les nounous peuvent changer les shells restrictifs + shells_droits = { + 'default' : [a.soi, a.nounou, a.cableur], + 'rbash' : [a.nounou], + 'rssh' : [a.nounou], + 'badPassSh' : [a.nounou], + 'disconnect_shell':[a.nounou], + } + shell = os.path.basename(str(proprio['loginShell'][0])).lower() + shells = config.shells_gest_crans + shells_order = config.shells_gest_crans_order + def box(): + # liste des shell éditables par l'utilisateur + editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)] + # Si l'utilisateur de gest_crans peut éditer le shell courant + # il peut le changer pour un autre shell qu'il peut éditer + if shell in editable_sheels: + choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels] + return self.dialog.radiolist( + text="", + height=0, width=0, list_height=0, + choices=choices_values if choices_values else choices, + title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), + timeout=self.timeout + ) + # Sinon, on affiche un message d'erreur et on annule + else: + self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur", + title="Édition du shell impossible", timeout=self.timeout, width=0, height=0) + return (self.dialog.DIALOG_CANCEL, None) + def todo(output, shell, shells, proprio, self_cont, cont): + loginShell = shells[output]['path'] + if shell and shell != output: + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + proprio['loginShell']=unicode(loginShell) + proprio.history_gen() + proprio.save() + self.dialog.msgbox("Shell modifié avec succès.\nLa modification peut prendre une quainzaine de minute avant d'être effective.", + title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), + width=50, timeout=self.timeout, + ) + raise Continue(cont(proprio=proprio)) + + (code, output) = self.handle_dialog(cont, box) + self_cont = TailCall(self.proprio_compte_shell, proprio=proprio, cont=cont, choices_values=[(s, shells[s]['desc'], 1 if s == output else 0) for s in shells_order]) + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [output, shell, shells, proprio, self_cont, cont])] + ) + + def proprio_compte(self, proprio, cont, default_item=None): + """Menu de gestion du compte crans d'un proprio""" + has_compte = 'cransAccount' in proprio['objectClass'] + disabled_compte = has_compte and 0 in proprio['shadowExpire'] + a = attributs + menu_droits = { + "Password": [a.cableur, a.nounou], + 'MailAlias': [a.cableur, a.nounou], + "Activer" : [a.nounou], + "Désactiver" : [a.nounou], + "Créer" : [a.cableur, a.nounou], + "Supprimer" : [a.cableur, a.nounou], + "Shell" : [a.nounou, a.soi, a.cableur], + } + menu = { + "Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password}, + 'MailAlias' : {'text': 'Créer ou supprimer des alias mail', "help":"", 'attribut':attributs.mailAlias}, + "Shell" : {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell}, + "Activer" : {"text" : "Activer le compte pour la connexion mail/serveur", "help":"Permet d'autoriser les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=False)}, + "Désactiver" : {"text" : "Désactiver le compte pour la connexion mail/serveur", "help":"Permet d'interdire les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=True)}, + "Créer" : {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create}, + "Supprimer" : {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete}, + } + menu_order = [] + tag_translate = { + "Créer":"Password", + "Password":"Password", + "Supprimer":"Créer", + "Activer":"Désactiver", + "Désactiver":"Activer", + "Shell":"Shell", + 'MailAlias':'MailAlias', + '':'', + } + if has_compte: + if disabled_compte: + menu_order.append("Activer") + else: + menu_order.append("Désactiver") + menu_order.extend(['MailAlias', "Shell", "Password", "Supprimer"]) + else: + menu_order.append("Créer") + def box(default_item=None): + return self.dialog.menu( + "Quel action effectuer sur le compte ?", + width=0, + height=0, + menu_height=0, + timeout=self.timeout, + item_help=1, + default_item=str(default_item), + title="Gestion du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)]) + + def todo(tag, menu, proprio, self_cont): + if not tag in menu_order: + raise Continue(self_cont) + elif 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio)) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_proprio_attributs, proprio=proprio, cont=self_cont, attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) + + + (code, tag) = self.handle_dialog(cont, box, default_item) + self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate.get(tag, tag)) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont])] + ) + + @tailcaller + def proprio_vente_set(self, article, cont): + """Permet de définir la quantité de l'article à vendre""" + def box(): + if article['pu'] == '*': + return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'], + text="", init=str(article.get('nombre','')), timeout=self.timeout, width=70) + else: + return self.dialog.inputbox(title="Nombre de %s ?" % article['designation'], + text="", timeout=self.timeout, init=str(article.get('nombre','1')), width=70) + def todo(article, output, cont): + article['nombre']=output + # Il faut entrer quelque chose + if not output: + raise ValueError("Merci d'entrer une valeur") + # Vérification des centimes + if article['pu'] == '*' and '.' in output: + if len(output.split('.', 1)[1])>2: + raise ValueError("Les centimes, c'est seulement deux chiffres après la virgule") + typ = float if article['pu'] == '*' else int + # Vérification du type de l'entré + try: + output=typ(output) + except ValueError: + raise ValueError("Merci d'entrez seulement des nombres") + # On définis le nombre d'entrée. Pour pu=* il y aura du trairement à faire + # avant de générer la facture : mettre pu à nombre et nombre à 1 + # on le met comme ça pour pouvoir naviger aisément entre les écrans dialog + article['nombre'] = output + return article + + (code, output) = self.handle_dialog(cont, box) + self_cont = TailCall(self.proprio_vente_set, article=article, cont=cont) + return self.handle_dialog_result( + code=code, + output=output, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [article, output, cont])] + ) + + @tailcaller + def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]): + """Menu de vente du crans. Permet également de recharger le solde crans""" + box_paiement = { + "liquide" : "Espèces", + "cheque" : "Chèque", + "solde" : "Solde Crans (actuel : %s€)", + } + + def box_choose_item(tags): + choices = [] + for code, article in gestion.config.factures.items.items(): + choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0)) + return self.dialog.checklist( + text="", + title="Vente de truc à %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), + choices=choices, + timeout=self.timeout) + + def box_choose_paiment(tag, articles): + box_paiement_order = ["liquide", "cheque"] + if "cransAccount" in proprio['objectClass']: + if not "SOLDE" in [art['code'] for art in articles]: + box_paiement_order.append("solde") + box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0] + choices = [] + for key in box_paiement_order: + choices.append((key, box_paiement[key], 1 if key == tag else 0)) + return self.dialog.radiolist( + text="", + title="Choix d'un mode de paiement pour %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), + choices=choices, + timeout=self.timeout) + + def choose_item(proprio, tags, articles, self_cont): + to_set=[] + for tag in tags: + articles[tag]['code']=tag + to_set.append(articles[tag]) + raise Continue(self_cont(to_set=to_set, have_set=[])) + + def number_of_items(to_set, have_set, self_cont): + # Où faut-il aller si l'utilisateur appuis sur annuler + if not have_set: + lcont = self_cont(to_set=[]) + else: + lcont = self_cont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) + art = self.proprio_vente_set(to_set[0], cont=lcont) + if not to_set[1:]: + total = 0 + line=1 + text=u"Résumé :\n" + for article in have_set + [art]: + if article['pu'] == '*': + total += article['nombre'] + text+=u" * %s pour %s€\n" % (article['designation'], article['nombre']) + else: + total += article['nombre']*article['pu'] + text+=u" * %dx %s à %s€\n" % (article['nombre'], article['designation'], article['pu']) + line+=1 + text+=u"Total à payer : %s€" % total + self.dialog.msgbox(text=text, + title="Résumé de la facture à payer", + width=70, height=5+line, timeout=self.timeout) + return self_cont(to_set=to_set[1:], have_set=have_set + [art]) + + def choose_paiment(have_set, tag, proprio, lcont, self_cont, cont): + if not tag: + raise ValueError("Il faut choisir un moyen de paiement") + code, comment = self.dialog.inputbox(text="Détail pour les espèce, nom de la note ou banque du chèque", title="Commentaire", width=70, timeout=self.timeout) + if code != self.dialog.DIALOG_OK: + raise Continue(self_cont) + if not comment: + raise ValueError("Commentaire nécessaire") + articles = copy.deepcopy(have_set) + for article in articles: + if article['pu'] == '*': + article['pu'] = article['nombre'] + article['nombre'] = 1 + + with self.conn.newFacture(proprio.dn, {}) as facture: + facture['modePaiement']=unicode(tag, 'utf-8') + facture['article']=articles + facture['info']=unicode(comment, 'utf-8') + if self.confirm_item(item=facture, + text=u"Le paiement de %s€ a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag), + title=u"Validation du paiement", + timeout=self.timeout): + # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture + facture.crediter() + arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE'] + if arts: + self.dialog.msgbox( + text=u"Vous pouvez remettre à l'adherent les articles (si se sont des articles) suivant :\n * %s" % '\n * '.join(arts), + title=u"Vente terminée", + width=0, height=0, timeout=self.timeout) + if tag == "solde": + self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout) + if [a for a in facture['article'] if art['code'] == 'SOLDE']: + self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout) + else: + self.dialog.msgbox(text=u"Le paiement n'ayant pas été reçue\nla vente est annulée", title="Annulation de la vente", width=0, height=0, timeout=self.timeout) + raise Continue(cont) + + self_cont=TailCall(self.proprio_vente, proprio=proprio, cont=cont, tags=tags, tag_paiment=tag_paiment, to_set=to_set, have_set=have_set) + # S'il y a des article dont il faut définir la quantité + if to_set: + return self.handle_dialog_result( + code=self.dialog.DIALOG_OK, + output=None, + cancel_cont=None, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], number_of_items, [to_set, have_set, self_cont])] + ) + # Sinon, si tous les quantités de tous les articles sont définis + elif have_set: + lcont = self_cont.copy() + lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) + (code, tag) = self.handle_dialog(lcont, box_choose_paiment, tag_paiment, have_set) + self_cont=self_cont(tag_paiment=tag) + lcont(tag_paiment=tag) + return self.handle_dialog_result( + code=code, + output=tag, + cancel_cont=lcont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], choose_paiment, [have_set, tag, proprio, lcont, self_cont, cont])] + ) + # Sinon, on propose des articles à chosir + else: + (code, tags) = self.handle_dialog(cont, box_choose_item, tags) + self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None) + return self.handle_dialog_result( + code=code, + output=tags, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.items), self_cont])] + ) diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py index 78e27ab8..d42aca33 100755 --- a/gestion/gest_crans_lc.py +++ b/gestion/gest_crans_lc.py @@ -15,198 +15,15 @@ Licence : GPLv3 import os import sys -import ssl -import time -import copy -import ldap -import signal -import inspect -import tempfile -import collections if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') -from pythondialog import Dialog -from pythondialog import DialogError, DialogTerminatedBySignal -from OpenSSL import crypto, SSL -from gestion.cert_utils import createCertRequest -from gestion.affich_tools import get_screen_size, coul -from gestion.chgpass import check_password -import gestion.config as config -import gestion.config.factures - -import lc_ldap.shortcuts import lc_ldap.objets as objets import lc_ldap.attributs as attributs -import lc_ldap.printing as printing -import lc_ldap.crans_utils as lc_utils - -from lc_ldap.attributs import UniquenessError - -import gestion.secrets_new as secrets -debugf=None -debug_enable = '--debug' in sys.argv[1:] -test_enabled = '--test' in sys.argv[1:] - -if debug_enable: - import traceback - -def mydebug(txt): - """Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log""" - global debugf, debug_enable - if debug_enable: - if debugf is None: - debugf = open('/tmp/gest_crans_lc.log', 'w') - if isinstance(txt, list): - for v in txt: - mydebug(' ' + str(v)) - else: - debugf.write(str(txt)+'\n') - debugf.flush() - os.fsync(debugf) - -# Implémentation "à la main" de la tail récursion en python -# voir http://kylem.net/programming/tailcall.html -# je trouve ça assez sioux -# En plus, ça nous permet de gérer plus facilement le -# code en CPS (Continuation Passing Style) -class TailCaller(object) : - """ - Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion - faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) - """ - other_callers = {} - def __init__(self, f) : - self.f = f - self.func_name = f.func_name - TailCaller.other_callers[id(self)]=f.func_name - def __del__(self): - del(TailCaller.other_callers[id(self)]) - - def __call__(self, *args, **kwargs) : - mydebug("***%s calling" % self.func_name) - stacklvl = len(inspect.stack()) - try: - ret = self.f(*args, **kwargs) - except Continue as c: - ret = c.tailCall - while isinstance(ret, TailCall): - # Si la continuation a été créer par un TailCaller précédent, on propage - # Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall - # Crée après lui. - if stacklvl>=ret.stacklvl: - mydebug("***%s terminate" % self.func_name) - raise Continue(ret) - mydebug("***%s doing %s" % (self.func_name, ret)) - try: - ret = ret.handle() - except Continue as c: - ret = c.tailCall - mydebug("***%s returning %s" % (self.func_name, ret)) - return ret - -def tailcaller(f): - """ - Décorateur retardant la décoration par TailCaller d'une fonction - À utiliser sur les fonctions faisant de la tail récursion - et devant retourner une valeur. On l'utilise de toute - façon sur la fonction d'entrée dans l'application - Une fonction de devrait pas instancier de TailCall sans - être décoré par ce décorteur de façon générale - """ - - f.tailCaller = True - return f - -class Continue(Exception): - """Exception pour envoyer des TailCall en les raisant""" - def __init__(self, tailCall): - self.tailCall = tailCall - -class TailCall(object) : - """ - Appel tail récursif à une fonction et ses argument - On peut voir un TailCall comme le fait de retarder - l'appel à une fonction (avec ses listes d'arguements) - à un moment futur. La fonction sera appelée dans les - cas suivant : - * Le TailCall est retourné par une fonction qui est un TailCaller - * L'exception Continue(TailCall(...)) est levée et traverse - une fonction qui est un TailCaller - """ - - def __init__(self, call, *args, **kwargs) : - self.stacklvl = len(inspect.stack()) - if isinstance(call, TailCall): - call.kwargs.update(**kwargs) - kwargs = call.kwargs - args = call.args + args - call = call.call - - self.call = call - self.args = args - self.kwargs = kwargs - - self.check(self.args, self.kwargs) - - def check(self, args, kwargs): - call = self.call - if isinstance(call, TailCaller): - call = call.f - targs = inspect.getargspec(call) - if targs.varargs is not None: - if len(args) + len(kwargs) > len(targs.args): - raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs))) - for key in kwargs: - if key not in targs.args: - raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key)) - - def __str__(self): - return "TailCall<%s(%s%s%s)>" % ( - self.call.func_name, - ', '.join(repr(a) for a in self.args), - ', ' if self.args and self.kwargs else '', - ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items()) - ) - - def copy(self): - ''' - Renvois une copie de l'objet courant - attention les elements et args ou kwargs sont juste linké - ça n'est pas gennant dans la mesure où ils ne sont normalement pas - éditer mais remplacé par d'autres éléments - ''' - result = TailCall(self.call, *list(self.args), **dict(self.kwargs)) - result.stacklvl = self.stacklvl - return result - - def __call__(self, *args, **kwargs): - tmpkwargs={} - tmpkwargs.update(self.kwargs) - tmpkwargs.update(kwargs) - self.check(self.args + args, tmpkwargs) - self.kwargs.update(kwargs) - self.args = self.args + args - return self - - def handle(self) : - """ - Exécute la fonction call sur sa liste d'argument. - on déréférence les TailCaller le plus possible pour réduire - la taille de la stack - """ - caller = None - call = self.call - while isinstance(call, TailCaller) : - caller = call - call = self.call.f - return call(*self.args, **self.kwargs) - -def unicode_of_Error(x): - """Formatte des exception""" - return u"\n".join(unicode(i, 'utf-8') if type(i) == str - else repr(i) for i in x.args) +from dialog import adherent, club, machine +from dialog.CPS import TailCall, tailcaller +from dialog.lc import main def handle_exit_code(d, code): """Gère les codes de retour dialog du menu principal""" @@ -224,2813 +41,9 @@ def handle_exit_code(d, code): sys.exit(0) return False else: - return True # code est d.DIALOG_OK - -def raiseKeyboardInterrupt(x, y): - """fonction utilisée pour réactiver les Ctrl-C""" - raise KeyboardInterrupt() - -class GestCrans(object): - """Interface de gestion des machines et des adhérents du crans, version lc_ldap""" - def __getattribute__(self, attr): - ret = super(GestCrans, self).__getattribute__(attr) - # Petit hack pour que ça soit les methodes de l'objet instancié qui soient - # décorée par TailCaller et pas les méthodes statiques de la classe GestCrans - if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller): - ret = TailCaller(ret) - setattr(self, attr, ret) - return ret - - def __init__(self): - signal.signal(signal.SIGINT, signal.SIG_IGN) - # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog - printing.template(dialog=True) - - self.check_ldap() - # On met un timeout à 10min d'innactivité sur dialog - self.timeout = 600 - self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN) - - def check_ldap(self): - """Se connecte à la base ldap et vérifie les droits de l'utilisateur courant""" - self.check_ldap_last = time.time() - - # S'il y a --test dans les argument, on utilise la base de test - if test_enabled: - self.conn = lc_ldap.shortcuts.lc_ldap_test() - else: - # On ouvre une connexion lc_ldap - self.conn = lc_ldap.shortcuts.lc_ldap_admin() - - # On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits) - luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % self.conn.current_login) - if not luser: - sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % self.conn.current_login) - sys.exit(1) - self.conn.droits = [str(d) for d in luser[0]['droits']] - self.conn.dn = luser[0].dn - - # Si un nom d'utilisateur est donné sur la ligne de commande - # et qu'on a les droits nounou, on l'utilise - if sys.argv[1:] and attributs.nounou in self.conn.droits: - for u in sys.argv[1:]: - luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % u) - if luser: - self.conn.current_login = u - self.conn.droits = [str(d) for d in luser[0]['droits']] - self.conn.dn = luser[0].dn - break - a = attributs - allowed_right = [a.cableur, a.tresorier, a.bureau, a.nounou, a.imprimeur] - for droit in allowed_right: - if droit in self.conn.droits: - break - else: - sys.stderr.write( - u"%s ne possède aucun des droits :\n * %s\nnécessaire à utiliser ce programme\n" % ( - self.conn.current_login, - '\n * '.join(allowed_right) - ) - ) - sys.exit(1) - - _dialog = None - @property - def dialog(self): - """ - Renvois l'objet dialog. De plus renouvelle régulièrement la connexion à la base ldap - """ - # Tous les self.timeout, on refraichie la connexion ldap - if time.time() - self.check_ldap_last > self.timeout: - self.check_ldap_last = time.time() - if self._dialog is None: - self._dialog = Dialog() - self.dialog_last_access = time.time() - return self._dialog - - def nyan(self, cont, *args, **kwargs): - """ - Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan - """ - (lines, cols) = get_screen_size() - print "\033[48;5;17m" - print " "*(lines * cols) - cols = int(min(cols/2, 65)) - lines = int(lines) -1 - cmd = "/usr/bin/nyancat -W %s -H %s" % (cols, lines) - os.system(cmd) - raise Continue(cont) - - - @tailcaller - def handle_dialog(self, cancel_cont, box, *args): - """ - Gère les fonctions appelant une fênetre dialog : - gestion de l'appuie sur Ctrl-C et rattrapage des exception / segfault de dialog. - cancel_cont représente l'endroit où retourner si Ctrl-C - """ - ctrlC=False - ret=None - try: - signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C - ret = box(*args) - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - except KeyboardInterrupt: - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - raise Continue(cancel_cont) - except DialogTerminatedBySignal as e: - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - if e[1] == 11: - self.dialog.msgbox( - "La fenêtre dialog à été fermée par une erreur de segmentation", - timeout=self.timeout, title="Erreur rencontrée", width=73, height=10 - ) - raise Continue(cancel_cont) - else: - raise - finally: - if ret: - return ret - else: - EnvironmentError("Pas de ret ?!? c'est pas possible ") - - @tailcaller - def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]): - """ - Gère les fonctions traitant les résultat d'appels à dialog. - s'occupe de gérer les exceptions, Ctrl-C, propagation de certaine exceptions, l'appuis sur annuler. - Le code à exécuté lui ai passé via la liste codes_todo, qui doit contenir une liste de triple : - (code de retour dialog, fonction à exécuter, liste des arguements de la fonction) - la fonction est appelée sur ses arguements si le code retourné par dialog correspond. - codes_todo = [(code, todo, todo_args)] - """ - # Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cancel_cont) - # Sinon, c'est OK - else: - for (codes, todo, todo_args) in codes_todo: - if code in codes: - try: - signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C - # On effectue ce qu'il y a a faire dans todo - ret = todo(*todo_args) - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - return ret - # On propage les Continue - except self.error_to_raise: - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - raise - # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition - except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - self.dialog.msgbox(traceback.format_exc() if debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout, - title="Erreur rencontrée", width=73, height=10) - raise Continue(error_cont) - except KeyboardInterrupt: - signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - raise Continue(cancel_cont) - - # En cas de code de retour dialog non attendu, on prévient et on retourne au menu d'édition - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, - timeout=self.timeout, title="Erreur rencontrée", width=73, height=10) - raise Continue(error_cont) - - @tailcaller - def edit_blacklist_select(self, obj, title, cont): - """ - Permet de choisir une blackliste parmis celle de obj oubien une nouvelle blacklist. - Retourne (index, bl) où bl est un dictionnaire représentant la blackliste et index - l'index de la blackliste dans obj['blacklist'] ou new pour une nouvelle blacklist - """ - def box(): - choices = [('new', 'Ajouter une nouvelle blackliste')] - index = 0 - for bl in obj['blacklist']: - choices.append( - (str(index), - coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None, - dialog=True) - ) - ) - index+=1 - return self.dialog.menu( - "Éditer une blacklist ou en ajouter une nouvelle ?\n(les blacklistes actives apparaissent en rouge)", - width=0, - timeout=self.timeout, - height=0, - menu_height=0, - item_help=0, - title=title, - scrollbar=True, - colors=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=choices) - - def todo(tag): - if tag == 'new': - return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''} - else: - bl = {} - bl.update(obj['blacklist'][int(tag)].value) - return tag, bl - - - (code, tag) = self.handle_dialog(cont, box) - retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])] - ) - - @tailcaller - def edit_blacklist_type(self, title, cont): - """Permet de choisir un type de blackliste pour les nouvelles blacklistes""" - retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont) - - def box(): - return self.dialog.menu( - "Choisissez un type de blacklist", - width=0, - height=0, - menu_height=0, - item_help=0, - timeout=self.timeout, - title=title, - scrollbar=True, - colors=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(k,v) for (k,v) in config.blacklist_items.items()]) - - def todo(tag, retry_cont): - if tag in config.blacklist_items: - return tag - else: - self.dialog.msgbox("%s n'est pas une blacklist" % tag, timeout=self.timeout, - title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) - - (code, tag) = self.handle_dialog(cont, box) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont(bl=None), - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, retry_cont])] - ) - - @tailcaller - def get_timestamp(self, title, text, cont, hour=-1, minute=-1, second=-1, day=0, month=0, year=0): - """Fait choisir une date et une heure et retourne le tuple (year, month, day, hour, minute, second)""" - retry_cont = TailCall(self.get_timestamp, title=title, text=text, cont=cont, hour=hour, - minute=minute, second=second, day=day, month=month, year=year) - def get_date(day, month, year): - (code, output) = self.dialog.calendar(text, day=day, month=month, year=year, - timeout=self.timeout, title=title) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - elif output: - (day, month, year) = output - return (year, month, day) - else: - raise EnvironmentError("Pourquoi je n'ai pas de date ?") - def get_time(hour, minute, second, day, month, year): - (code, output) = self.dialog.timebox(text, timeout=self.timeout, hour=hour, - minute=minute, second=second) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(retry_cont(day=day, month=month, year=year)) - elif output: - (hour, minute, second) = output - return (hour, minute, second) - else: - raise EnvironmentError("Pourquoi je n'ai pas d'horaire ?") - (year, month, day) = get_date(day, month, year) - (hour, minute, second) = get_time(hour, minute, second, day, month, year) - return (year, month, day) + (hour, minute, second) - - def edit_blacklist(self, obj, title, update_obj, cont, bl=None, tag=None, bl_type=None, - debut=None, fin=None, comm=None): - """Pour éditer les blacklistes d'un objet lc_ldap""" - self_cont = TailCall(self.edit_blacklist, obj=obj, title=title, update_obj=update_obj, - cont=cont, bl=bl, tag=tag, bl_type=bl_type, debut=debut, fin=fin, comm=comm) - # Si bl ou tag ne sont pas définit on les demande - if bl is None or tag is None: - bl_type = None - debut = None - fin = None - comm = None - tag, bl = self.edit_blacklist_select(obj, title, cont(**{update_obj:obj})) - if bl_type is None and tag == 'new': - bl['type'] = self.edit_blacklist_type(title, self_cont(obj=obj)) - elif tag == 'new': - bl['type'] = bl_type - - # Cas de l'ajout d'un blacklist - if tag == 'new': - # Si debut n'est pas encore spécifié, on le demande - if debut is None: - debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", - cont=self_cont(bl=bl, tag=tag, bl_type=None, debut=None, fin=None, comm=None)) - debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) - # Idem pour fin - if fin is None: - if self.dialog.yesno("Mettre une date de fin ?", title=title, - timeout=self.timeout) == self.dialog.DIALOG_OK: - fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", - cont=self_cont(bl=bl, tag=tag, bl_type=bl_type, - debut=None, fin=None, comm=None)) - fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) - else: - fin = '-' - bl['debut']=debut - bl['fin']=fin - bl['comm']=self.get_comment(title=title, text="Commentaire ?", - cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) - if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), - title="Ajouter la blacklist ?"): - try: - with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: - obj['blacklist'].append(bl) - obj.history_gen() - obj.save() - # On s'en va en mettant à jour dans la continuation la valeur de obj - raise Continue(self_cont(bl=None, obj=obj)) - # On propage les Continue - except self.error_to_raise: - raise - # En cas d'une autre erreur, on l'affiche et on retourne - except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: - self.dialog.msgbox(traceback.format_exc() if debug_enable else ("%s" % unicode_of_Error(e)), timeout=self.timeout, - title="Erreur rencontrée", width=73) - raise Continue(self_cont(obj=obj)) - else: - raise Continue(self_cont(bl=None, obj=obj)) - - # Cas de l'édition d'une blacklist - else: - if debut is None: - # Mettre un warning pour éditer (seulement quand debut vaut None pour ne pas le répéter à chaque fois que l'on revient en arrière par la suite - if not self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Éditer la blackliste ?"): - raise Continue(self_cont(bl=None, obj=obj)) - debut = time.localtime(bl['debut']) - debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, debut=None, fin=None, comm=None), - day=debut.tm_mday, - month=debut.tm_mon, - year=debut.tm_year, - hour=debut.tm_hour, - minute=debut.tm_min, - second=debut.tm_sec - ) - debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) - bl['debut'] = debut - if fin is None: - if self.dialog.yesno("Mettre une date de fin ?", timeout=self.timeout, title=title) == self.dialog.DIALOG_OK: - if bl['fin'] == '-': - fin = time.localtime() - else: - fin = time.localtime(bl['fin']) - fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, debut=debut, fin=None, comm=None), - day=fin.tm_mday, - month=fin.tm_mon, - year=fin.tm_year, - hour=fin.tm_hour, - minute=fin.tm_min, - second=fin.tm_sec - ) - fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) - else: - fin = '-' - bl['fin'] = fin - bl['comm']=self.get_comment(title=title, text="Commentaire ?", init=bl['comm'], cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) - if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Modifier la blacklist ?"): - try: - with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: - obj['blacklist'][int(tag)]=bl - obj.history_gen() - obj.save() - # On s'en va en mettant à jour dans la continuation la valeur de obj - raise Continue(self_cont(bl=None, obj=obj)) - # On propage les Continue - except self.error_to_raise: - raise - # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition - except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: - self.dialog.msgbox(traceback.format_exc() if debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout, title="Erreur rencontrée", width=73) - raise Continue(self_cont) - else: - raise Continue(self_cont(bl=None, obj=obj)) - - - - def edit_boolean_attributs(self, obj, attribs, title, update_obj, cont, values={}): - """ - Permet d'éditer des attributs booléen de l'objet obj listé dans attribs. - update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié - """ - # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut - missing = { - 'default' : False, # par défaut, on dit que c'est False - attributs.dnsIpv6 : True # pour dnsIpv6, c'est True - } - choices = [(a.ldap_name, a.legend, 1 if values.get(a.ldap_name, obj[a.ldap_name][0] if obj[a.ldap_name] else missing.get(a, missing['default'])) else 0) for a in attribs] - def box(): - return self.dialog.checklist("Activier ou désactiver les propriétés suivantes", - height=0, - width=0, - timeout=self.timeout, - list_height=7, - choices=choices, - title=title) - - def todo(values, obj, attribs, cont): - # On met à jour chaque attribut si sa valeur à changé - with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: - for a in attribs: - if obj[a.ldap_name] and obj[a.ldap_name][0] != values[a.ldap_name]: - obj[a.ldap_name]=values[a.ldap_name] - elif not obj[a.ldap_name] and missing.get(a, missing['default']) != values[a.ldap_name]: - obj[a.ldap_name]=values[a.ldap_name] - obj.history_gen() - obj.save() - # On s'en va en mettant à jour dans la continuation la valeur de obj - raise Continue(cont(**{update_obj:obj})) - - (code, output) = self.handle_dialog(cont, box) - - # On transforme la liste des cases dialog cochée en dictionnnaire - values = dict((a.ldap_name, a.ldap_name in output) for a in attribs) - - # Une continuation que l'on suivra si quelque chose se passe mal - retry_cont = TailCall(self.edit_boolean_attributs, obj=obj, update_obj=update_obj, attribs=attribs, title=title, cont=cont, values=values) - - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [values, obj, attribs, cont])] - ) - - def edit_attributs(self, obj, attr, title, update_obj, cont, tag=None, values=None): - """ - Permet d'éditer la liste d'attribut attr de l'objet obj. - update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié - """ - - # Il n'y a pas inputmenu dans la lib dialog, du coup, on traite les arguments à la main. - # Ça reste relativement acceptable comme on utilise la fonction de la lib pour appeler dialog - def box(values, default_tag): - cmd = ['--inputmenu', "Édition de l'attribut %s :" % attr, "0", "0", "20"] - index=0 - for value in values: - cmd.extend([str(index), str(value)]) - index+=1 - cmd.extend(['new', '']) - (code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, default_item=str(default_tag)) - if code == self.dialog.DIALOG_EXTRA: - output = output.split(' ', 2)[1:] - else: - output = '' - return (code, output) - - def todo_extra(output, values, retry_cont): - tag, value = output - if tag == 'new': - if value: - values.append(value) - elif value == '': - values.pop(int(tag)) - else: - values[int(tag)] = value - raise Continue(retry_cont(values=values, tag=tag)) - - def todo(obj, values, cont): - with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: - obj[attr] = [unicode(value, 'utf-8') for value in values] - obj.history_gen() - obj.save() - raise Continue(cont(**{update_obj:obj})) - - if values is None: - values = [str(a) for a in obj[attr]] - retry_cont = TailCall(self.edit_attributs, obj=obj, attr=attr, title=title, update_obj=update_obj, cont=cont, values=values) - (code, output) = self.handle_dialog(cont, box, values, tag) - - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[ - ([self.dialog.DIALOG_OK], todo, [obj, values, cont]), - ([self.dialog.DIALOG_EXTRA], todo_extra, [output, values, retry_cont]), - ] - ) - - def search(self, objectClassS, title, values={}, cont=None, disable_field=[]): - """ - Rechercher des adhérents ou des machines dans la base ldap - retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés) - La fonction est découpé en trois partie : - * affichage dialog et récupération du résultat - * construction de filtres de recherche ldap et recherches ldap - * filtre sur les résultats des recherches ldap - """ - select_dict = { - #label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars - 'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]}, - 'Prénom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]}, - 'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]}, - 'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]}, - 'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]}, - 'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 40, 00]}, - # seconde colone - 'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]}, - 'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]}, - 'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]}, - 'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]}, - 'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]}, - } - # On a besoin de l'ordre pour récupérer les valeurs ensuite - select_adherent = ['Nom', 'Prénom', 'Téléphone', 'Chambre', 'aid', 'mail'] - select_machine = ['Host', 'Mac', 'IP', 'mid'] - if 'club' in objectClassS and not 'adherent' in objectClassS: - select_dict['cid']=select_dict['aid'] - select_dict['cid']['ldap']='cid' - select_dict['cid']['params'][2]=values.get('cid', "") - select_adherent[select_adherent.index('aid')]='cid' - def box(): - # On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone - cmd = ["--mixedform", "Entrez vos paramètres de recherche", '0', '0', '0'] - for key in select_adherent: - cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0']) - cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']] + ['2']) - for key in select_machine: - cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0']) - cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0', '2' ]) - # On utilise quand même la fonction de la bibliothèques pour passer les arguments - (code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, backtitle="Entrez vos paramètres de recherche") - if output: - return (code, output.split('\n')[:-1]) - else: # empty selection - return (code, []) - - (code, dialog_values) = self.handle_dialog(cont, box) - # Si il a appuyé sur annuler ou sur escape, on saute sur la continuation - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - else: - # Transformation de la liste des valeures entrée en dictionnnaire - dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) - ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()]) - - # Construction des filtres ldap pour les adhérents et les machines - filter_adherent = [] - filter_machine = [] - for (key, value) in dialog_values.items(): - if value: - if key in select_adherent: - filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) - elif key in select_machine: - filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) - if filter_adherent: - filter_adherent=u"(&%s)" % "".join(filter_adherent) - if filter_machine: - filter_machine=u"(&%s)" % "".join(filter_machine) - - # Récupération des adhérents et des machines - adherents=self.conn.search(filter_adherent) if filter_adherent else [] - machines=self.conn.search(filter_machine) if filter_machine else [] - - # Filtrage des machines en fonction des adhérents - if filter_adherent: - if filter_machine: - # Si on filtre sur des adhérent et des machines, on calcule l'intersection - adherents_dn = set([a.dn for a in adherents]) - machines_f = [m for m in machines if m.parent_dn in adherents_dn] - else: - # Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés - machines_f = [m for a in adherents for m in a.machines()] - else: - # Sinon si on filtre seulement sur des machines - machines_f = machines - - # Filtrage des adhérents en fonction des machines - if filter_machine: - if filter_adherent: - # Si on filtre sur des adhérents et des machines, on calcule l'intersection - machines_dn = set([m.parent_dn for m in machines]) - adherents_f = [a for a in adherents if a.dn in machines_dn] - else: - # Sinon on récupères les proprios des machines trouvées - adherents_f = [m.proprio() for m in machines] - else: - # Sinon si on filtre seulement sur des adhérents - adherents_f = adherents - - # On filtre sur les objectClassS - return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] - - @tailcaller - def select_one(self, items, title, text="Que souhaitez vous faire ?", default_item=None, cont=None): - """Fait selectionner un item parmis une liste d'items à l'utisateur""" - - def box(items, default_item): - choices=[] - olist={} - count = 0 - - # On sépare les item d'items en fonction de leur type - for o in items: - olist[o.__class__] = olist.get(o.__class__, []) + [o] - classes = olist.keys() - classes.sort() - default_tag = items.index(default_item) if default_item in items else default_item - - # On se débrouille pour faire corresponde l'ordre d'affichache des objets - # et leur ordre dans la liste items. On donne la largeur de l'affichage à la main - # pour prendre en compte la largeur du widget dialog - del items[:] # On vide la liste pour la modifier en place - items_id = {} - (line, col) = get_screen_size() - for c in classes: - items.extend(olist[c]) - items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n') - choices.append(("", str(items_s[0]))) - next=1 - if items_s[next:]: - choices.append(("", str(items_s[next]))) - next+=1 - for i in items_s[next:]: - if i: # on zap les lignes vides - choices.append((str(count), str(i))) - count+=1 - # On laisse une ligne vide pour séparer les listes d'objets de type différent - choices.append(("", "")) - - return self.dialog.menu( - text, - width=0, - height=0, - menu_height=0, - timeout=self.timeout, - item_help=0, - default_item=str(default_tag), - title=title, - scrollbar=True, - choices=choices, - colors=True) - - - def todo(tag, items, title, cont, retry_cont): - # Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose - if not tag: - self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", timeout=self.timeout, title="Sélection", width=0, height=0) - raise Continue(retry_cont) - # Sinon on retourne l'item choisis - elif self.confirm_item(items[int(tag)], title): - return items[int(tag)] - else: - raise Continue(cont) - - (code, tag) = self.handle_dialog(cont, box, items, default_item) - retry_cont = TailCall(self.select_one, items=items, title=title, default_item=tag, cont=cont) - - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, items, title, cont, retry_cont])] - ) - - @tailcaller - def get_comment(self, title, text, cont, init='', force=False): - """ - Fait entrer à l'utilisateur un commentaire et le retourne. - Si force est à True, on oblige le commentaire à être non vide - """ - (code, output) = self.dialog.inputbox(text=text, title=title, timeout=self.timeout, init=init) - retry_cont = TailCall(self.get_comment, title=title, text=text, cont=cont, force=force) - def todo(output, force, title, retry_cont): - if force and not output: - self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose", timeout=self.timeout, title=title) - raise Continue(retry_cont) - else: - return unicode(output, 'utf-8') - - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [output, force, title, retry_cont])] - ) - - def confirm_item(self, item, title, defaultno=False, text='', text_bottom="", **params): - """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" - return self.dialog.yesno( - text + printing.sprint(item, **params) + text_bottom, - no_collapse=True, - colors=True, - no_mouse=True, - timeout=self.timeout, - title=title, - defaultno=defaultno, - width=0, height=0, - backtitle="Appuyez sur MAJ pour selectionner du texte" - ) == self.dialog.DIALOG_OK - - def display_item(self, item, title, **params): - """Affiche un item""" - return self.dialog.msgbox( - printing.sprint(item, **params), - no_collapse=True, - colors=True, - timeout=self.timeout, - title=title, - width=0, height=0, - backtitle="Appuyez sur MAJ pour selectionner du texte" - ) - - # On a besoin du décorateur ici car select va retourner un item après avoir - # possblement traiter plusieurs tailcall - @tailcaller - def select(self, objectClassS, title, values={}, cont=None, disable_field=[]): - """Permet de choisir un objet adhérent ou machine dans la base ldap""" - try: - # On fait effectuer une recherche à l'utilisateur - values, items = self.search(objectClassS, title, values, cont=cont, disable_field=disable_field) - # S'il n'y a pas de résultas, on recommence - if not items: - self.dialog.msgbox("Aucun Résultat", timeout=self.timeout, title="Recherche", width=0, height=0) - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) - # S'il y a plusieurs résultats - elif len(items)>1: - # On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select - return self.select_one(items, title, cont=TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) - # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur - elif len(items) == 1: - item=items[0] - # On fait confirmer son choix à l'utilisateur - if self.confirm_item(item, title): - return item - else: - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) - except self.error_to_raise: - raise - except Exception as e: - self.dialog.msgbox(traceback.format_exc() if debug_enable else "%r" % e, timeout=self.timeout, title="Erreur rencontrée", width=0, height=0) - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont)) - - def machine_information(self, cont, machine=None, objectClass=None, proprio=None, realm=None, fields_values=None): - """ - Permet de modifier une machine si elle est fournit par le paramètre machine - sinon, crée une machine à partir de proprio, objectClass et realm. - Si on ne fait qu'éditer une machine, proprio, objectClass et realm sont ignoré - D'une machinère générale, il faudrait mettre ici tous les attributs single value - et les multivalué que l'on peut simplement représenter de façon textuelle avec - un séparateur. - Pour le moment il y a : - * host - * macAddress - * ipHostNumber - * port(TCP|UDP)(in|out) - """ - a = attributs - # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant - to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15), - (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50), - (a.portUDPin, 50) - ] - - # Quel séparateur on utilise pour les champs multivalué - separateur = ' ' - - def box(): - if machine: - attrs = dict((k,[str(a) for a in at]) for k,at in machine.items()) - else: - attrs = {} - - fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display] - - return self.dialog.form( - text="", - timeout=self.timeout, - height=0, width=0, form_height=0, - fields=fields_values if fields_values else fields, - title="Paramètres machine", - backtitle="Gestion des machines du Crans") - - def check_host(host, objectClass): - # Si c'est une machine wifi, host doit finir par wifi.crans.org - if "machineWifi" == objectClass or 'borneWifi' == objectClass: - hostend = ".wifi.crans.org" - # Si c'est une machine wifi, host doit finir par crans.org - elif "machineFixe" == objectClass: - hostend = ".crans.org" - # Si l'object class est machineCrans, pas de vérification - elif "machineCrans" == objectClass: - return host - # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer - # plus finement fonction des droits de self.conn.droits - else: - raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass) - - if not host.endswith(hostend) and not '.' in host: - host = "%s.wifi.crans.org" % host - elif host.endswith(hostend) and '.' in host[:-len(hostend)]: - raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) - elif not host.endswith(hostend) and '.' in host: - raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) - - return host - - def modif_machine(machine, attrs): - with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: - for (key, values) in attrs.items(): - machine[key]=values - machine.validate_changes() - machine.history_gen() - machine.save() - return machine - - def create_machine(proprio, realm, attrs): - # Dans ce cas, on a besoin d'un proprio et d'un realm pour déterminer le rid - if proprio is None or realm is None: - raise EnvironmentError("On essaye de créer une machine mais proprio ou realm vaut None") - ldif = { - 'macAddress': ['%s' % attrs['macAddress']], - 'host': ['%s' % attrs['host']] - } - with self.conn.newMachine(proprio.dn, realm, ldif) as machine: - for (key, values) in attrs.items(): - machine[key]=values - if attributs.ipsec in machine.attribs: - machine[attributs.ipsec.ldap_name]=attributs.ipsec.default - machine.validate_changes() - if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"): - machine.create() - self.display_item(machine, "La machine à bien été créée", ipsec=True) - return machine - else: - raise Continue(cont) - - def todo(to_display, tags, objectClass, machine, proprio, realm, separateur, cont): - attrs = {} - # On traite les valeurs reçues - for ((a,l),values) in zip(to_display, tags): - values = unicode(values, 'utf-8') - # Si le champs n'est pas single value, on utilise separateur pour découper - # et on ne garde que les valeurs non vides - if not a.singlevalue: - values = [v for v in values.split(separateur) if v] - # Pour host, on fait quelques vérification de syntaxe - if a.ldap_name == 'host': - attrs[a.ldap_name]=check_host(values, objectClass) - else: - attrs[a.ldap_name]=values - # Soit on édite une machine existante - if machine: - machine = modif_machine(machine, attrs) - # Soit on crée une nouvelle machine - else: - machine = create_machine(proprio, realm, attrs) - raise Continue(cont(machine=machine)) - - - if machine: - objectClass = machine["objectClass"][0] - - (code, tags) = self.handle_dialog(cont, box) - - # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu - # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré - # c'est au cableur de corriger ou d'annuler - fields_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)] - retry_cont = TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, proprio=proprio, realm=realm, fields_values=fields_values) - - return self.handle_dialog_result( - code=code, - output=tags, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, tags, objectClass, machine, proprio, realm, separateur, cont])] - ) - - def modif_machine_blacklist(self, machine, cont): - """Raccourci vers edit_blacklist spécifique aux machines""" - return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont) - - def certificat_tlsa(self, certificat, cont, values=None): - """Menu d'éditions des paramètres TLSA d'un certificat""" - separateur = ' ' - form = { - 'Type de certificat' : {'ldap_name' : 'certificatUsage', 'text':"".join(str(s) for s in certificat.get('certificatUsage', [])), 'len':1}, - 'Type de correspondance' : {'ldap_name' : 'matchingType', 'text':"".join(str(s) for s in certificat.get('matchingType', [])), 'len':1}, - 'Ports TCP' : {'ldap_name' : 'portTCPin', 'text':separateur.join(str(p) for p in certificat.get('portTCPin', [])), 'len':30}, - 'Ports UDP' : {'ldap_name' : 'portUDPin', 'text':separateur.join(str(p) for p in certificat.get('portUDPin', [])), 'len':30}, - } - form_order = ['Type de certificat', 'Type de correspondance', 'Ports TCP', 'Ports UDP'] - def box(fields_values=None): - fields = [("%s : " % k, form[k]['text'], form[k]['len'] + 1, form[k]['len']) for k in form_order] - return self.dialog.form( - text="""Type de certificat : Type de correspondance : - * 0 - CA pinning * 0 - certificat entier - * 1 - Cert pinning * 1 - sha256 - * 2 - CA auto signé * 2 - sha512 - * 3 - Cert autosigné""", - no_collapse=True, - height=0, width=0, form_height=0, - timeout=self.timeout, - fields=fields_values if fields_values else fields, - title="Paramètres TLS d'un certificat de la machine %s" % certificat.machine()['host'][0], - backtitle="Gestion des certificats des machines du Crans") - - def todo(form, values, certificat, cont): - if not values['certificatUsage'] in ['0', '1', '2', '3']: - raise ValueError("""Type de certificat invalide : -les valeurs valident sont : - * 0 pour du CA pinning - (le certificat doit être une autorité de certification valide) - * 1 pour du certificat pinning - (le certificat doit déjà être validé par les navigateur) - * 2 pour ajouter un CA - (pour les autorité de certification autosigné) - * 3 pour les certificats autosigné""" -) - if not values['matchingType'] in ['0', '1', '2']: - raise ValueError("""Type de correspondance invalide : -les valeurs valident sont : - * 0 le certificat sera mis entièrement dans le dns - * 1 le sha256 du certificat sera mis dans le dns - * 2 le sha512 du certificat sera mis dans le dns""" -) - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - if "TLSACert" in certificat['objectClass']: - certificat['certificatUsage'] = unicode(values['certificatUsage']) - certificat['matchingType'] = unicode(values['matchingType']) - else: - certificat.tlsa(values['certificatUsage'], values['matchingType']) - certificat['portTCPin'] = [unicode(s, 'utf-8') for s in values['portTCPin'].split(separateur) if s] - certificat['portUDPin'] = [unicode(s, 'utf-8') for s in values['portUDPin'].split(separateur) if s] - certificat.history_gen() - certificat.save() - raise Continue(cont(certificat=certificat)) - - (code, output) = self.handle_dialog(cont, box, values) - values = dict(zip([form[k]['ldap_name'] for k in form_order], output)) - fields_values = [("%s : " % k, values.get(form[k]['ldap_name'], ""), form[k]['len'] + 1, form[k]['len']) for k in form_order] - self_cont=TailCall(self.certificat_tlsa, certificat=certificat, cont=cont, values=fields_values) - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [form, values, certificat, cont])] - ) - - def create_certificat(self, cont, machine=None, certificat=None): - """Permet d'ajouter un certificat à une machine à partir du PEM du certificat""" - if machine is None and certificat is None: - raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") - # input multiline en utilisant un editbox - def box(): - fp, path = tempfile.mkstemp() - os.close(fp) - cmd = ['--editbox', path, "0", "0"] - (code, output) = self.dialog._perform(*(cmd,), - no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic - backtitle="Appuyez sur CTRL+MAJ+V pour coller", - timeout=self.timeout, - title="Création d'un certificat, entrez le PEM du certificat") - os.remove(path) - if code == self.dialog.DIALOG_OK: - return code, output - else: - return code, None - - def todo(machine, certificat, cont): - if certificat: - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - certificat['certificat'] = unicode(pem.strip(), 'utf-8') - certificat.history_gen() - certificat.save() - else: - with self.conn.newCertificat(machine.dn, {}) as certificat: - certificat['certificat'] = unicode(pem.strip(), 'utf-8') - certificat.create() - raise Continue(cont(certificat=certificat, machine=certificat.machine())) - - (code, pem) = self.handle_dialog(cont, box) - self_cont = TailCall(self.create_certificat, machine=machine, certificat=certificat, cont=cont) - return self.handle_dialog_result( - code=code, - output=pem, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, cont])] - ) - - @tailcaller - def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs): - """ - Affiche une série d'inpuxbox pour faire entrer un mot de passe puis le retourne, - si confirm=True, il y a une confirmation du mot de passe de demandée - """ - def todo(self_cont, cont): - (code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs) - if code != self.dialog.DIALOG_OK: - raise Continue(cont) - elif not pass1: - raise ValueError("Mot de pass vide !") - if confirm: - (code, pass2) = self.dialog.passwordbox("Comfirmer le mot de passe", timeout=self.timeout, title=title, **kwargs) - if code != self.dialog.DIALOG_OK: - raise Continue(self_cont) - if pass1 != pass2: - raise ValueError("Les deux mots de passe ne concordent pas") - return pass1 - self_cont = TailCall(self.get_password, cont=cont) - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [self_cont, cont])] - ) - - - - def create_privatekey(self, cont, machine=None, certificat=None, imp=False, size=4096): - """Permet de générer ou importer une clef privée à une machine""" - if machine is None and certificat is None: - raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") - # input multiline en utilisant un editbox - def box(): - fp, path = tempfile.mkstemp() - os.close(fp) - cmd = ['--editbox', path, "0", "0"] - (code, output) = self.dialog._perform(*(cmd,), - no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic - backtitle="Appuyez sur CTRL+MAJ+V pour coller", - timeout=self.timeout, - title="Création d'un certificat, entrez le PEM du certificat") - os.remove(path) - if code == self.dialog.DIALOG_OK: - return code, output - else: - return code, None - - def todo(machine, certificat, pem, imp, size, cont): - if not imp: - if not machine: - machine=certificat.machine() - if "machineCrans" in machine['objectClass']: - passphrase = secrets.get('privatekey_passphrase') - else: - self.dialog.msgbox("Vous aller être inviter à entrez un mot de passe. Ce mot de passe est utilisé pour chiffrer la clef privée qui va être générée dans la base de donnée du crans.\n\nCe mot de passe n'est pas conservé, sous quelque forme que se soit par le crans.\nAussi, en cas de perte, la clef privée deviendrait inutilisable.\n Pensez à le sauvegarder quelque part", - title="Génération d'une clée privée", - width=70, timeout=self.timeout, - height=12) - passphrase = self.get_password(cont) - self.dialog.infobox("Génération d'une clef privée RSA de taille %s en cours.\nMerci de patienter" % size) - pem = crypto.PKey() - pem.generate_key(crypto.TYPE_RSA, size) - pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, pem, "des3", passphrase) - elif not pem.startswith("-----BEGIN ENCRYPTED PRIVATE KEY-----"): - raise ValueError("On n'accepte que des clef chiffrée PKCS#8 en PEM. Donc la clef doit commencer par -----BEGIN ENCRYPTED PRIVATE KEY-----") - if certificat: - if "privatekey" in certificat: - raise ValueError("Il y a déjà une clef privée, merci d'annuler") - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - certificat.private(pem, encrypted=True) - certificat.history_gen() - certificat.save() - self.dialog.msgbox("Clef privée bien ajouté", timeout=self.timeout, title="Ajout d'une clef privée") - raise Continue(cont(certificat=certificat, machine=certificat.machine())) - else: - with self.conn.newCertificat(machine.dn, {}) as certificat: - certificat['hostCert']=unicode(machine['host'][0]) - certificat.private(pem, encrypted=True) - certificat.create() - self.dialog.msgbox("Clef privée créée avec succès", timeout=self.timeout, title="Création d'une clef privée") - raise Continue(cont(certificat=certificat, machine=certificat.machine())) - - if imp: - (code, pem) = self.handle_dialog(cont, box) - else: - (code, pem) = (self.dialog.DIALOG_OK, "") - self_cont = TailCall(self.create_privatekey, machine=machine, certificat=certificat, cont=cont, imp=imp, size=size) - return self.handle_dialog_result( - code=code, - output=pem, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, imp, size, cont])] - ) - - def delete_certificat(self, certificat, cont): - """Supprime un certificat""" - def todo(certificat, cont): - if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"): - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - certificat.delete() - self.dialog.msgbox("Le certificat a bien été supprimé", timeout=self.timeout, title="Suppression d'un certificat") - raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True))) - else: - raise Continue(cont(certificat=certificat)) - - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont, - error_cont=TailCall(self.delete_certificat, certificat=certificat, cont=cont), - codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, cont])] - ) - - - def gen_csr(self, certificat, cont): - """Permet de générer un csr à partir de la clef privée du certificat""" - def todo(certificat, self_cont, cont): - if certificat['encrypted']: - if "machineCrans" in certificat.machine()["objectClass"]: - passphrase = secrets.get('privatekey_passphrase') - else: - self.dialog.msgbox("Mercie de fournir le mot de passe chiffrant la clef privée.\nIl a été choisis lors de la création de la clef.", - title="Génération d'un CSR", - width=70, - height=10, timeout=self.timeout) - passphrase = self.get_password(cont, confirm=False) - else: - passphrase = None - - try: - if passphrase: - pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0]), passphrase) - else: - pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0])) - except crypto.Error as e: - if len(e.message) > 2 and len(e.message[2]) > 2 and e.message[2][2] == 'bad password read': - self.dialog.msgbox("Mauvais mot de passe", timeout=self.timeout) - raise Continue(self_cont) - else: - raise - - req = createCertRequest(pkey, - digest="sha1", - subjectAltName=[str(host) for host in certificat['hostCert'][1:]], - C=u"FR", - ST=u"Ile de France", - L=u"Cachan", - O=u"Association Cachan Réseaux A Normal SUP (C.R.A.N.S)", - OU=u"Crans", - CN=unicode(certificat['hostCert'][0]), - ) - csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - certificat['csr']=unicode(csr) - certificat.history_gen() - certificat.save() - self.handle_dialog(cont, box, csr) - if self.dialog.yesno("Remplacer le certificat actuel ?", timeout=self.timeout) == self.dialog.DIALOG_OK: - return self.create_certificat(certificat=certificat, cont=cont(certificat=certificat)) - else: - raise Continue(cont(certificat=certificat)) - - self_cont = TailCall(self.gen_csr, certificat=certificat, cont=cont) - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, self_cont, cont])] - ) - - def get_certificat(self, certificat, cont, privatekey=False, csr=False): - """Permet d'afficher le certificat courant""" - def box(text): - fp, path = tempfile.mkstemp() - os.write(fp, text) - os.close(fp) - self.dialog.textbox(filename=path, height=0, width=0, - backtitle="Appuyez sur CTRL+MAJ+V pour coller", - title="Récupération d'un certificat", - no_mouse=True, timeout=self.timeout,) - os.remove(path) - return - if privatekey: - self.handle_dialog(cont, box, unicode(certificat['privatekey'][0])) - elif csr: - self.handle_dialog(cont, box, unicode(certificat['csr'][0])) - else: - self.handle_dialog(cont, box, unicode(ssl.DER_cert_to_PEM_cert(str(certificat['certificat'][0])))) - raise Continue(cont) - - def create_csr(self, cont, machine=None, certificat=None): - """Permet d'ajouter un csr à une machine à partir du PEM du csr""" - if machine is None and certificat is None: - raise EnvironmentError("Il faut fournir au moins une machine ou un certificat") - # input multiline en utilisant un editbox - def box(): - fp, path = tempfile.mkstemp() - os.close(fp) - cmd = ['--editbox', path, "0", "0"] - (code, output) = self.dialog._perform(*(cmd,), - no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic - backtitle="Appuyez sur CTRL+MAJ+V pour coller", - timeout=self.timeout, - title="Création d'un certificat, entrez le PEM du certificat") - os.remove(path) - if code == self.dialog.DIALOG_OK: - return code, output - else: - return code, None - - def todo(machine, certificat, pem, cont): - if certificat: - with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: - certificat['csr'] = unicode(pem.strip(), 'utf-8') - certificat.history_gen() - certificat.save() - else: - with self.conn.newCertificat(machine.dn, {}) as certificat: - certificat['hostCert']=unicode(machine['host'][0]) - certificat['csr'] = unicode(pem.strip(), 'utf-8') - certificat.create() - raise Continue(cont(certificat=certificat, machine=certificat.machine())) - - (code, pem) = self.handle_dialog(cont, box) - self_cont = TailCall(self.create_csr, machine=machine, certificat=certificat, cont=cont) - return self.handle_dialog_result( - code=code, - output=pem, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, cont])] - ) - - def modif_machine_certificat(self, machine, cont, tag=None, certificat=None): - """Permet l'édition d'un certificat d'une machine""" - self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat) - if certificat is None: - certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont) - if certificat_index == 'new': - raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont)) - elif certificat_index == 'priv': - raise Continue(TailCall(self.create_privatekey, machine=machine, cont=self_cont)) - elif certificat_index == 'csr': - raise Continue(TailCall(self.create_csr, machine=machine, cont=self_cont)) - certificat = machine.certificats()[certificat_index] - a = attributs - menu_droits = { - 'Hostname':[a.parent, a.nounou], - 'AddPrivateKey':[a.parent, a.nounou], - 'AddCertificate':[a.parent, a.nounou], - 'SetCertificate':[a.parent, a.nounou], - 'TLSA':[a.parent, a.nounou], - 'Autre':[a.nounou], - 'GetPriv':[a.parent, a.nounou], - 'GetCert':[a.parent, a.nounou, a.cableur], - 'GetCSR':[a.parent, a.nounou, a.cableur], - 'GenCSR':[a.parent, a.nounou], - 'Remarque':[a.parent, a.nounou, a.cableur], - 'Supprimer':[a.parent, a.nounou], - } - menu = { - 'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "help":'Il doivent être inclus dans les host et hostAlias de la machine parente', "attribut":attributs.hostCert}, - 'AddPrivateKey' : {'text': 'Ajouter la clef privée', 'help':'La clef doit être obligatoirement chiffrée avant envoi', "callback":TailCall(self.create_privatekey, imp=True)}, - 'AddCertificate' : {'text': 'Ajouter un certificat X509', 'help':'', "callback":self.create_certificat}, - 'SetCertificate' : {'text': 'Remplacer le certificat X509', 'help':'', "callback":self.create_certificat}, - 'TLSA' : {'text':"Paramètres pour les champs dns TLSA", 'help':'Permet de configurer DANE pour le certificat X509', "callback":self.certificat_tlsa}, - 'Autre': {'text' : "Modifier les attribut booléen comme revocked", 'help':'', "callback":self.modif_certificat_boolean}, - 'GetPriv' : {'text' : 'Récupérer la clef privée', 'help':"Affiche la clef privée telle qu'elle est dans la base de donnée, c'est à dire chiffrée", 'callback':TailCall(self.get_certificat, privatekey=True)}, - 'GetCert' : {'text' : 'Récupérer le certificat', 'help':"Affiche le certificat au format PEM", 'callback':self.get_certificat}, - 'GetCSR' : {'text' : 'Récupérer la requête de signature de certificat', 'help':"Affiche le CSR au format PEM", 'callback':TailCall(self.get_certificat, csr=True)}, - 'GenCSR' : {'text' : 'Générer un CSR, puis remplacer le certificat', 'help':'Généré à partir de la clef privée. Les noms (CN et subjectAltName) sont pris à partir de Hostname (attribut hostCert)', "callback":self.gen_csr}, - 'Remarque' : {'text': 'Mettre des remarques', 'help':'La première apparait dans la liste des certificats', 'attribut':attributs.info}, - 'Supprimer' : {'text' : "Supprimer le certificat", 'help':'', "callback":self.delete_certificat}, - } - if "privateKey" in certificat["objectClass"]: - menu - menu_order = ['Hostname'] - if not "privateKey" in certificat['objectClass']: - menu_order.extend(['AddPrivateKey', 'SetCertificate']) - if not "x509Cert" in certificat['objectClass']: - menu_order.extend([ 'AddCertificate']) - if "x509Cert" in certificat['objectClass']: - menu_order.extend(['TLSA', 'Autre', 'GetCert']) - if certificat['csr']: - menu_order.extend(['GetCSR']) - if "privateKey" in certificat['objectClass']: - if attributs.nounou in self.conn.droits or machine.dn.startswith(self.conn.dn): - menu_order.extend(['GetPriv']) - menu_order.extend(['GenCSR']) - menu_order.extend(['Remarque', 'Supprimer']) - def box(default_item=None): - text="Certificat de %s, xid=%s :\n" % (certificat['hostCert'][0], certificat['xid'][0]) - if "x509Cert" in certificat['objectClass']: - text += " * Certificat N°0x%X émis par %s, valable du %s au %s\n" % ( - int(str(certificat['serialNumber'][0])), - certificat['issuerCN'][0], - time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))), - time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0]))) - ) - if "privateKey" in certificat['objectClass']: - text += " * Clef privée\n" - if certificat['csr']: - text += " * Requête de signature de certificat\n" - if certificat['info']: - text += str(certificat['info'][0]) - return self.dialog.menu( - text, - width=0, - height=0, - menu_height=0, - item_help=1, - timeout=self.timeout, - default_item=str(default_item), - title="Modification des certificats de %s" % certificat.machine()['host'][0], - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(key, menu[key]['text'], menu[key]['help']) for key in menu_order if self.has_right(menu_droits[key], certificat)]) - - def todo(tag, menu, certificat, self_cont): - if not tag in menu_order: - raise Continue(self_cont(certificat=certificat)) - else: - if 'callback' in menu[tag]: - raise Continue(TailCall(menu[tag]['callback'], certificat=certificat, cont=self_cont(certificat=certificat, tag=tag))) - elif 'attribut' in menu[tag]: - raise Continue(TailCall(self.modif_certificat_attributs, certificat=certificat, cont=self_cont(certificat=certificat, tag=tag), attr=menu[tag]['attribut'].ldap_name)) - else: - raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - cancel_cont = cont(machine=machine) if certificat is None else self_cont(machine=certificat.machine(), certificat=None, tag=tag) - (code, tag) = self.handle_dialog(cancel_cont, box, tag) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cancel_cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, certificat, self_cont])] - ) - - @tailcaller - def edit_certificat_select(self, machine, title, cont): - """Permet de choisir un certificat existant ou nouveau d'une machine""" - a = attributs - menu_droits = { - 'new':[a.parent, a.nounou], - 'priv':[a.parent, a.nounou], - 'csr':[a.parent, a.nounou], - } - menu = { - 'new':'Ajouter un nouveau certificat', - 'priv':'Générer une nouvelle clef privée', - 'csr':'Ajouter une nouvelle requête de certificat', - } - menu_order = ['new', 'priv', 'csr'] - menu_special = ['new', 'priv', 'csr'] - def box(default_item=None): - index=0 - choices = [] - for key in menu_order: - if self.has_right(menu_droits[key], machine): - choices.append((key, menu[key])) - for cert in machine.certificats(): - if cert['info']: - item = str(cert['info'][0]) - elif "x509Cert" in cert['objectClass']: - item = "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0])))) - elif "privateKey" in cert['objectClass']: - item = "Clef privée de %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0]) - elif cert['csr']: - item = "Requête de signature de certificat pour %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0]) - choices.append((str(index), item)) - index+=1 - return self.dialog.menu( - "Modifier ou ajouter un certificat ?", - width=0, - height=0, - timeout=self.timeout, - menu_height=0, - item_help=0, - title="Modification des certificats de %s" % machine['host'][0], - scrollbar=True, - default_item=str(default_item), - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=choices) - - def todo(tag): - if tag in ['new', 'priv', 'csr']: - return tag - else: - return int(tag) - - (code, tag) = self.handle_dialog(cont, box) - retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont(machine=machine), - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])] - ) - - def modif_machine_attributs(self, machine, attr, cont): - """Juste un raccourci vers edit_attributs spécifique aux machines""" - return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont) - - def modif_adherent_attributs(self, adherent, attr, cont): - """Juste un raccourci vers edit_attributs spécifique aux adherents""" - return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont) - - def modif_proprio_attributs(self, proprio, attr, cont): - """Juste un raccourci vers edit_attributs spécifique aux proprios""" - return self.edit_attributs(obj=proprio, update_obj='proprio', attr=attr, title="Modification de %s %s" % (proprio.get('prenom', [''])[0], proprio['nom'][0]), cont=cont) - - def modif_certificat_attributs(self, certificat, attr, cont): - """Juste un raccourci vers edit_attributs spécifique aux certificats""" - return self.edit_attributs(obj=certificat, update_obj='certificat', attr=attr, title="Modification d'un certificat de la machine %s" % certificat.machine()['host'][0], cont=cont) - - def modif_machine_boolean(self, machine, cont): - """Juste un raccourci vers edit_boolean_attributs spécifique aux machines""" - a = attributs - attribs = [a.dnsIpv6] - return self.edit_boolean_attributs( - obj=machine, - attribs=attribs, - title="Édition des attributs booléen de la machine %s" % machine['host'][0], - update_obj='machine', - cont=cont) - - def modif_certificat_boolean(self, certificat, cont): - """Juste un raccourci vers edit_boolean_attributs spécifique aux certificats""" - a = attributs - attribs = [a.revocked] - return self.edit_boolean_attributs( - obj=certificat, - attribs=attribs, - title="Édition des attributs booléen d'un certificat de la machine %s" % certificat.machine()['host'][0], - update_obj='certificat', - cont=cont) - - def modif_machine(self, cont, machine=None, tag=None): - """ - Permet d'éditer une machine. Si fournie en paramètre on éditer en place, - sinon, on en cherche une dans la base ldap - """ - if machine is None: - machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont) - a = attributs - menu_droits = { - 'Information' : [a.parent, a.cableur, a.nounou], - 'Autre': [a.parent, a.cableur, a.nounou], - 'Blackliste':[a.cableur, a.nounou], - 'Certificat': [a.parent, a.cableur, a.nounou], - 'Exemption' : [a.nounou], - 'Alias' : [a.parent, a.cableur, a.nounou], - 'Remarques' : [a.cableur, a.nounou], - 'SshKey' : [a.parent, a.nounou], - 'Supprimer' : [a.parent, a.cableur, a.nounou], - } - menu = { - 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information}, - 'Autre' : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean}, - 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist}, - 'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat}, - 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt}, - 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias}, - 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, - 'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint}, - 'Supprimer' : {'text':'Supprimer la machine', 'callback':self.delete_machine}, - } - menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques', 'Supprimer'] - def box(default_item=None): - return self.dialog.menu( - "Que souhaitez vous modifier ?", - width=0, - height=0, - menu_height=0, - timeout=self.timeout, - item_help=0, - default_item=str(default_item), - title="Modification de %s" % machine['host'][0], - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], machine)]) - - def todo(tag, menu, machine, cont_ret): - if not tag in menu_order: - raise Continue(cont_ret) - else: - if 'callback' in menu[tag]: - raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret)) - elif 'attribut' in menu[tag]: - raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) - else: - raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - - (code, tag) = self.handle_dialog(cont, box, tag) - cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) - - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont(machine=machine), - error_cont=cont_ret, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, machine, cont_ret])] - ) - - def create_machine_proprio(self, cont, proprio, tag=None): - """Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)""" - a = attributs - menu_droits = { - 'Fixe' : [a.soi, a.cableur, a.nounou], - 'Wifi' : [a.soi, a.cableur, a.nounou], - } - menu = { - 'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'}, - 'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'}, - } - menu_order = ['Fixe', 'Wifi'] - if isinstance(proprio, objets.AssociationCrans): - menu_droits.update({ - 'Fixe' : [a.nounou], - 'Wifi' : [a.nounou], - 'Adm' : [a.nounou], - }) - menu.update({ - 'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'}, - 'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'}, - 'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'}, - }) - menu_order.append('Adm') - def box(default_item=None): - return self.dialog.menu( - "Type de Machine ?", - width=0, - height=0, - menu_height=0, - item_help=0, - timeout=self.timeout, - default_item=str(default_item), - title="Création de machines", - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], proprio)]) - - def todo(tag, menu, proprio, self_cont, cont): - if not tag in menu_order: - raise Continue(self_cont) - else: - return self.machine_information( - cont=cont, - machine=None, - objectClass=menu[tag]['objectClass'], - proprio=proprio, - realm=menu[tag]['realm'] - ) - - (code, tag) = self.handle_dialog(cont, box, tag) - cont = cont(proprio=None) if isinstance(proprio, objets.AssociationCrans) else cont(proprio=proprio) - self_cont = TailCall(self.create_machine_proprio, cont=cont, proprio=proprio, tag=tag) - - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont, cont])] - ) - - def create_machine_adherent(self, cont, adherent=None): - """ - Permet d'ajouter une machine à un adhérent. - On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) - """ - if adherent is None: - adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont) - return self.create_machine_proprio(cont=cont, proprio=adherent) - - def delete_machine(self, cont, machine=None): - """Permet la suppression d'une machine de la base ldap""" - if machine is None: - machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) - - def todo(machine): - if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True): - with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: - machine.delete() - self.dialog.msgbox("La machine a bien été supprimée", timeout=self.timeout, title="Suppression d'une machine") - raise Continue(cont(machine=None)) - else: - raise Continue(cont) - - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont, - error_cont=cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [machine])] - ) - - def modif_adherent_blacklist(self, adherent, cont): - """Raccourci vers edit_blacklist spécifique aux adherent""" - return self.edit_blacklist(obj=adherent, title="Éditions des blacklist de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont) - - - def modif_adherent(self, cont, adherent=None, tag=None): - """Menu d'édition d'un adhérent""" - if adherent is None: - adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) - a = attributs - menu_droits = { - 'Administratif' : [a.cableur, a.nounou], - 'Personnel':[a.cableur, a.nounou, a.soi], - 'Études':[a.nounou, a.soi, a.cableur], - 'Chambre':[a.cableur, a.nounou], - 'Compte':[a.cableur, a.nounou], - 'GPGFingerprint' : [a.nounou, a.soi], - 'Remarques' : [a.cableur, a.nounou], - 'Droits':[a.nounou, a.bureau], - 'Blackliste':[a.cableur, a.nounou], - 'Vente':[a.cableur, a.nounou], - 'Supprimer':[a.nounou, a.bureau], - } - menu = { - 'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif}, - 'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel}, - 'Études' : {'text' : "Étude en cours (perso, je pense que c'est à supprimer)", "callback":self.adherent_etudes}, - 'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre}, - 'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":self.proprio_compte, 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"}, - 'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint}, - 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, - 'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits}, - 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_adherent_blacklist}, - 'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente}, - 'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))}, - } - menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte'] - menu_compte_crans = ['Droits'] - medu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Supprimer'] - - if "cransAccount" in adherent['objectClass']: - menu_order.extend(menu_compte_crans) - menu_order.extend(medu_end) - def box(default_item=None): - choices = [] - for key in menu_order: - if self.has_right(menu_droits[key], adherent): - choices.append((key, menu[key]['text'], menu[key].get('help', ""))) - return self.dialog.menu( - "Que souhaitez vous modifier ?", - width=0, - height=0, - menu_height=0, - timeout=self.timeout, - item_help=1, - default_item=str(default_item), - title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=choices) - - def todo(tag, menu, adherent, cont_ret): - if not tag in menu_order: - raise Continue(cont_ret) - else: - if 'callback' in menu[tag]: - raise Continue(TailCall(menu[tag]['callback'], cont=cont_ret, **{menu[tag].get('adherent', 'adherent'):adherent})) - elif 'attribut' in menu[tag]: - raise Continue(TailCall(self.modif_adherent_attributs, adherent=adherent, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) - else: - raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - - (code, tag) = self.handle_dialog(cont, box, tag) - cont_ret = TailCall(self.modif_adherent, cont=cont, adherent=adherent, tag=tag) - - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont(proprio=adherent), - error_cont=cont_ret, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])] - ) - - def adherent_administratif(self, cont, adherent, default_item=None): - """Menu de gestion du compte crans d'un proprio""" - - a = attributs - menu_droits = { - "Adhésion": [a.cableur, a.nounou], - 'Connexion': [a.cableur, a.nounou], - "Charte MA" : [a.nounou, a.bureau], - "Carte Étudiant" : [a.nounou, a.cableur, a.tresorier], - } - menu = { - "Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion}, - 'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion}, - "Carte Étudiant" : {"text" : "Validation de la carte étudiant", "help":"", "callback":self.adherent_carte_etudiant}, - "Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte}, - } - menu_order = ["Adhésion", 'Connexion'] - if self.has_right(a.tresorier, adherent) or not adherent.carte_controle(): - menu_order.append("Carte Étudiant") - menu_order.append("Charte MA") - def box(default_item=None): - return self.dialog.menu( - "Quelle action administrative effectuer", - width=0, - height=0, - timeout=self.timeout, - item_help=1, - default_item=str(default_item), - title="Gestion administrative de %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]), - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) - - def todo(tag, menu, adherent, self_cont): - if not tag in menu_order: - raise Continue(self_cont) - elif 'callback' in menu[tag]: - raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, adherent=adherent)) - else: - raise EnvironmentError("Il n'y a pas de champs 'callback' pour le tag %s" % tag) - - - (code, tag) = self.handle_dialog(cont, box, default_item) - self_cont = TailCall(self.adherent_administratif, adherent=adherent, cont=cont, default_item=tag) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont])] - ) - - def adherent_adhesion(self, cont, adherent): - self.dialog.msgbox("todo", width=0, height=0) - return cont - def adherent_connexion(self, cont, adherent, cancel_cont=None): - self.dialog.msgbox("todo", width=0, height=0) - return cont - def adherent_carte_etudiant(self, cont, adherent, values={}, cancel_cont=None): - # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut - a = attributs - choices = [] - if self.has_right(a.tresorier, adherent) or not adherent.carte_controle(): - choices.append((a.carteEtudiant.ldap_name, "Carte étudiant présentée", 1 if adherent[a.carteEtudiant.ldap_name] or values.get(a.carteEtudiant.ldap_name, False) else 0)) - if self.has_right(a.tresorier, adherent): - choices.append(("controleCarte", "La carte a-t-elle été controlée", 1 if adherent.carte_controle() or values.get("controleCarte", False) else 0)) - - if not choices: - self.dialog.msgbox("Carte d'étudiant déjà validée et non modifiable", title="Gestion de la carte étudiant", width=0, height=0) - if cancel_cont: - cancel_cont(cont=cont) - try: - cont(cancel_cont=cancel_cont) - except TypeError: - pass - raise Continue(cont) - - def box(): - return self.dialog.checklist("Gestion de la carte étudiant", - height=0, - width=0, - timeout=self.timeout, - list_height=7, - choices=choices, - title="Gestion de la carte étudiant") - - def todo(values, adherent, cont): - # On met à jour chaque attribut si sa valeur à changé - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - # Si on est trésorier et que controleCarte a changer on enregistre le changement - if self.has_right(a.tresorier, adherent) and values["controleCarte"] and not adherent.carte_controle(): - if adherent["controle"]: - adherent["controle"] = u"c%s" % adherent["controle"][0] - else: - adherent["controle"] = u"c" - elif self.has_right(a.tresorier, adherent) and not values["controleCarte"] and adherent.carte_controle(): - adherent["controle"]=unicode(adherent["controle"][0]).replace('c','') - if not adherent["controle"][0]: - adherent["controle"] = [] - # Si la carte n'est pas validé ou qu'on est trésorier, on sauvegarde les changements - if values[a.carteEtudiant.ldap_name] and not adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)): - adherent[a.carteEtudiant.ldap_name] = u"TRUE" - elif not values[a.carteEtudiant.ldap_name] and adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)): - adherent[a.carteEtudiant.ldap_name] = [] - if adherent["controle"]: - adherent["controle"]=unicode(adherent["controle"][0]).replace('c','') - if not adherent["controle"][0]: - adherent["controle"] = [] - adherent.history_gen() - adherent.save() - # On s'en va en mettant à jour dans la continuation la valeur de obj - raise Continue(cont(adherent=adherent)) - - (code, output) = self.handle_dialog(cont, box) - # On transforme la liste des cases dialog cochée en dictionnnaire - values = dict((a[0], a[0] in output) for a in choices) - - # Une continuation que l'on suivra si quelque chose se passe mal - retry_cont = TailCall(self.adherent_carte_etudiant, adherent=adherent, cont=cont, values=values) - - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cancel_cont if cancel_cont else cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [values, adherent, cont])] - ) - def adherent_charte(self, cont, adherent): - a = attributs - attribs = [a.charteMA] - return self.edit_boolean_attributs( - obj=adherent, - attribs=attribs, - title="Signature de la charte membre actif de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), - update_obj='adherent', - cont=cont) - - def adherent_personnel(self, cont, adherent=None, fields_attrs={}, make_compte_crans=None, force_create=False): - """ - Permet d'éditer les nom, prénom et téléphone d'un adhérent, ou de créer un adhérent. - Il faut encore trouver un moyen de récupérer des valeurs pour les attributs mail et chbre - """ - a = attributs - # Quel sont les attributs ldap dont on veut afficher et - # la taille du champs d'édition correspondant - to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)] - non_empty = [a.nom, a.prenom, a.tel] - input_type = {'default':0} - - # Quel séparateur on utilise pour les champs multivalué - separateur = ' ' - - def box(make_compte_crans): - if force_create and adherent is None and fields_attrs and make_compte_crans is not None: - return (self.dialog.DIALOG_OK, [fields_attrs[a] for a,l in to_display], make_compte_crans) - if adherent: - attrs = dict((k,[str(a) for a in at]) for k,at in adherent.items()) - if 'cransAccount' in adherent['objectClass']: - input_type[attributs.mail] = 2 - to_display.append((attributs.mailExt, 30)) - else: - attrs = {} - if make_compte_crans is None: - if self.dialog.yesno("Crééra-t-on un compte crans à l'utilisateur ?", timeout=self.timeout, title="Création d'un adhérent", width=50) == self.dialog.DIALOG_OK: - input_type[attributs.mail] = 2 - make_compte_crans = True - to_display.append((attributs.mailExt, 30)) - else: - make_compte_crans = False - - fields = [( - "%s %s:" % (a.legend, '(optionnel) ' if a.optional else ''), - fields_attrs.get(a, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else []))), - l+1, l, - input_type.get(a, input_type['default']) - ) for a,l in to_display] - - (code, tags) = self.dialog.form( - text="", - timeout=self.timeout, - height=0, width=0, form_height=0, - fields=fields, - title="Création d'un adhérent" if adherent is None else "Édition des informations de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), - backtitle="Gestion des adhérents du Crans") - return (code, tags, make_compte_crans) - - def modif_adherent(adherent, attrs): - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - for (key, values) in attrs.items(): - adherent[key]=values - adherent.validate_changes() - adherent.history_gen() - adherent.save() - return adherent - - def create_adherent(attrs, make_compte_crans, force_create, self_cont, cont): - if not force_create: - items = self.conn.search("(&(prenom=%s)(nom=%s))" % (attrs['prenom'], attrs['nom'])) - if items: - newadherent = self.select_one(items, title="Choisir un adhérant existant", text="Des adhérent avec les même noms et prénoms existent déjà, en utiliser un ?\n(Annuler pour continuer la création)", cont=self_cont(make_compte_crans=make_compte_crans, force_create=True)) - raise Continue(cont(adherent=newadherent)) - with self.conn.newAdherent({}) as adherent: - delay={} - for (key, values) in attrs.items(): - try: - adherent[key]=values - # En cas d'erreur, on a peut être besoin du compte crans - except ValueError: - delay[key]=values - print delay - # on récupère la chambre - adherent = self.adherent_chambre_campus(success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) - # Si c'est EXT, on demande une adresse complète - if 'EXT' in adherent['chbre']: - adherent = self.adherent_chambre_ext(keep_machine=True, keep_compte=True, success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) - # Si compte crans à créer, on le crée. - # On le met en dernier pour éviter de faire entrez plusieurs fois son mdp à l'adhérent - # en cas d'erreur de la part du cableur - if make_compte_crans: - adherent = self.proprio_compte_create(proprio=adherent, cont=self_cont(make_compte_crans=None, force_create=False, adherent=None), update_obj='adherent', return_obj=True) - # On réeaffecte les attributs de tout à l'heure - for (key, values) in delay.items(): - adherent[key]=values - # On confirme la création - if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"): - adherent.validate_changes() - adherent.create() - else: - adherent = None - return adherent - - def todo(to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, self_cont, cont): - attrs = {} - # On traite les valeurs reçues - for ((a,l),values) in zip(to_display, tags): - if not values and a in non_empty: - raise ValueError("%s ne devrait pas être vide" % a.legend) - values = unicode(values, 'utf-8') - # Si le champs n'est pas single value, on utilise separateur pour découper - # et on ne garde que les valeurs non vides - if not a.singlevalue: - values = [v for v in values.split(separateur) if v] - attrs[a.ldap_name]=values - if adherent: - adherent = modif_adherent(adherent, attrs) - else: - adherent = create_adherent(attrs, make_compte_crans, force_create, self_cont, cont) - raise Continue(cont(adherent=adherent)) - - - (code, tags, make_compte_crans) = self.handle_dialog(cont, box, make_compte_crans) - - # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu - # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré - # c'est au cableur de corriger ou d'annuler - fields_attrs = dict((a, values) for ((a,l),values) in zip(to_display, tags)) - retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, fields_attrs=fields_attrs) - - return self.handle_dialog_result( - code=code, - output=tags, - cancel_cont=cont, - error_cont=retry_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, retry_cont, cont])] - ) - - def adherent_etudes(self, adherent, cont, cancel_cont=None): - """Gestion des études de l'adhérent""" - self.dialog.msgbox("todo", width=0, height=0) - return cont - - @tailcaller - def adherent_chambre_campus(self, success_cont, cont, adherent, create=False): - """Permet de faire déménager d'adhérent sur le campus""" - def box(): - return self.dialog.inputbox( - "chambre ?", - title="%s de %s %s" % ("Création" if create else "Déménagement", adherent['prenom'][0], adherent["nom"][0]), - cancel_label="Retour", - width=50, timeout=self.timeout, - ) - - def expulse_squatteur(adherent, chbre): - with self.conn.search(u"chbre=%s" % chbre, mode='rw')[0] as squatteur: - if self.confirm_item( - item=squatteur, - title="Chambre occupée", - defaultno=True, - text=u"L'adhérent ci-dessous occupé déjà la chambre %s :\n" % output, - text_bottom=u"\nPasser la chambre de cet adhérent en chambre inconnue ?" - ): - squatteur['chbre']=u'????' - squatteur.history_gen() - squatteur.save() - return True - else: - return False - - def set_chambre(adherent, chbre): - try: - adherent['postalAddress']=[] - adherent['chbre']=unicode(output, 'utf-8') - except UniquenessError: - if expulse_squatteur(adherent, chbre): - # La chambre est maintenant normalement libre - adherent['chbre']=unicode(output, 'utf-8') - else: - raise Continue(self_cont) - return adherent - - def todo(chbre, adherent, self_cont, success_cont): - if not output: - raise Continue(self_cont) - if output == "????": - raise ValueError("Chambre ???? invalide") - if create: - return set_chambre(adherent, chbre) - else: - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - adherent = set_chambre(adherent, chbre) - adherent.history_gen() - adherent.save() - self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output) - raise Continue(success_cont(adherent=adherent)) - - (code, output) = self.handle_dialog(cont, box) - self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont, create=create) - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, self_cont, success_cont])] - ) - @tailcaller - def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False): - """Permet de faire déménager l'adhérent hors campus""" - if keep_machine and not keep_compte: - raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines") - elif keep_machine and keep_compte: - def box(values={}): - form = [("Adresse", 40), ("Compl. adr.", 40), ("Code postal", 7), ("Ville", 16)] - fields = [("%s :" % k, values.get(k, ""), l, 50) for k,l in form] - return self.dialog.form( - text="", - timeout=self.timeout, - height=0, width=0, form_height=0, - fields=fields, - title="Paramètres machine", - backtitle="Gestion des machines du Crans") - def todo(output, adherent, success_cont, cont): - if not create: - if self.dialog.yesno("changer l'adresse de l'adhérent pour %s ?" % ", ".join([o for o in output if o]), - title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - defaultno=True, timeout=self.timeout) == self.dialog.DIALOG_OK: - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output] - adherent['chbre']=u'EXT' - adherent.history_gen() - adherent.save() - self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines conservées") - raise Continue(success_cont(adherent=adherent)) - else: - raise Continue(cont) - else: - adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output] - adherent['chbre']=u'EXT' - return adherent - elif not keep_machine and keep_compte: - if create: - raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines") - def box(values={}): - return (self.dialog.DIALOG_OK, "") - def todo(output, adherent, success_cont, cont): - if self.confirm_item( - item=adherent, - text=u"Supprimer toutes les machines de l'adhérent ci-dessous ?\n\n", - text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", - title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - defaultno=True): - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - for machine in adherent.machines(): - with machine: - machine.delete() - adherent['chbre']=u'EXT' - adherent.history_gen() - adherent.save() - self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées") - raise Continue(success_cont(adherent=adherent)) - else: - raise Continue(cont) - elif not keep_machine and not keep_compte: - if create: - raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines et un compte") - def box(values={}): - return (self.dialog.DIALOG_OK, "") - def todo(output, adherent, success_cont, cont): - if adherent.get('solde', [0])[0] > 0: - self.dialog.msgbox("Solde de l'adhérent %s€ strictement positif, impossible de supprimer le compte\nRepasser le solde à 0€ pour supprimer le compte." % adherent.get('solde', [0])[0], - title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - width=50, timeout=self.timeout) - raise Continue(cont) - elif self.confirm_item( - item=adherent, - text=u"Supprimer toutes les machines et du compte crans de l'adhérent ci-dessous ?\n\n", - text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", - title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - defaultno=True): - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - for machine in adherent.machines(): - with machine: - machine.delete() - adherent['chbre']=u'EXT' - adherent.history_gen() - adherent.save() - # On supprime le compte crans (on essaye) - def post_deletion(proprio, cont): - if not "cransAccount" in proprio['objectClass']: - self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées") - else: - self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées et compte crans concervé") - raise Continue(cont(adherent=proprio)) - self.proprio_compte_delete(proprio=adherent, cont=TailCall(post_deletion, proprio=adherent, cont=success_cont(adherent=adherent)), force=True) - else: - raise Continue(cont) - else: - raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué") - - (code, output) = self.handle_dialog(cont, box) - self_cont = TailCall(self.adherent_chambre_ext, adherent=adherent, keep_machine=keep_machine, keep_compte=keep_compte, success_cont=success_cont, cont=cont) - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, success_cont, cont])] - ) - - def adherent_chambre(self, adherent, cont, default_item=None): - """Permet de faire déménager d'adhérent""" - a = attributs - menu_droits = { - '0' : [a.cableur, a.nounou], - '1' : [a.cableur, a.nounou], - '2' : [a.cableur, a.nounou], - '3' : [a.cableur, a.nounou], - } - menu = { - "0": {'text':"Déménagement sur le campus", 'callback':self.adherent_chambre_campus, 'help': "Déménagement vers une chambre sur le campus, on ne demande que le bâtiment et la chambre"}, - "1": {'text':"Déménagement à l'extérieur en conservant les machines", "callback": TailCall(self.adherent_chambre_ext, keep_machine=True, keep_compte=True), "help": "Pour concerver ses machines, il faut donner un adresse postale complète"}, - "2": {'text':"Départ du campus en conservant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=True), "help":"Supprime les machines mais concerve le compte crans, l'adresse passe en EXT sans plus d'information"}, - "3": {'text':"Départ du campus en supprimant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=False), "help":"Supprime les machines et le compte crans, l'adhérent reste dans la base de donnée, il est possible de mettre une redirection du mail crans vers une autre adresse dans bcfg2"}, - } - menu_order = [str(i) for i in range(4)] - def box(default_item=None): - return self.dialog.menu( - "Quel est le type du déménagement ?", - width=0, - height=0, - menu_height=0, - timeout=self.timeout, - item_help=1, - default_item=str(default_item), - title="Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) - - def todo(tag, menu, adherent, self_cont, cont): - if not tag in menu_order: - raise Continue(self_cont) - else: - raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent)) - - (code, tag) = self.handle_dialog(cont, box, default_item) - self_cont = TailCall(self.adherent_chambre, adherent=adherent, cont=cont, default_item=tag) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont, cont])] - ) - - @tailcaller - def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'): - """Permet de créer un compte crans à un proprio (club ou adhérent)""" - def box_warning(warning, proprio, cont): - # Affiche-t-on le warning sur la consutation de l'adresse crans - if warning: - if self.dialog.yesno( - text="\Zr\Z1AVERTISSEMENT :\Zn \nL'adhérent devra impérativement consulter l'adresse mail associée\n\n\n\ZnContinuer ?", - title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - defaultno=True, - width=70, - colors=True, timeout=self.timeout) != self.dialog.DIALOG_OK: - raise Continue(cont) - - def get_login(guess_login, guess_pass, proprio, self_cont, cont): - # Essaye-t-on de deviner le login à utiliser - if not guess_login: - (code, login) = self.dialog.inputbox( - text="Le login doit faire au maximum %s caractères\nIl ne doit pas être un pseudo ou prénom mais doit être relié au nom de famille\nSeuls les caractères alphabétiques et le trait d'union sont autorisés" % config.maxlen_login, - title="Choix du login pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - init=str(proprio['nom'][0]).lower(), - width=60, - height=10, timeout=self.timeout) - if code != self.dialog.DIALOG_OK: - raise Continue(cont) - else: - # Si oui, de quelle manière - if guess_pass == 0: - login = str(proprio['nom'][0]) - elif guess_pass == 1 and proprio.get('prenom', [''])[0]: - login = "%s%s" % (str(proprio['prenom'][0])[0], proprio['nom'][0]) - # Si toutes les manières ont échoués, la prochaine fois, ça on n'essaye pas de deviner - else: - raise Continue(self_cont(warning=False, guess_login=False, guess_pass=2)) - return login - - def create_compte(proprio, login, guess_login, self_cont, cont): - try: - proprio.compte(login=unicode(login, 'utf-8')) - except ValueError: - # Il y a eu une erreur, si on essaye de deviner, on essaye la manière suivante - if guess_login: - raise Continue(self_cont(warning=False, guess_login=True, guess_pass=guess_pass+1)) - # Sinon on propage l'erreur pour l'afficher à l'utilisateur - else: - raise - self.dialog.msgbox( - text="Le compte ne sera créé que lors de l'enregistrement des données\n\nL'adresse mail de l'adhérent est : %s\nL'adhérent possède également l'alias :\n%s\n" % (proprio['mail'][0], proprio['canonicalAlias'][0]), - title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - width=75, - height=12, timeout=self.timeout, - ) - return proprio - - @tailcaller - def set_password(proprio, update_obj, cont): - if self.dialog.yesno("Attribuer un mot de passe maintenant ?", - title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - timeout=self.timeout - ) == self.dialog.DIALOG_OK: - #return self.proprio_compte_password(proprio=proprio, return_obj=return_obj, cont=cont(**{update_obj:proprio})) - proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont)) - if return_obj: - return proprio - else: - raise Continue(cont(**{update_obj:proprio})) - elif return_obj: - return proprio - else: - raise Continue(cont(**{update_obj:proprio})) - - def todo(proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont): - box_warning(warning, proprio, cont) - login = get_login(guess_login, guess_pass, proprio, self_cont, cont) - if return_obj: - proprio = create_compte(proprio, login, guess_login, self_cont, cont) - return set_password(proprio, update_obj, cont) - else: - with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: - proprio = create_compte(proprio, login, guess_login, self_cont, cont) - if not self.confirm_item(item=proprio, title="Création du compte crans pour l'adhérent ?"): - raise Continue(cont) - else: - proprio.history_gen() - proprio.save() - self.dialog.msgbox( - text="Compte créé avec succès.", - title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - timeout=self.timeout - ) - return set_password(proprio, update_obj, cont) - - self_cont = TailCall(self.proprio_compte_create, proprio=proprio, cont=cont, warning=warning, guess_login=guess_login, guess_pass=guess_pass) - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont])] - ) - - @tailcaller - def proprio_compte_password(self, proprio, cont, return_obj=False): - """Permet de changer le mot de passe d'un compte crans""" - def test_password(password, self_cont): - (good, msg) = check_password(password, dialog=True) - if not good: - self.dialog.msgbox( - msg, - title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - colors=True, - width=70, timeout=self.timeout) - raise Continue(self_cont) - else: - return True - def todo(passwords, proprio, return_obj, self_cont, cont): - password = self.get_password(cont=cont, - title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - backtitle="Le mot de passe doit être assez difficile") - - if test_password(password, self_cont): - if return_obj: - proprio['userPassword']=unicode(lc_utils.hash_password(password)) - return proprio - else: - with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: - proprio['userPassword']=unicode(lc_utils.hash_password(password)) - proprio.history_gen() - proprio.save() - self.dialog.msgbox( - "Mot de passe changé avec succès", - title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - width=70, timeout=self.timeout - ) - raise Continue(cont(proprio=proprio)) - #(code, passwords) = self.handle_dialog(cont, box) - (code, passwords) = (self.dialog.DIALOG_OK, "") - self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont) - return self.handle_dialog_result( - code=code, - output=passwords, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [passwords, proprio, return_obj, self_cont, cont])] - ) - - @tailcaller - def proprio_compte_delete(self, proprio, cont, force=False): - """Permet la suppression du compte crans d'un proprio""" - def todo(proprio, self_cont, cont): - if force or self.confirm_item(item=proprio, title="Voulez vous vraiement supprimer le compte de %s %s ?" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), defaultno=True): - (code, mail) = self.dialog.inputbox( - text="Il faut choisir une nouvelle adresse de contact.\n(On regarde s'il y a une adresse optionnel)", - title="Choix d'une adresse de contact pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - init=str(proprio.get("mailExt", [""])[0]), - width=50, timeout=self.timeout) - if not code == self.dialog.DIALOG_OK: - raise Continue(cont) - elif not mail: - raise ValueError("Il faut entrer une adresse mail") - with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: - proprio.delete_compte(unicode(mail, 'utf-8')) - proprio.history_gen() - proprio.save() - self.dialog.msgbox("Le compte a bien été supprimée", timeout=self.timeout, title="Suppression du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0])) - raise Continue(cont(proprio=proprio)) - else: - raise Continue(cont) - - self_cont = TailCall(self.proprio_compte_delete, proprio=proprio, cont=cont, force=force) - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont(proprio=proprio), - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, self_cont, cont])] - ) - - def proprio_compte_etat(self, proprio, disable, cont): - """Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire""" - with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: - if disable: - proprio["shadowExpire"]=0 - else: - proprio["shadowExpire"]=[] - proprio.history_gen() - proprio.save() - raise Continue(cont(proprio=proprio)) - - def proprio_compte_shell(self, proprio, cont, choices_values=None): - """Permet de modifier le shell d'un compte crans""" - a = attributs - # Seul les nounous peuvent changer les shells restrictifs - shells_droits = { - 'default' : [a.soi, a.nounou, a.cableur], - 'rbash' : [a.nounou], - 'rssh' : [a.nounou], - 'badPassSh' : [a.nounou], - 'disconnect_shell':[a.nounou], - } - shell = os.path.basename(str(proprio['loginShell'][0])).lower() - shells = config.shells_gest_crans - shells_order = config.shells_gest_crans_order - def box(): - # liste des shell éditables par l'utilisateur - editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)] - # Si l'utilisateur de gest_crans peut éditer le shell courant - # il peut le changer pour un autre shell qu'il peut éditer - if shell in editable_sheels: - choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels] - return self.dialog.radiolist( - text="", - height=0, width=0, list_height=0, - choices=choices_values if choices_values else choices, - title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), - timeout=self.timeout - ) - # Sinon, on affiche un message d'erreur et on annule - else: - self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur", - title="Édition du shell impossible", timeout=self.timeout, width=0, height=0) - return (self.dialog.DIALOG_CANCEL, None) - def todo(output, shell, shells, proprio, self_cont, cont): - loginShell = shells[output]['path'] - if shell and shell != output: - with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: - proprio['loginShell']=unicode(loginShell) - proprio.history_gen() - proprio.save() - self.dialog.msgbox("Shell modifié avec succès.\nLa modification peut prendre une quainzaine de minute avant d'être effective.", - title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), - width=50, timeout=self.timeout, - ) - raise Continue(cont(proprio=proprio)) - - (code, output) = self.handle_dialog(cont, box) - self_cont = TailCall(self.proprio_compte_shell, proprio=proprio, cont=cont, choices_values=[(s, shells[s]['desc'], 1 if s == output else 0) for s in shells_order]) - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [output, shell, shells, proprio, self_cont, cont])] - ) - - def proprio_compte(self, proprio, cont, default_item=None): - """Menu de gestion du compte crans d'un proprio""" - has_compte = 'cransAccount' in proprio['objectClass'] - disabled_compte = has_compte and 0 in proprio['shadowExpire'] - a = attributs - menu_droits = { - "Password": [a.cableur, a.nounou], - 'MailAlias': [a.cableur, a.nounou], - "Activer" : [a.nounou], - "Désactiver" : [a.nounou], - "Créer" : [a.cableur, a.nounou], - "Supprimer" : [a.cableur, a.nounou], - "Shell" : [a.nounou, a.soi, a.cableur], - } - menu = { - "Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password}, - 'MailAlias' : {'text': 'Créer ou supprimer des alias mail', "help":"", 'attribut':attributs.mailAlias}, - "Shell" : {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell}, - "Activer" : {"text" : "Activer le compte pour la connexion mail/serveur", "help":"Permet d'autoriser les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=False)}, - "Désactiver" : {"text" : "Désactiver le compte pour la connexion mail/serveur", "help":"Permet d'interdire les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=True)}, - "Créer" : {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create}, - "Supprimer" : {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete}, - } - menu_order = [] - tag_translate = { - "Créer":"Password", - "Password":"Password", - "Supprimer":"Créer", - "Activer":"Désactiver", - "Désactiver":"Activer", - "Shell":"Shell", - 'MailAlias':'MailAlias', - '':'', - } - if has_compte: - if disabled_compte: - menu_order.append("Activer") - else: - menu_order.append("Désactiver") - menu_order.extend(['MailAlias', "Shell", "Password", "Supprimer"]) - else: - menu_order.append("Créer") - def box(default_item=None): - return self.dialog.menu( - "Quel action effectuer sur le compte ?", - width=0, - height=0, - menu_height=0, - timeout=self.timeout, - item_help=1, - default_item=str(default_item), - title="Gestion du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), - scrollbar=True, - cancel_label="Retour", - backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, - choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)]) - - def todo(tag, menu, proprio, self_cont): - if not tag in menu_order: - raise Continue(self_cont) - elif 'callback' in menu[tag]: - raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio)) - elif 'attribut' in menu[tag]: - raise Continue(TailCall(self.modif_proprio_attributs, proprio=proprio, cont=self_cont, attr=menu[tag]['attribut'].ldap_name)) - else: - raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - - - (code, tag) = self.handle_dialog(cont, box, default_item) - self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate.get(tag, tag)) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont])] - ) - - def adherent_droits(self, adherent, cont, choices_values=None): - """Gestion des droits d'un adhérent""" - def box(): - return self.dialog.checklist( - text="", - height=0, width=0, list_height=0, - choices=choices_values if choices_values else [(droit, "", 1 if droit in adherent["droits"] else 0) for droit in attributs.TOUS_DROITS], - title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), - timeout=self.timeout - ) - def todo(droits, adherent, self_cont, cont): - # Les vérifications de sécurité sont faites dans lc_ldap - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - adherent['droits']=[unicode(d) for d in droits] - adherent.history_gen() - adherent.save() - if adherent["uid"] and adherent["uid"][0] == self.conn.current_login: - self.check_ldap() - raise Continue(cont(adherent=adherent)) - - (code, droits) = self.handle_dialog(cont, box) - self_cont = TailCall(self.adherent_droits, adherent=adherent, cont=cont, choices_values=[(d, "", 1 if d in droits else 0) for d in attributs.TOUS_DROITS]) - return self.handle_dialog_result( - code=code, - output=droits, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, cont])] - ) - - - @tailcaller - def proprio_vente_set(self, article, cont): - """Permet de définir la quantité de l'article à vendre""" - def box(): - if article['pu'] == '*': - return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'], - text="", init=str(article.get('nombre','')), timeout=self.timeout, width=70) - else: - return self.dialog.inputbox(title="Nombre de %s ?" % article['designation'], - text="", timeout=self.timeout, init=str(article.get('nombre','1')), width=70) - def todo(article, output, cont): - article['nombre']=output - # Il faut entrer quelque chose - if not output: - raise ValueError("Merci d'entrer une valeur") - # Vérification des centimes - if article['pu'] == '*' and '.' in output: - if len(output.split('.', 1)[1])>2: - raise ValueError("Les centimes, c'est seulement deux chiffres après la virgule") - typ = float if article['pu'] == '*' else int - # Vérification du type de l'entré - try: - output=typ(output) - except ValueError: - raise ValueError("Merci d'entrez seulement des nombres") - # On définis le nombre d'entrée. Pour pu=* il y aura du trairement à faire - # avant de générer la facture : mettre pu à nombre et nombre à 1 - # on le met comme ça pour pouvoir naviger aisément entre les écrans dialog - article['nombre'] = output - return article - - (code, output) = self.handle_dialog(cont, box) - self_cont = TailCall(self.proprio_vente_set, article=article, cont=cont) - return self.handle_dialog_result( - code=code, - output=output, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [article, output, cont])] - ) - - @tailcaller - def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]): - """Menu de vente du crans. Permet également de recharger le solde crans""" - box_paiement = { - "liquide" : "Espèces", - "cheque" : "Chèque", - "solde" : "Solde Crans (actuel : %s€)", - } - - def box_choose_item(tags): - choices = [] - for code, article in gestion.config.factures.items.items(): - choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0)) - return self.dialog.checklist( - text="", - title="Vente de truc à %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), - choices=choices, - timeout=self.timeout) - - def box_choose_paiment(tag, articles): - box_paiement_order = ["liquide", "cheque"] - if "cransAccount" in proprio['objectClass']: - if not "SOLDE" in [art['code'] for art in articles]: - box_paiement_order.append("solde") - box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0] - choices = [] - for key in box_paiement_order: - choices.append((key, box_paiement[key], 1 if key == tag else 0)) - return self.dialog.radiolist( - text="", - title="Choix d'un mode de paiement pour %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), - choices=choices, - timeout=self.timeout) - - def choose_item(proprio, tags, articles, self_cont): - to_set=[] - for tag in tags: - articles[tag]['code']=tag - to_set.append(articles[tag]) - raise Continue(self_cont(to_set=to_set, have_set=[])) - - def number_of_items(to_set, have_set, self_cont): - # Où faut-il aller si l'utilisateur appuis sur annuler - if not have_set: - lcont = self_cont(to_set=[]) - else: - lcont = self_cont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) - art = self.proprio_vente_set(to_set[0], cont=lcont) - if not to_set[1:]: - total = 0 - line=1 - text=u"Résumé :\n" - for article in have_set + [art]: - if article['pu'] == '*': - total += article['nombre'] - text+=u" * %s pour %s€\n" % (article['designation'], article['nombre']) - else: - total += article['nombre']*article['pu'] - text+=u" * %dx %s à %s€\n" % (article['nombre'], article['designation'], article['pu']) - line+=1 - text+=u"Total à payer : %s€" % total - self.dialog.msgbox(text=text, - title="Résumé de la facture à payer", - width=70, height=5+line, timeout=self.timeout) - return self_cont(to_set=to_set[1:], have_set=have_set + [art]) - - def choose_paiment(have_set, tag, proprio, lcont, self_cont, cont): - if not tag: - raise ValueError("Il faut choisir un moyen de paiement") - code, comment = self.dialog.inputbox(text="Détail pour les espèce, nom de la note ou banque du chèque", title="Commentaire", width=70, timeout=self.timeout) - if code != self.dialog.DIALOG_OK: - raise Continue(self_cont) - if not comment: - raise ValueError("Commentaire nécessaire") - articles = copy.deepcopy(have_set) - for article in articles: - if article['pu'] == '*': - article['pu'] = article['nombre'] - article['nombre'] = 1 - - with self.conn.newFacture(proprio.dn, {}) as facture: - facture['modePaiement']=unicode(tag, 'utf-8') - facture['article']=articles - facture['info']=unicode(comment, 'utf-8') - if self.confirm_item(item=facture, - text=u"Le paiement de %s€ a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag), - title=u"Validation du paiement", - timeout=self.timeout): - # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture - facture.crediter() - arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE'] - if arts: - self.dialog.msgbox( - text=u"Vous pouvez remettre à l'adherent les articles (si se sont des articles) suivant :\n * %s" % '\n * '.join(arts), - title=u"Vente terminée", - width=0, height=0, timeout=self.timeout) - if tag == "solde": - self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout) - if [a for a in facture['article'] if art['code'] == 'SOLDE']: - self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout) - else: - self.dialog.msgbox(text=u"Le paiement n'ayant pas été reçue\nla vente est annulée", title="Annulation de la vente", width=0, height=0, timeout=self.timeout) - raise Continue(cont) - - self_cont=TailCall(self.proprio_vente, proprio=proprio, cont=cont, tags=tags, tag_paiment=tag_paiment, to_set=to_set, have_set=have_set) - # S'il y a des article dont il faut définir la quantité - if to_set: - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output=None, - cancel_cont=None, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], number_of_items, [to_set, have_set, self_cont])] - ) - # Sinon, si tous les quantités de tous les articles sont définis - elif have_set: - lcont = self_cont.copy() - lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) - (code, tag) = self.handle_dialog(lcont, box_choose_paiment, tag_paiment, have_set) - self_cont=self_cont(tag_paiment=tag) - lcont(tag_paiment=tag) - return self.handle_dialog_result( - code=code, - output=tag, - cancel_cont=lcont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], choose_paiment, [have_set, tag, proprio, lcont, self_cont, cont])] - ) - # Sinon, on propose des articles à chosir - else: - (code, tags) = self.handle_dialog(cont, box_choose_item, tags) - self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None) - return self.handle_dialog_result( - code=code, - output=tags, - cancel_cont=cont, - error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.items), self_cont])] - ) - - def create_adherent(self, cont): - """Crée un adhérent et potentiellement son compte crans avec lui""" - def mycont(adherent=None, **kwargs): - if adherent: - # Une fois l'adhérent créé, on vois s'il donne sa carte étudiant et s'il adhére/prend la connexion internet - adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent) - conn_cont = TailCall(self.adherent_connexion, cont=adh_cont, adherent=adherent) - carte_cont = TailCall(self.adherent_carte_etudiant, cont=conn_cont, adherent=adherent) - etude_cont = TailCall(self.adherent_etudes, cont=carte_cont, adherent=adherent) - etude_cont(cancel_cont=etude_cont) - carte_cont(cancel_cont=etude_cont) - conn_cont(cancel_cont=carte_cont) - raise Continue(etude_cont) - else: - raise Continue(cont) - return self.adherent_personnel(cont=TailCall(mycont)) - - def delete_adherent(self, cont, del_cont, adherent=None): - """Permet la suppression d'un adhérent de la base ldap""" - if adherent is None: - adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) - - def todo(adherent): - if self.confirm_item(item=adherent, title="Voulez vous vraiement supprimer l'adhérent ?", defaultno=True): - with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: - adherent.delete() - self.dialog.msgbox("L'adherent a bien été supprimée", timeout=self.timeout, title="Suppression d'un adherent") - raise Continue(del_cont(proprio=None)) - else: - raise Continue(cont) - - return self.handle_dialog_result( - code=self.dialog.DIALOG_OK, - output="", - cancel_cont=cont(proprio=adherent), - error_cont=cont(proprio=adherent), - codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent])] - ) - - def create_club(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont - - def delete_club(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont - - def modif_club(self, cont, club=None): - if club is None: - club = self.select(["club"], "Recherche d'un club pour modification", disable_field=["Prénom", "Téléphone"], cont=cont) - self.dialog.msgbox("todo", width=0, height=0) - return cont(proprio=club) - - def create_machine_club(self, cont, club=None): - """ - Permet d'ajouter une machine à un club. - On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) - """ - if club is None: - club = self.select(["club"], "Recherche d'un club pour lui ajouter une machine", cont=cont) - return self.create_machine_proprio(cont=cont, proprio=club) - - def create_machine_crans(self, cont): - """Permet l'ajout d'une machine à l'association""" - associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0] - return self.create_machine_proprio(cont=cont, proprio=associationCrans) - - def has_right(self, liste, obj=None): - """Vérifie que l'un des droits de l'utilisateur courant est inclus dans list""" - if not isinstance(liste, list): - liste = [liste] - if obj: - droits = obj.rights() - else: - droits = self.conn.droits - for d in liste: - if d in droits: - return True - return False - + return True +class GestCrans(adherent.Dialog, club.Dialog, machine.Dialog): @tailcaller def menu_principal(self, tag=None, machine=None, proprio=None): """Menu principal de l'application affiché au lancement""" @@ -3136,28 +149,8 @@ les valeurs valident sont : else: return self_cont -@TailCaller -def main(gc, cont=None): - """ - Fonction principale gérant l'appel au menu principal, - le timeout des écrans dialog et les reconnexion a ldap en cas de perte de la connexion - """ - while True: - try: - # tant que le timeout est atteint on revient au menu principal - gc.menu_principal() - except DialogError as e: - # Si l'erreur n'est pas due à un timeout, on la propage - if time.time() - gc.dialog_last_access < gc.timeout: - raise - # Si on perd la connexion à ldap, on en ouvre une nouvelle - except ldap.SERVER_DOWN: - if gc.dialog.pause(title="Erreur", duration=10, height=9, width=50, text="La connection au serveur ldap à été perdue.\nTentative de reconnexion en cours…") == gc.dialog.DIALOG_OK: - gc.check_ldap() - else: - return + if __name__ == '__main__': - main(GestCrans()) + main(GestCrans(ldap_test="--test" in sys.argv[1:], debug_enable="--debug" in sys.argv[1:])) os.system('clear') -