Commit 2eeac760 authored by Valentin Samir's avatar Valentin Samir
Browse files

[gest_crans_lc] Docstring et gestion des permission sur les shells

Seul les nounou peuvent éditer les shells restrictif, les cableurs peuvent éditer
les shells standard
parent 12fa9bfc
......@@ -47,6 +47,7 @@ debugf=None
debug_enable = False
def mydebug(txt):
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log"""
global debugf, debug_enable
if debug_enable:
if debugf is None:
......@@ -145,7 +146,12 @@ class TailCall(object) :
return "TailCall<%s(%s%s%s)>" % (self.call.func_name, ', '.join(repr(a) for a in self.args), ', ' if self.args and self.kwargs else '', ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items()))
def copy(self):
''' Patch the deepcopy dispatcher to pass modules back unchanged '''
'''
Renvois une copie de l'objet courant
attention les elements et args ou kwargs sont juste linké
ça n'est pas gennant dans la mesure où ils ne sont normalement pas
éditer mais remplacé par d'autres éléments
'''
result = TailCall(self.call, *list(self.args), **dict(self.kwargs))
result.stacklvl = self.stacklvl
return result
......@@ -156,6 +162,11 @@ class TailCall(object) :
return self
def handle(self) :
"""
Exécute la fonction call sur sa liste d'argument.
on déréférence les TailCaller le plus possible pour réduire
la taille de la stack
"""
caller = None
call = self.call
while isinstance(call, TailCaller) :
......@@ -164,7 +175,7 @@ class TailCall(object) :
return call(*self.args, **self.kwargs)
def unicode_of_Error(x):
"""Formatte l'exception de type ValueError"""
"""Formatte des exception"""
return u"\n".join(unicode(i, 'utf-8') if type(i) == str
else repr(i) for i in x.args)
......@@ -173,8 +184,8 @@ def handle_exit_code(d, code):
"""Gère les codes de retour dialog du menu principal"""
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
if code == d.DIALOG_CANCEL:
msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \
"Voulez vous quitter le programme ?"
#msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \
# "Voulez vous quitter le programme ?"
os.system('clear')
sys.exit(0)
else:
......@@ -188,6 +199,7 @@ def handle_exit_code(d, code):
return True # code est d.DIALOG_OK
def raiseKeyboardInterrupt(x, y):
"""fonction utilisée pour réactiver les Ctrl-C"""
raise KeyboardInterrupt()
class GestCrans(object):
......@@ -212,6 +224,7 @@ class GestCrans(object):
self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN)
def check_ldap(self):
"""Se connecte à la base ldap et vérifie les droits de l'utilisateur courant"""
self.check_ldap_last = time.time()
# S'il y a --test dans les argument, on utilise la base de test
......@@ -251,6 +264,9 @@ class GestCrans(object):
_dialog = None
@property
def dialog(self):
"""
Renvois l'objet dialog. De plus renouvelle régulièrement la connexion à la base ldap
"""
# Tous les self.timeout, on refraichie la connexion ldap
if time.time() - self.check_ldap_last > self.timeout:
self.check_ldap_last = time.time()
......@@ -260,6 +276,9 @@ class GestCrans(object):
return self._dialog
def nyan(self, cont, *args, **kwargs):
"""
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
"""
(lines, cols) = get_screen_size()
print "\033[48;5;17m"
print " "*(lines * cols)
......@@ -272,6 +291,11 @@ class GestCrans(object):
@tailcaller
def handle_dialog(self, cancel_cont, box, *args):
"""
Gère les fonctions appelant une fênetre dialog :
gestion de l'appuie sur Ctrl-C et rattrapage des exception / segfault de dialog.
cancel_cont représente l'endroit où retourner si Ctrl-C
"""
ctrlC=False
ret=None
try:
......@@ -296,7 +320,14 @@ class GestCrans(object):
@tailcaller
def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]):
""" codes_todo = [(code, todo, todo_args)]"""
"""
Gère les fonctions traitant les résultat d'appels à dialog.
s'occupe de gérer les exceptions, Ctrl-C, propagation de certaine exceptions, l'appuis sur annuler.
Le code à exécuté lui ai passé via la liste codes_todo, qui doit contenir une liste de triple :
(code de retour dialog, fonction à exécuter, liste des arguements de la fonction)
la fonction est appelée sur ses arguements si le code retourné par dialog correspond.
codes_todo = [(code, todo, todo_args)]
"""
# Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cancel_cont)
......@@ -845,6 +876,7 @@ class GestCrans(object):
text + printing.sprint(item, **params) + text_bottom,
no_collapse=True,
colors=True,
no_mouse=True,
timeout=self.timeout,
title=title,
defaultno=defaultno,
......@@ -1143,6 +1175,10 @@ les valeurs valident sont :
@tailcaller
def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs):
"""
Affiche une série d'inpuxbox pour faire entrer un mot de passe puis le retourne,
si confirm=True, il y a une confirmation du mot de passe de demandée
"""
def todo(self_cont, cont):
(code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs)
if code != self.dialog.DIALOG_OK:
......@@ -1255,6 +1291,7 @@ les valeurs valident sont :
def gen_csr(self, certificat, cont):
"""Permet de générer un csr à partir de la clef privée du certificat"""
def todo(certificat, self_cont, cont):
if certificat['encrypted']:
if "machineCrans" in certificat.machine()["objectClass"]:
......@@ -1267,7 +1304,7 @@ les valeurs valident sont :
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)
......@@ -1310,6 +1347,7 @@ les valeurs valident sont :
)
def get_certificat(self, certificat, cont, privatekey=False, csr=False):
"""Permet d'afficher le certificat courant"""
def box(text):
fp, path = tempfile.mkstemp()
os.write(fp, text)
......@@ -1369,7 +1407,7 @@ les valeurs valident sont :
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)
......@@ -1491,7 +1529,7 @@ les valeurs valident sont :
'csr':'Ajouter une nouvelle requête de certificat',
}
menu_order = ['new', 'priv', 'csr']
menu_special = ['new', 'priv', 'csr']
menu_special = ['new', 'priv', 'csr']
def box(default_item=None):
index=0
choices = []
......@@ -1646,6 +1684,7 @@ les valeurs valident sont :
)
def create_machine_proprio(self, cont, proprio, tag=None):
"""Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)"""
a = attributs
menu_droits = {
'Fixe' : [a.soi, a.cableur, a.nounou],
......@@ -1744,6 +1783,7 @@ les valeurs valident sont :
def modif_adherent(self, cont, adherent=None, proprio=None, tag=None):
"""Menu d'édition d'un adhérent"""
if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
a = attributs
......@@ -1836,7 +1876,7 @@ les valeurs valident sont :
to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)]
non_empty = [a.nom, a.prenom, a.tel]
input_type = {'default':0}
# Quel séparateur on utilise pour les champs multivalué
separateur = ' '
......@@ -1954,11 +1994,13 @@ les valeurs valident sont :
)
def adherent_etudes(self, adherent, cont):
"""Gestion des études de l'adhérent"""
self.dialog.msgbox("todo", width=0, height=0)
return cont
@tailcaller
def adherent_chambre_campus(self, success_cont, cont, adherent, create=False):
"""Permet de faire déménager d'adhérent sur le campus"""
def box():
return self.dialog.inputbox(
"chambre ?",
......@@ -2019,6 +2061,7 @@ les valeurs valident sont :
)
@tailcaller
def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False):
"""Permet de faire déménager l'adhérent hors campus"""
if keep_machine and not keep_compte:
raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines")
elif keep_machine and keep_compte:
......@@ -2118,6 +2161,7 @@ les valeurs valident sont :
)
def adherent_chambre(self, adherent, cont, default_item=None):
"""Permet de faire déménager d'adhérent"""
a = attributs
menu_droits = {
'0' : [a.cableur, a.nounou],
......@@ -2165,6 +2209,7 @@ les valeurs valident sont :
@tailcaller
def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'):
"""Permet de créer un compte crans à un proprio (club ou adhérent)"""
def box_warning(warning, proprio, cont):
# Affiche-t-on le warning sur la consutation de l'adresse crans
if warning:
......@@ -2264,7 +2309,7 @@ les valeurs valident sont :
@tailcaller
def proprio_compte_password(self, proprio, cont, return_obj=False):
"""Permet de changer le mot de passe d'un compte crans"""
def test_password(password, self_cont):
(good, msg) = checkpass(password, dialog=True)
if not good:
......@@ -2338,6 +2383,7 @@ les valeurs valident sont :
)
def proprio_compte_etat(self, proprio, disable, cont):
"""Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire"""
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
if disable:
proprio["shadowExpire"]=0
......@@ -2347,20 +2393,41 @@ les valeurs valident sont :
raise Continue(cont(proprio=proprio))
def proprio_compte_shell(self, proprio, cont, choices_values=None):
"""Permet de modifier le shell d'un compte crans"""
a = attributs
# Seul les nounous peuvent changer les shells restrictifs
shells_droits = {
'default' : [a.soi, a.nounou, a.cableur],
'rbash' : [a.nounou],
'rssh' : [a.nounou],
'badPassSh' : [a.nounou],
'disconnect_shell':[a.nounou],
}
shell = os.path.basename(str(proprio['loginShell'][0])).lower()
shells = config.shells_gest_crans
shells_order = config.shells_gest_crans_order
def box():
return self.dialog.radiolist(
# liste des shell éditables par l'utilisateur
editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)]
# Si l'utilisateur de gest_crans peut éditer le shell courant
# il peut le changer pour un autre shell qu'il peut éditer
if shell in editable_sheels:
choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels]
return self.dialog.radiolist(
text="",
height=0, width=0, list_height=0,
choices=choices_values if choices_values else [(s, shells[s]['desc'], 1 if s == shell else 0) for s in shells_order],
choices=choices_values if choices_values else choices,
title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]),
timeout=self.timeout
)
)
# Sinon, on affiche un message d'erreur et on annule
else:
self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur",
title="Édition du shell impossible", timeout=self.timeout, width=0, height=0)
return (self.dialog.DIALOG_CANCEL, None)
def todo(output, shell, shells, proprio, self_cont, cont):
loginShell = shells[output]['path']
if shell != output:
if shell and shell != output:
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio['loginShell']=unicode(loginShell)
proprio.save()
......@@ -2381,6 +2448,7 @@ les valeurs valident sont :
)
def proprio_compte(self, proprio, cont, default_item=None):
"""Menu de gestion du compte crans d'un proprio"""
has_compte = 'cransAccount' in proprio['objectClass']
disabled_compte = has_compte and 0 in proprio['shadowExpire']
a = attributs
......@@ -2391,6 +2459,7 @@ les valeurs valident sont :
"Désactiver" : [a.nounou],
"Créer" : [a.cableur, a.nounou],
"Supprimer" : [a.cableur, a.nounou],
"Shell" : [a.nounou, a.soi, a.cableur],
}
menu = {
"Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password},
......@@ -2418,7 +2487,7 @@ les valeurs valident sont :
else:
menu_order.append("Désactiver")
menu_order.extend(['MailAlias', "Shell", "Password", "Supprimer"])
else:
else:
menu_order.append("Créer")
def box(default_item=None):
return self.dialog.menu(
......@@ -2433,7 +2502,7 @@ les valeurs valident sont :
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[key], proprio)])
choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)])
def todo(tag, menu, proprio, self_cont):
if not tag in menu_order:
......@@ -2457,6 +2526,7 @@ les valeurs valident sont :
)
def adherent_droits(self, adherent, cont, choices_values=None):
"""Gestion des droits d'un adhérent"""
def box():
return self.dialog.checklist(
text="",
......@@ -2484,12 +2554,10 @@ les valeurs valident sont :
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
@tailcaller
def proprio_vente_set(self, article, cont):
"""Permet de définir la quantité de l'article à vendre"""
def box():
if article['pu'] == '*':
return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'],
......@@ -2530,6 +2598,7 @@ les valeurs valident sont :
@tailcaller
def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]):
"""Menu de vente du crans. Permet également de recharger le solde crans"""
box_paiement = {
"liquide" : "Espèces",
"cheque" : "Chèque",
......@@ -2630,7 +2699,7 @@ les valeurs valident sont :
else:
self.dialog.msgbox(text=u"Le paiement n'ayant pas été reçue\nla vente est annulée", title="Annulation de la vente", width=0, height=0, timeout=self.timeout)
raise Continue(cont)
self_cont=TailCall(self.proprio_vente, proprio=proprio, cont=cont, tags=tags, tag_paiment=tag_paiment, to_set=to_set, have_set=have_set)
# S'il y a des article dont il faut définir la quantité
if to_set:
......@@ -2644,7 +2713,7 @@ les valeurs valident sont :
# Sinon, si tous les quantités de tous les articles sont définis
elif have_set:
lcont = self_cont.copy()
lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1])
lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1])
(code, tag) = self.handle_dialog(lcont, box_choose_paiment, tag_paiment, have_set)
self_cont=self_cont(tag_paiment=tag)
lcont(tag_paiment=tag)
......@@ -2668,6 +2737,7 @@ les valeurs valident sont :
)
def create_adherent(self, cont):
"""Crée un adhérent et potentiellement son compte crans avec lui"""
def mycont(adherent=None, **kwargs):
if adherent:
raise Continue(TailCall(self.modif_adherent, cont=cont, adherent=adherent))
......@@ -2721,10 +2791,12 @@ les valeurs valident sont :
return self.create_machine_proprio(cont=cont, proprio=club)
def create_machine_crans(self, cont):
"""Permet l'ajout d'une machine à l'association"""
associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
return self.create_machine_proprio(cont=cont, proprio=associationCrans)
def has_right(self, list, obj=None):
"""Vérifie que l'un des droits de l'utilisateur courant est inclus dans list"""
if obj:
droits = obj.rights()
else:
......@@ -2742,7 +2814,7 @@ les valeurs valident sont :
menu_droits = {
'default' : [a.cableur, a.nounou],
'aKM' : [a.nounou],
}
}
menu = {
'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': self.create_adherent,},
'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': self.modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."},
......@@ -2840,7 +2912,12 @@ les valeurs valident sont :
else:
return self_cont
@TailCaller
def main(gc, cont=None):
"""
Fonction principale gérant l'appel au menu principal,
le timeout des écrans dialog et les reconnexion a ldap en cas de perte de la connexion
"""
while True:
try:
# tant que le timeout est atteint on revient au menu principal
......@@ -2849,12 +2926,19 @@ def main(gc, cont=None):
# Si l'erreur n'est pas due à un timeout, on la propage
if time.time() - gc.dialog_last_access < gc.timeout:
raise
else:
try:
gc.nyan(TailCall(lambda:None))
except Continue:
pass
# Si on perd la connexion à ldap, on en ouvre une nouvelle
except ldap.SERVER_DOWN:
if gc.dialog.pause(title="Erreur", duration=10, height=9, width=50, text="La connection au serveur ldap à été perdue.\nTentative de reconnexion en cours…") == gc.dialog.DIALOG_OK:
gc.check_ldap()
else:
return
if __name__ == '__main__':
main(GestCrans())
os.system('clear')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment