machine.py 16.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-

u"""
Copyright (C) Valentin Samir
Licence : GPLv3

"""
import sys
if '/usr/scripts' not in sys.path:
    sys.path.append('/usr/scripts')

import lc_ldap.objets as objets
import lc_ldap.attributs as attributs

import certificat
import blacklist
from CPS import TailCall, tailcaller, Continue

class Dialog(certificat.Dialog, blacklist.Dialog):
    def machine_information(self, cont, machine=None, objectClass=None, proprio=None, realm=None, fields_values=None):
        """
        Permet de modifier une machine si elle est fournit par le paramètre machine
        sinon, crée une machine à partir de proprio, objectClass et realm.
        Si on ne fait qu'éditer une machine, proprio, objectClass et realm sont ignoré
        D'une machinère générale, il faudrait mettre ici tous les attributs single value
        et les multivalué que l'on peut simplement représenter de façon textuelle avec
        un séparateur.
        Pour le moment il y a :
            * host
            * macAddress
            * ipHostNumber
            * port(TCP|UDP)(in|out)
        """
        a = attributs
        # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant
        to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15),
                      (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
                      (a.portUDPin, 50)
                     ]

        # Quel séparateur on utilise pour les champs multivalué
        separateur = ' '

        def box():
            if machine:
                attrs = dict((k,[str(a) for a in at]) for k,at in machine.items())
            else:
                attrs = {}

            fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display]

            return self.dialog.form(
                    text="",
                    timeout=self.timeout,
                    height=0, width=0, form_height=0,
                    fields=fields_values if fields_values else fields,
                    title="Paramètres machine",
                    backtitle="Gestion des machines du Crans")

        def check_host(host, objectClass):
            # Si c'est une machine wifi, host doit finir par wifi.crans.org
            if "machineWifi" == objectClass or 'borneWifi' == objectClass:
                hostend = ".wifi.crans.org"
            # Si c'est une machine wifi, host doit finir par crans.org
            elif "machineFixe" == objectClass:
                hostend = ".crans.org"
            # Si l'object class est machineCrans, pas de vérification
            elif "machineCrans" == objectClass:
                return host
            # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer
            # plus finement fonction des droits de self.conn.droits
            else:
                raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass)

            if not host.endswith(hostend) and not '.' in host:
