diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py index 3314f429..7f1192ae 100755 --- a/gestion/gest_crans_lc.py +++ b/gestion/gest_crans_lc.py @@ -15,6 +15,7 @@ Licence : GPLv3 import os import sys +import ssl import time import ldap import signal @@ -24,7 +25,9 @@ import collections sys.path.append('/usr/scripts/') from pythondialog import Dialog from pythondialog import DialogError +from OpenSSL import crypto, SSL +from gestion.cert_utils import createCertRequest from gestion.affich_tools import get_screen_size, coul from gestion.chgpass import checkpass import gestion.config as config @@ -37,8 +40,9 @@ import lc_ldap.crans_utils as lc_utils from lc_ldap.attributs import UniquenessError +import gestion.secrets_new as secrets debugf=None -debug_enable = True +debug_enable = False def mydebug(txt): global debugf, debug_enable @@ -166,7 +170,7 @@ def handle_exit_code(d, code): os.system('clear') sys.exit(0) else: - msg = "Vous avez appuyer sur ESC dans la dernière fenêtre de dialogue.\n\n" \ + msg = "Vous avez appuyer sur ESC ou CTRL+C dans la dernière fenêtre de dialogue.\n\n" \ "Voulez vous quitter le programme ?" if d.yesno(msg, width=60) == d.DIALOG_OK: os.system('clear') @@ -175,6 +179,8 @@ def handle_exit_code(d, code): else: return True # code est d.DIALOG_OK +def raiseKeyboardInterrupt(x, y): + raise KeyboardInterrupt() class GestCrans(object): """Interface de gestion des machines et des adhérents du crans, version lc_ldap""" @@ -188,8 +194,7 @@ class GestCrans(object): return ret def __init__(self): - if not debug_enable: - signal.signal(signal.SIGINT, signal.SIG_IGN) + 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) @@ -202,7 +207,7 @@ class GestCrans(object): self.check_ldap_last = time.time() # On ouvre une connexion lc_ldap self.conn = lc_ldap.shortcuts.lc_ldap_admin() - self.conn.current_login = 'totocrans' + #self.conn.current_login = 'totocrans' # 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: @@ -222,6 +227,26 @@ class GestCrans(object): self.dialog_last_access = time.time() return self._dialog + @tailcaller + def handle_dialog(self, cancel_cont, box, *args): + 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 + ctrlC = True + finally: + if ctrlC: + raise Continue(cancel_cont) + elif ret: + return ret + else: + EnvironmentError("Ni Ctrl+C ni ret ?!? c'est pas possible ") + + @tailcaller def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]): """ 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 @@ -232,15 +257,23 @@ class GestCrans(object): 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 - return todo(*todo_args) + 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: - self.dialog.msgbox("%s" % unicode_of_Error(e), timeout=self.timeout,title="Erreur rencontrée", width=73, height=10) - raise Continue(error_cont) + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C + self.dialog.msgbox("%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) @@ -282,7 +315,7 @@ class GestCrans(object): return tag, bl - (code, tag) = box() + (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, @@ -319,7 +352,7 @@ class GestCrans(object): 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) = box() + (code, tag) = self.handle_dialog(cont, box) return self.handle_dialog_result( code=code, output=tag, @@ -544,7 +577,7 @@ class GestCrans(object): 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) = box(values, tag) + (code, output) = self.handle_dialog(cont, box, values, tag) return self.handle_dialog_result( code=code, @@ -605,7 +638,7 @@ class GestCrans(object): else: # empty selection return (code, []) - (code, dialog_values) = box() + (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) @@ -724,7 +757,7 @@ class GestCrans(object): else: raise Continue(cont) - (code, tag) = box(items, default_item) + (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( @@ -885,7 +918,7 @@ class GestCrans(object): machine.save() return machine - def create_machine(prorio, realm, attrs): + 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") @@ -925,14 +958,14 @@ class GestCrans(object): machine = modif_machine(machine, attrs) # Soit on crée une nouvelle machine else: - machine = create_machine(prorio, realm, attrs) + machine = create_machine(proprio, realm, attrs) raise Continue(cont(machine=machine)) if machine: objectClass = machine["objectClass"][0] - (code, tags) = box() + (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é @@ -1002,7 +1035,7 @@ les valeurs valident sont : certificat.save() raise Continue(cont(certificat=certificat)) - (code, output) = box(values) + (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) @@ -1014,8 +1047,10 @@ les valeurs valident sont : codes_todo=[([self.dialog.DIALOG_OK], todo, [form, values, certificat, cont])] ) - def create_certificat(self, machine, 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() @@ -1032,20 +1067,116 @@ les valeurs valident sont : else: return code, None - def todo(machine, cont): - with self.conn.newCertificat(machine.dn, {}) as certificat: - certificat['certificat'] = unicode(pem, 'utf-8') - certificat.create() + 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, 'utf-8') + certificat.save() + else: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['certificat'] = unicode(pem, 'utf-8') + certificat.create() raise Continue(cont(certificat=certificat, machine=certificat.machine())) - (code, pem) = box() - self_cont = TailCall(self.create_certificat, machine=machine, cont=cont) + (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, cont])] + codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, cont])] + ) + + @tailcaller + def get_password(self, cont, confirm=True): + def todo(self_cont, cont): + (code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title="Choix d'un mot de passe") + if code != self.dialog.DIALOG_OK: + raise Continue(cont) + if confirm: + (code, pass2) = self.dialog.passwordbox("Comfirmer le mot de passe", title="Choix d'un mot de passe") + 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, + 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.save() + self.dialog.msgbox("Clef privée bien ajouté", 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", 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): @@ -1068,6 +1199,133 @@ les valeurs valident sont : ) + def gen_csr(self, certificat, cont): + 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,) + os.remove(path) + return + + 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) + 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") + 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.save() + self.handle_dialog(cont, box, csr) + if self.dialog.yesno("Remplacer le certificat actuel ?") == 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): + 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,) + 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, 'utf-8') + certificat.save() + else: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['hostCert']=unicode(machine['host'][0]) + certificat['csr'] = unicode(pem, '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) @@ -1075,33 +1333,69 @@ les valeurs valident sont : 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] menu = { - 'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "attribut":attributs.hostCert}, - 'TLSA' : {'text':"Paramètres pour les champs dns TLSA", "callback":self.certificat_tlsa}, - 'Autre': {'text' : "Modifier les attribut booléen comme revocked", "callback":self.modif_certificat_boolean}, - 'Supprimer' : {'text' : "Supprimer le certificat", "callback":self.delete_certificat}, + '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}, } - menu_order = ['Hostname', 'TLSA', 'Autre', 'Supprimer'] + if "privateKey" in certificat["objectClass"]: + menu + menu_order = ['Hostname'] + if not "privateKey" in certificat['objectClass']: + menu_order.append('AddPrivateKey') + 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( - "Paramètres pour le certificat N°0x%X émis par %s, valable du %s au %s" % ( - 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]))) - ), + text, width=0, height=0, menu_height=0, - item_help=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']) for key in menu_order]) + choices=[(key, menu[key]['text'], menu[key]['help']) for key in menu_order]) def todo(tag, menu, certificat, self_cont): if not tag in menu_order: @@ -1113,8 +1407,8 @@ les valeurs valident sont : 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) - (code, tag) = box(tag) - cancel_cont = cont(machine=machine) if certificat is None else self_cont(certificat=None) + (code, tag) = self.handle_dialog(cont, box, tag) + cancel_cont = cont(machine=machine) if certificat is None else self_cont(machine=certificat.machine(), certificat=None) return self.handle_dialog_result( code=code, output=tag, @@ -1129,8 +1423,18 @@ les valeurs valident sont : def box(default_item=None): index=0 choices = [('new', 'Ajouter un nouveau certificat')] + choices.append(('priv', 'Générer une nouvelle clef privée')) + choices.append(('csr', 'Ajouter une nouvelle requête de certificat')) for cert in machine.certificats(): - choices.append((str(index), "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])))))) + 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 ?", @@ -1147,17 +1451,17 @@ les valeurs valident sont : choices=choices) def todo(tag): - if tag == 'new': + if tag in ['new', 'priv', 'csr']: return tag else: return int(tag) - (code, tag) = box() + (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, + cancel_cont=cont(machine=machine), error_cont=retry_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])] ) @@ -1241,7 +1545,7 @@ les valeurs valident sont : else: raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - (code, tag) = box(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( @@ -1280,19 +1584,19 @@ les valeurs valident sont : backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(key, menu[key]['text']) for key in menu_order]) - def todo(tag, menu, proprio, self_cont): + def todo(tag, menu, proprio, self_cont, cont): if not tag in menu_order: raise Continue(self_cont) else: return self.machine_information( - cont=self_cont, + cont=cont, machine=None, objectClass=menu[tag]['objectClass'], proprio=proprio, realm=menu[tag]['realm'] ) - (code, tag) = box(tag) + (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) @@ -1301,7 +1605,7 @@ les valeurs valident sont : output=tag, cancel_cont=cont, error_cont=self_cont, - codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont])] + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont, cont])] ) def create_machine_adherent(self, cont, adherent=None): @@ -1384,7 +1688,7 @@ les valeurs valident sont : else: raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - (code, tag) = box(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( @@ -1492,7 +1796,7 @@ les valeurs valident sont : raise Continue(cont(adherent=adherent)) - (code, tags, make_compte_crans) = box(make_compte_crans) + (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é @@ -1563,7 +1867,7 @@ les valeurs valident sont : self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output) raise Continue(success_cont(adherent=adherent)) - (code, output) = box() + (code, output) = self.handle_dialog(cont, box) self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont) return self.handle_dialog_result( code=code, @@ -1658,7 +1962,7 @@ les valeurs valident sont : else: raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué") - (code, output) = box() + (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, @@ -1697,7 +2001,7 @@ les valeurs valident sont : else: raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent)) - (code, tag) = box(default_item) + (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, @@ -1808,7 +2112,7 @@ les valeurs valident sont : title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), ) raise Continue(cont(proprio=proprio)) - (code, passwords) = box() + (code, passwords) = self.handle_dialog(cont, box) self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont) return self.handle_dialog_result( code=code, @@ -1911,7 +2215,7 @@ les valeurs valident sont : else: raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio)) - (code, tag) = box(default_item) + (code, tag) = self.handle_dialog(cont, box, default_item) self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate[tag]) return self.handle_dialog_result( code=code, @@ -1956,7 +2260,7 @@ les valeurs valident sont : self.check_ldap() raise Continue(cont(adherent=adherent)) - (code, droits) = box() + (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, @@ -2112,12 +2416,13 @@ les valeurs valident sont : backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in menu_order]) - (code, tag) = box(tag) + self_cont = TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) + (code, tag) = self.handle_dialog(TailCall(handle_exit_code, self.dialog, self.dialog.DIALOG_ESC), box, tag) callback = menu.get(tag, menu[''])['callback'] if handle_exit_code(self.dialog, code) and callback: return TailCall(callback, cont=TailCall(self.menu_principal, tag=tag, machine=machine, proprio=proprio)) else: - return TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) + return self_cont def main(gc, cont=None): while True: