diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py index 038d3c4f..3314f429 100755 --- a/gestion/gest_crans_lc.py +++ b/gestion/gest_crans_lc.py @@ -23,17 +23,22 @@ import tempfile import collections sys.path.append('/usr/scripts/') from pythondialog import Dialog +from pythondialog import DialogError from gestion.affich_tools import get_screen_size, coul +from gestion.chgpass import checkpass import gestion.config as config import lc_ldap.shortcuts -import lc_ldap.objets +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 debugf=None -debug_enable = False +debug_enable = True def mydebug(txt): global debugf, debug_enable @@ -188,8 +193,16 @@ class GestCrans(object): # 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): + 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' # 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: @@ -198,7 +211,40 @@ class GestCrans(object): self.conn.droits = [str(d) for d in luser[0]['droits']] self.conn.dn = luser[0].dn - self.dialog = Dialog() + _dialog = None + @property + def dialog(self): + # 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 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 + 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: + # On effectue ce qu'il y a a faire dans todo + return todo(*todo_args) + # 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("%s" % unicode_of_Error(e), timeout=self.timeout,title="Erreur rencontrée", width=73, height=10) + raise Continue(error_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): @@ -216,6 +262,7 @@ class GestCrans(object): 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, @@ -226,28 +273,30 @@ class GestCrans(object): backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=choices) - (code, tag) = box() - retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - elif code in [self.dialog.DIALOG_OK]: + def todo(tag): if tag == 'new': return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''} else: - try: - bl = {} - bl.update(obj['blacklist'][int(tag)].value) - return tag, bl - except Exception as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont(values=values)) - else: - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) + bl = {} + bl.update(obj['blacklist'][int(tag)].value) + return tag, bl + + + (code, tag) = 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", @@ -255,32 +304,36 @@ class GestCrans(object): 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()]) - (code, tag) = box() - retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - elif code in [self.dialog.DIALOG_OK]: + + def todo(tag, retry_cont): if tag in config.blacklist_items: return tag else: - self.dialog.msgbox("%s n'est pas une blacklist" % tag, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont(values=values)) - else: - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) + 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() + 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, title=title) + (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: @@ -289,7 +342,7 @@ class GestCrans(object): 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, hour=hour, minute=minute, second=second) + (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: @@ -324,7 +377,7 @@ class GestCrans(object): 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) == self.dialog.DIALOG_OK: + 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: @@ -340,11 +393,11 @@ class GestCrans(object): # On s'en va en mettant à jour dans la continuation la valeur de obj raise Continue(cont(**{update_obj:obj})) # On propage les Continue - except 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("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) + self.dialog.msgbox("%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)) @@ -367,7 +420,7 @@ class GestCrans(object): 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 ?", title=title) == self.dialog.DIALOG_OK: + 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: @@ -393,11 +446,11 @@ class GestCrans(object): # On s'en va en mettant à jour dans la continuation la valeur de obj raise Continue(cont(**{update_obj:obj})) # On propage les Continue - except 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("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) + self.dialog.msgbox("%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)) @@ -418,41 +471,36 @@ class GestCrans(object): (code, output) = 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.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(**{update_obj:obj})) + + # 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) - # 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(cont) - # Sinon, c'est OK - elif code in [self.dialog.DIALOG_OK]: - # 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) - try: - # 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.save() - # On s'en va en mettant à jour dans la continuation la valeur de obj - raise Continue(cont(**{update_obj:obj})) - # On propage les Continue - except Continue: - 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), title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont(values=values)) - else: - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) + 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): """ @@ -469,22 +517,14 @@ class GestCrans(object): cmd.extend([str(index), str(value)]) index+=1 cmd.extend(['new', '']) - (code, output) = self.dialog._perform(*(cmd,), title=title, default_item=str(default_tag)) + (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) - 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) - # Si on a fait annulé ou appuyé sur ESC - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - # Si on a éditer ou ajouté une valeur de/dans la liste de l'attribut - elif code in [self.dialog.DIALOG_EXTRA]: + def todo_extra(output, values, retry_cont): tag, value = output if tag == 'new': if value: @@ -494,24 +534,30 @@ class GestCrans(object): else: values[int(tag)] = value raise Continue(retry_cont(values=values, tag=tag)) - # Si on valide les changement affectué, on sauvegarde les nouvelles valeurs dans l'objet - elif code in [self.dialog.DIALOG_OK]: - try: - 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.save() - raise Continue(cont(**{update_obj:obj})) - except Continue: - raise - except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) - # Si dialog retourne un code inconnu, on le signale - else: - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) - def search(self, objectClassS, title, values={}, cont=None): + 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.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) = 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) @@ -523,11 +569,11 @@ class GestCrans(object): 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]}, - 'Prenom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 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, 20, 00]}, + '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]}, @@ -536,19 +582,24 @@ class GestCrans(object): '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', 'Prenom', 'Téléphone', 'Chambre', 'aid', 'mail'] + 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 = ["--form", "Entrez vos paramètres de recherche", '0', '0', '0'] + 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']]) - cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']]) + 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']]) - cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0']) + 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,), title=title, backtitle="Entrez vos paramètres de recherche") + (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 @@ -569,9 +620,9 @@ class GestCrans(object): 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'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + 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'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + 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: @@ -610,41 +661,50 @@ class GestCrans(object): # 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'] ] - def select_one(self, items, title, default_item=None, cont=None): + @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""" - 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] - default_tag = items.index(default_item) if default_item in items else default_item + def box(items, default_item): + choices=[] + olist={} + count = 0 - # 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 - items=[] - items_id = {} - (line, col) = get_screen_size() - for c in olist.keys(): - items.extend(olist[c]) - items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n') - choices.append(("", str(items_s[0]))) - choices.append(("", str(items_s[1]))) - for i in items_s[2:]: - 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(("", "")) - # On supprime la dernière ligne qui est vide - del(choices[-1]) + # 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 - (code, tag) = self.dialog.menu( - "Que souhaitez vous faire ?", + # 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 + items=[] + 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:]: + 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(("", "")) + # On supprime la dernière ligne qui est vide + del(choices[-1]) + + 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, @@ -652,18 +712,28 @@ class GestCrans(object): choices=choices, colors=True) - # Si l'utilisateur à annulé, on coninue avec la continuation - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - # Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose - elif not tag: - self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0) - raise Continue(TailCall(self.select_one, items=items, title=title, default_item=default_item, cont=cont)) - # Sinon on retourne l'item choisis - elif self.confirm_item(items[int(tag)], title): - return items[int(tag)] - else: - raise Continue(cont) + + 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) = 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): @@ -671,21 +741,30 @@ class GestCrans(object): 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, init=init) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - elif force and not output: - self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose") - raise Continue(TailCall(self.get_comment, title=title, text=text, cont=cont, force=force)) - else: - return unicode(output, 'utf-8') + (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') - def confirm_item(self, item, title, defaultno=False, **params): + 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( - printing.sprint(item, **params), + text + printing.sprint(item, **params) + text_bottom, no_collapse=True, colors=True, + timeout=self.timeout, title=title, defaultno=defaultno, width=0, height=0, @@ -698,6 +777,7 @@ class GestCrans(object): 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" @@ -706,19 +786,19 @@ class GestCrans(object): # 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): + 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) + 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", title="Recherche", width=0, height=0) - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) + 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, cont=cont)) + 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] @@ -726,18 +806,18 @@ class GestCrans(object): if self.confirm_item(item, title): return item else: - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) - except Continue: + 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("%r" % e, title="Erreur rencontrée", width=0, height=0) - raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) + self.dialog.msgbox("%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, parent=None, realm=None, fields_values=None): + 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 parent, objectClass et realm. - Si on ne fait qu'éditer une machine, parent, objectClass et realm sont ignoré + 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. @@ -757,31 +837,76 @@ class GestCrans(object): # Quel séparateur on utilise pour les champs multivalué separateur = ' ' - if machine: - objectClass = machine["objectClass"][0] - attrs = dict((k,[str(a) for a in at]) for k,at in machine.items()) - else: - attrs = {} + 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] + 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] - (code, tags) = self.dialog.form( - text="", - 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") + 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") - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) + 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) - # 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 = [] - for ((a,l),values) in zip(to_display, tags): - fields_values.append(("%s :" % a.legend, values, l)) - try: + 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.save() + return machine + + def create_machine(prorio, 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): @@ -790,55 +915,38 @@ class GestCrans(object): # 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 quelqes vérification de syntaxe + # Pour host, on fait quelques vérification de syntaxe if a.ldap_name == 'host': - # Si c'est une machine wifi, host doit finir par wifi.crans.org - if "machineWifi" == objectClass: - hostend = ".wifi.crans.org" - # Si c'est une machine wifi, host doit finir par crans.org - elif "machineFixe" == objectClass: - hostend = ".crans.org" - # 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 values.endswith(hostend) and not '.' in values: - values = "%s.wifi.crans.org" % values - elif values.endswith(hostend) and '.' in values[:-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 values.endswith(hostend) and '.' in values: - raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) - - attrs[a.ldap_name]=values + attrs[a.ldap_name]=check_host(values, objectClass) + else: + attrs[a.ldap_name]=values + # Soit on édite une machine existante if machine: - 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.save() + machine = modif_machine(machine, attrs) + # Soit on crée une nouvelle machine else: - if parent is None or realm is None: - raise EnvironmentError("On essaye de créer une machine mais parent ou realm vaut None") - ldif = { - 'macAddress': ['%s' % attrs['macAddress'].encode('utf-8')], - 'host': ['%s' % attrs['host'].encode('utf-8')] - } - with self.conn.newMachine(parent.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) - else: - raise Continue(cont) - return cont(machine=machine) - except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) - raise Continue(TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, parent=parent, realm=realm, fields_values=fields_values)) + machine = create_machine(prorio, realm, attrs) + raise Continue(cont(machine=machine)) + + + if machine: + objectClass = machine["objectClass"][0] + + (code, tags) = 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""" @@ -859,20 +967,14 @@ class GestCrans(object): return self.dialog.form( text="", 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") - (code, output) = box(values) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - else: - values = dict(zip([form[k]['ldap_name'] for k in form_order], output)) - fields_values = [("%s : " % k, values[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) - try: - if not values['certificatUsage'] in ['0', '1', '2', '3']: - raise ValueError("""Type de certificat invalide : + 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) @@ -882,28 +984,35 @@ les valeurs valident sont : (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 : + 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.save() - raise Continue(cont(certificat=certificat)) - except Continue: - raise - except Exception as e: - self.dialog.msgbox("%s" % (unicode_of_Error(e)), title="Erreur rencontrée", width=73, height=10) - raise Continue(self_cont) + 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.save() + raise Continue(cont(certificat=certificat)) + + (code, output) = 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, machine, cont): """Permet d'ajouter un certificat à une machine à partir du PEM du certificat""" @@ -913,8 +1022,9 @@ les valeurs valident sont : os.close(fp) cmd = ['--editbox', path, "0", "0"] (code, output) = self.dialog._perform(*(cmd,), - no_mouse=True, + 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: @@ -922,37 +1032,41 @@ 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() + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + (code, pem) = box() - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - else: - try: - with self.conn.newCertificat(machine.dn, {}) as certificat: - certificat['certificat'] = unicode(pem, 'utf-8') - certificat.create() - raise Continue(cont(certificat=certificat, machine=certificat.machine())) - except Continue: - raise - except Exception as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) - self_cont = TailCall(self.create_certificat, machine=machine, cont=cont) - raise Continue(self_cont) + self_cont = TailCall(self.create_certificat, machine=machine, 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])] + ) def delete_certificat(self, certificat, cont): """Supprime un certificat""" - if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"): - try: + 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é", title="Suppression d'un certificat") + 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))) - except Continue: - raise - except Exception as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) - raise Continue(cont) - else: - raise Continue(cont(certificat=certificat)) + 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 modif_machine_certificat(self, machine, cont, tag=None, certificat=None): """Permet l'édition d'un certificat d'une machine""" @@ -981,27 +1095,33 @@ les valeurs valident sont : height=0, menu_height=0, item_help=0, + 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]) + + 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) (code, tag) = box(tag) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - if certificat is None: - raise Continue(cont(machine=machine)) - else: - raise Continue(self_cont(certificat=None)) - elif 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(certificat=None) + 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): @@ -1016,6 +1136,7 @@ les valeurs valident sont : "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], @@ -1025,24 +1146,30 @@ les valeurs valident sont : backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=choices) - (code, tag) = box() - retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont) - elif code in [self.dialog.DIALOG_OK]: + def todo(tag): if tag == 'new': return tag else: return int(tag) - else: - self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) - raise Continue(retry_cont) - return cont + + (code, tag) = 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, + 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_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) @@ -1094,6 +1221,7 @@ les valeurs valident sont : 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], @@ -1101,32 +1229,42 @@ les valeurs valident sont : 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]) - (code, tag) = box(tag) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont(machine=machine)) - elif not tag in menu_order: - raise Continue(TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)) - else: - cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) - 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) - def create_machine_adherent(self, cont, adherent=None, tag=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) + 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) = 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): 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.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 ?", @@ -1134,60 +1272,736 @@ les valeurs valident sont : 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]) + + def todo(tag, menu, proprio, self_cont): + if not tag in menu_order: + raise Continue(self_cont) + else: + return self.machine_information( + cont=self_cont, + machine=None, + objectClass=menu[tag]['objectClass'], + proprio=proprio, + realm=menu[tag]['realm'] + ) + (code, tag) = box(tag) - if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - raise Continue(cont(proprio=adherent)) - elif not tag in menu_order: - raise Continue(TailCall(self.create_machine_adherent, cont=cont, adherent=adherent, tag=tag)) - else: - return self.machine_information( - cont=cont(proprio=adherent), - machine=None, - objectClass=menu[tag]['objectClass'], - parent=adherent, - realm=menu[tag]['realm'] - ) + 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])] + ) + + 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) - if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True): - try: + + 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", title="Suppression d'une machine") + self.dialog.msgbox("La machine a bien été supprimée", timeout=self.timeout, title="Suppression d'une machine") raise Continue(cont(machine=None)) - except Continue: - raise - except Exception as e: - self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + else: raise Continue(cont) - else: - raise Continue(cont) - return 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(self, cont, adherent=None): + def modif_adherent(self, cont, adherent=None, tag=None): if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) + a = attributs + 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"}, + #'Mail' :{'text' : "adresse mail de contact (alternative si compte crans)", "adherent":"proprio", "callback":self.proprio_mail}, + #'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.mailAlias}, + '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', "adherent":"proprio", 'callback':self.modif_proprio_blacklist}, + 'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente}, + } + menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte', 'GPGFingerprint', 'Remarques', 'Blackliste', 'Vente'] + menu_compte_crans = ['Droits'] + + if "cransAccount" in adherent['objectClass']: + menu_order.extend(menu_compte_crans) + 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=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=[(key, menu[key]['text'], menu[key].get('help', "")) for key in menu_order]) + + 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) = 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): self.dialog.msgbox("todo", width=0, height=0) - return cont(proprio=adherent) + return cont + + def adherent_personnel(self, cont, adherent=None, uldif={}, fields_values=None, 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)] + 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_values and make_compte_crans is not None: + return (self.dialog.DIALOG_OK, [t[1] for t in fields_values], 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 ?", 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 :" % a.legend, 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_values if fields_values else 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.save() + return adherent + + def create_adherent(attrs, make_compte_crans, force_create, uldif, 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(uldif) as adherent: + for (key, values) in attrs.items(): + adherent[key]=values + # Si compte crans à créer, on le crée + if make_compte_crans: + self.dialog.msgbox("todo", width=0, height=0) + return None + # Sinon, 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) + # 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, tags, adherent, uldif, separateur, make_compte_crans, force_create, cont, self_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] + attrs[a.ldap_name]=values + if adherent: + adherent = modif_adherent(adherent, attrs) + else: + adherent = create_adherent(attrs, make_compte_crans, force_create, uldif, self_cont, cont) + raise Continue(cont(adherent=adherent)) + + + (code, tags, make_compte_crans) = 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_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)] + retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, uldif=uldif, 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, adherent, uldif, separateur, make_compte_crans, force_create, cont, retry_cont])] + ) + + def adherent_etudes(self, adherent, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + @tailcaller + def adherent_chambre_campus(self, success_cont, cont, adherent, create=False): + 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, + ) + + 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.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.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) = box() + self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, 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, self_cont, success_cont])] + ) + @tailcaller + def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False): + 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) == 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.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.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) + 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.save() + ### TODO SUpression du compte crans + self.dialog.msgbox("todo Supression du compte crans, lc_ldap ne sais pas encore faire", width=0, height=0) + self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées") + raise Continue(success_cont(adherent=adherent)) + else: + raise Continue(cont) + else: + raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué") + + (code, output) = 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): + 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]) + + 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) = 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 proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0): + def todo(proprio, warning, guess_login, guess_pass, self_cont, 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, + colors=True) != self.dialog.DIALOG_OK: + raise Continue(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) + 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)) + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + 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, + ) + if not self.confirm_item(item=proprio, title="Création du compte crans pour l'adhérent ?"): + raise Continue(cont) + else: + 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]), + ) + 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]), + ) == self.dialog.DIALOG_OK: + return self.proprio_compte_password(proprio=proprio, cont=cont(proprio=proprio)) + else: + raise Continue(cont(proprio=proprio)) + + 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, self_cont, cont])] + ) + + def proprio_compte_password(self, proprio, cont): + def box(): + return self.dialog.passwordform(text="Remplacer le mot de passe", + height=15, width=54, form_height=7, + fields=[("Mot de passe", "", 10, 20), + ("Confirmation", "", 10, 20)], + 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") + + def todo(passwords, proprio, self_cont, cont): + (good, msg) = checkpass(passwords[0], 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) + raise Continue(self_cont) + elif passwords[0] != passwords[1]: + self.dialog.msgbox( + "Les deux mots de passes ne sont pas identiques", + title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), + colors=True) + raise Continue(self_cont) + else: + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + proprio['userPassword']=unicode(lc_utils.hash_password(passwords[0])) + 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]), + ) + raise Continue(cont(proprio=proprio)) + (code, passwords) = box() + 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, self_cont, cont])] + ) + + + def proprio_compte_delete(self, proprio, cont): + """Permet la suppression du compte crans d'un proprio""" + def todo(proprio, self_cont, cont): + if 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) + 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.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) + 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): + with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: + if disable: + proprio["shadowExpire"]=0 + else: + proprio["shadowExpire"]=[] + proprio.save() + raise Continue(cont(proprio=proprio)) + + def proprio_compte_shell(self, proprio, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def proprio_compte(self, proprio, cont, default_item=None): + has_compte = 'cransAccount' in proprio['objectClass'] + disabled_compte = has_compte and 0 in proprio['shadowExpire'] + menu = {} + menu_order = [] + tag_translate = { + "Créer":"Password", + "Password":"Password", + "Supprimer":"Créer", + "Activer":"Désactiver", + "Désactiver":"Activer", + "Shell":"Shell", + '':'', + } + if has_compte: + menu["Password"] = {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password} + menu["Shell"] = {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell} + menu["Supprimer"] = {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete} + menu_order.extend(["Password", "Shell", "Supprimer"]) + if disabled_compte: + menu["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)} + menu_order.insert(1, "Activer") + else: + menu["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)} + menu_order.insert(1, "Désactiver") + else: + menu["Créer"] = {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create} + 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]) + + def todo(tag, menu, proprio, self_cont): + if not tag in menu_order: + raise Continue(self_cont) + else: + raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio)) + + (code, tag) = 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, + output=tag, + cancel_cont=cont, + error_cont=self_cont, + codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont])] + ) + + def proprio_mail(self, proprio, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def adherent_droits(self, adherent, cont, choices_values=None): + 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]), + ) + def todo(droits, adherent, self_cont, cont): + """if attributs.nounou in self.conn.droits: + droits_modifiable = attributs.DROITS_SUPERVISEUR[attributs.nounou] + elif attributs.bureau in self.conn.droits: + droits_modifiable = attributs.DROITS_SUPERVISEUR[attributs.bureau] + else: + droits_modifiable = [] + new_droits = [str(d) for d in droits if d not in adherent['droits']] + del_droits = [str(d) for d in adherent['droits'] if d not in droits] + modif_droit = set(new_droits+del_droits) + modif_interdite = [d for d in modif_droit if d not in droits_modifiable] + if modif_interdite: + self.dialog.msgbox("Vous n'avez pas le droits de modifier les droits %s, seulement les droits '%s'" % (", ".join(modif_interdite), ", ".join(droits_modifiable)), + title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0])) + raise Continue(self_cont) + else:""" + with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: + adherent['droits']=[unicode(d) for d in droits] + adherent.save() + if adherent["uid"] and adherent["uid"][0] == self.conn.current_login: + self.check_ldap() + raise Continue(cont(adherent=adherent)) + + (code, droits) = 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])] + ) + def modif_proprio_blacklist(self, proprio, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def proprio_vente(self, proprio, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont def create_adherent(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + def mycont(adherent=None, **kwargs): + if adherent: + raise Continue(TailCall(self.modif_adherent, cont=cont, adherent=adherent)) + else: + raise Continue(cont) + return self.adherent_personnel(cont=TailCall(mycont)) - def delete_adherent(self, cont): - adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) - self.dialog.msgbox("todo", width=0, height=0) - return cont + def delete_adherent(self, 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(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) @@ -1197,21 +2011,24 @@ les valeurs valident sont : self.dialog.msgbox("todo", width=0, height=0) return cont - def modif_club(self, 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 + return cont(proprio=club) - def create_machine_club(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + 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): - self.dialog.msgbox("todo", width=0, height=0) - return cont - - def create_borne(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0] + return self.create_machine_proprio(cont=cont, proprio=associationCrans) @tailcaller def menu_principal(self, tag=None, machine=None, proprio=None): @@ -1228,12 +2045,12 @@ les valeurs valident sont : 'aMC': {'text':"Ajouter une machine à un club", 'callback': self.create_machine_club}, 'dC' : {'text':"Détruire un club", 'callback': self.delete_club}, 'aKM': {'text':"Ajouter une machine à l'association", 'callback': self.create_machine_crans}, - 'aKB': {'text':"Ajouter une borne wifi", 'callback': self.create_borne}, '' : {'text':"---------------------------------------",'callback': None}, } ### Les clef qui n'existe pas sont toute renvoyé sur la clef '' - menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM", "aKB"] - + menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM"] + if isinstance(proprio, objets.AssociationCrans): + proprio = None if machine and not proprio: proprio = machine.proprio() if machine or proprio: @@ -1250,7 +2067,7 @@ les valeurs valident sont : menu.update(menu_machine) menu_order = menu_machine_order + menu_order if proprio: - menu_proprio = { + menu_adherent = { 'mAc' : { 'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0], 'callback': TailCall(self.modif_adherent, adherent=proprio) @@ -1260,7 +2077,24 @@ les valeurs valident sont : 'callback': TailCall(self.create_machine_adherent, adherent=proprio) }, } - menu_proprio_order = ['mAc', 'aMc'] + menu_club = { + 'mCc' : { + 'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0], + 'callback': TailCall(self.modif_club, club=proprio) + }, + 'aMc' : { + 'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0], + 'callback': TailCall(self.create_machine_club, club=proprio) + }, + } + if 'adherent' in proprio['objectClass']: + menu_proprio = menu_adherent + menu_proprio_order = ['mAc', 'aMc'] + elif 'club' in proprio['objectClass']: + menu_proprio = menu_club + menu_proprio_order = ['mCc', 'aMc'] + else: + raise EnvironmentError("Je ne connais que des adherents et des club comme proprio") menu.update(menu_proprio) menu_order = menu_proprio_order + menu_order def box(default_item=None): @@ -1273,6 +2107,7 @@ les valeurs valident sont : default_item=str(default_item), title="Menu principal", scrollbar=True, + timeout=self.timeout, cancel_label="Quitter", 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]) @@ -1284,6 +2119,21 @@ les valeurs valident sont : else: return TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) +def main(gc, cont=None): + 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__': - GestCrans().menu_principal() + main(GestCrans()) os.system('clear')