77
                host = host + hostend
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
            elif host.endswith(hostend) and '.' in host[:-len(hostend)]:
                raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
            elif not host.endswith(hostend) and '.' in host:
                raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)

            return host

        def modif_machine(machine, attrs):
            with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
                for (key, values) in attrs.items():
                    machine[key]=values
                machine.validate_changes()
                machine.history_gen()
                machine.save()
                return machine

        def create_machine(proprio, realm, attrs):
            # Dans ce cas, on a besoin d'un proprio et d'un realm pour déterminer le rid
            if proprio is None or realm is None:
                raise EnvironmentError("On essaye de créer une machine mais proprio ou realm vaut None")
            ldif = {
                'macAddress': ['%s' % attrs['macAddress']],
                'host': ['%s' % attrs['host']]
            }
            with self.conn.newMachine(proprio.dn, realm, ldif) as machine:
                for (key, values) in attrs.items():
                    machine[key]=values
                if attributs.ipsec in machine.attribs:
                    machine[attributs.ipsec.ldap_name]=attributs.ipsec.default
                machine.validate_changes()
                if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"):
                    machine.create()
                    self.display_item(machine, "La machine à bien été créée", ipsec=True)
                    return machine
                else:
                    raise Continue(cont)

        def todo(to_display, tags, objectClass, machine, proprio, realm, separateur, cont):
            attrs = {}
            # On traite les valeurs reçues
            for ((a,l),values) in zip(to_display, tags):
                values = unicode(values, 'utf-8')
                # Si le champs n'est pas single value, on utilise separateur pour découper
                # et on ne garde que les valeurs non vides
                if not a.singlevalue:
                    values = [v for v in values.split(separateur) if v]
                # Pour host, on fait quelques vérification de syntaxe
                if a.ldap_name == 'host':
                    attrs[a.ldap_name]=check_host(values, objectClass)
                else:
                    attrs[a.ldap_name]=values
            # Soit on édite une machine existante
            if machine:
                machine = modif_machine(machine, attrs)
            # Soit on crée une nouvelle machine
            else:
                machine = create_machine(proprio, realm, attrs)
            raise Continue(cont(machine=machine))


        if machine:
            objectClass = machine["objectClass"][0]

        (code, tags) = self.handle_dialog(cont, box)

        # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu
        # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré
        # c'est au cableur de corriger ou d'annuler
        fields_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)]
        retry_cont = TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, proprio=proprio, realm=realm, fields_values=fields_values)

        return self.handle_dialog_result(
            code=code,
            output=tags,
            cancel_cont=cont,
            error_cont=retry_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, tags, objectClass, machine, proprio, realm, separateur, cont])]
        )

    def modif_machine_blacklist(self, machine, cont):
        """Raccourci vers edit_blacklist spécifique aux machines"""
        return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont)



    def modif_machine_attributs(self, machine, attr, cont):
        """Juste un raccourci vers edit_attributs spécifique aux machines"""
        return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont)

    def modif_machine_boolean(self, machine, cont):
        """Juste un raccourci vers edit_boolean_attributs spécifique aux machines"""
        a = attributs
        attribs = [a.dnsIpv6]
        return self.edit_boolean_attributs(
                    obj=machine,
                    attribs=attribs,
                    title="Édition des attributs booléen de la machine %s" % machine['host'][0],
                    update_obj='machine',
                    cont=cont)



    def modif_machine(self, cont, machine=None, tag=None):
        """
        Permet d'éditer une machine. Si fournie en paramètre on éditer en place,
        sinon, on en cherche une dans la base ldap
        """
        if machine is None:
            machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont)
        a = attributs
        menu_droits = {
          'Information' : [a.parent, a.cableur, a.nounou],
          'Autre': [a.parent, a.cableur, a.nounou],
          'Blackliste':[a.cableur, a.nounou],
          'Certificat': [a.parent, a.cableur, a.nounou],
          'Exemption' : [a.nounou],
          'Alias' : [a.parent, a.cableur, a.nounou],
          'Remarques' : [a.cableur, a.nounou],
          'SshKey' : [a.parent, a.nounou],
          'Supprimer' : [a.parent, a.cableur, a.nounou],
        }
        menu = {
            'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information},
            'Autre'       : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean},
            'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist},
            'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat},
            'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt},
            'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias},
            'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
            'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint},
            'Supprimer' : {'text':'Supprimer la machine', 'callback':self.delete_machine},
        }
        menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques', 'Supprimer']
        def box(default_item=None):
            return self.dialog.menu(
                "Que souhaitez vous modifier ?",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=0,
                default_item=str(default_item),
                title="Modification de %s" % machine['host'][0],
                scrollbar=True,
                cancel_label="Retour",
223
                backtitle=self._connected_as(),
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
                choices=[(key,  menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], machine)])

        def todo(tag, menu, machine, cont_ret):
            if not tag in menu_order:
                raise Continue(cont_ret)
            else:
                if 'callback' in menu[tag]:
                    raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret))
                elif 'attribut' in menu[tag]:
                    raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name))
                else:
                    raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)

        (code, tag) = self.handle_dialog(cont, box, tag)
        cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)

        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont(machine=machine),
            error_cont=cont_ret,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, machine, cont_ret])]
        )

    def create_machine_proprio(self, cont, proprio, tag=None):
        """Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)"""
        a = attributs
        menu_droits = {
            'Fixe' : [a.soi, a.cableur, a.nounou],
            'Wifi' : [a.soi, a.cableur, a.nounou],
        }
        menu = {
            'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'},
            'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'},
        }
        menu_order = ['Fixe', 'Wifi']
        if isinstance(proprio, objets.AssociationCrans):
            menu_droits.update({
                'Fixe' : [a.nounou],
                'Wifi' : [a.nounou],
                'Adm' : [a.nounou],
            })
            menu.update({
                'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'},
                'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'},
                'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'},
            })
            menu_order.append('Adm')
        def box(default_item=None):
            return self.dialog.menu(
                "Type de Machine ?",
                width=0,
                height=0,
                menu_height=0,
                item_help=0,
                timeout=self.timeout,
                default_item=str(default_item),
                title="Création de machines",
                scrollbar=True,
                cancel_label="Retour",
284
                backtitle=self._connected_as(),
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
                choices=[(key,  menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], proprio)])

        def todo(tag, menu, proprio, self_cont, cont):
            if not tag in menu_order:
                raise Continue(self_cont)
            else:
                return self.machine_information(
                   cont=cont,
                   machine=None,
                   objectClass=menu[tag]['objectClass'],
                   proprio=proprio,
                   realm=menu[tag]['realm']
                 )

        (code, tag) = self.handle_dialog(cont, box, tag)
        cont = cont(proprio=None) if isinstance(proprio, objets.AssociationCrans) else cont(proprio=proprio)
        self_cont = TailCall(self.create_machine_proprio, cont=cont, proprio=proprio, tag=tag)

        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont, cont])]
        )

    def create_machine_adherent(self, cont, adherent=None):
        """
        Permet d'ajouter une machine à un adhérent.
        On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
        """
        if adherent is None:
            adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont)
        return self.create_machine_proprio(cont=cont, proprio=adherent)

    def create_machine_club(self, cont, club=None):
        """
        Permet d'ajouter une machine à un club.
        On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
        """
        if club is None:
            club = self.select(["club"], "Recherche d'un club pour lui ajouter une machine", cont=cont)
        return self.create_machine_proprio(cont=cont, proprio=club)

    def create_machine_crans(self, cont):
        """Permet l'ajout d'une machine à l'association"""
        associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
        return self.create_machine_proprio(cont=cont, proprio=associationCrans)
    def delete_machine(self, cont, machine=None):
        """Permet la suppression d'une machine de la base ldap"""
        if machine is None:
            machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont)

        def todo(machine):
            if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True):
                with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
                    machine.delete()
                self.dialog.msgbox("La machine a bien été supprimée", timeout=self.timeout, title="Suppression d'une machine")
                raise Continue(cont(machine=None))
            else:
                raise Continue(cont)

        return self.handle_dialog_result(
            code=self.dialog.DIALOG_OK,
            output="",
            cancel_cont=cont,
            error_cont=cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [machine])]
        )