diff --git a/attributs.py b/attributs.py index 1f7af71..7737934 100644 --- a/attributs.py +++ b/attributs.py @@ -548,7 +548,7 @@ class etudes(Attr): class chbre(Attr): singlevalue = True optional = False - unique = True + unique = False legend = u"Chambre sur le campus" can_modify = [soi, cableur, nounou] category = 'perso' @@ -1125,6 +1125,14 @@ class gecos(Attr): category = 'id' ldap_name = "gecos" +@crans_attribute +class userPassword(Attr): + singlevalue = True + optional = True + legend = "Le mot de passe" + category = '' + ldap_name = "userPassword" + @crans_attribute class sshFingerprint(Attr): singlevalue = False diff --git a/crans_utils.py b/crans_utils.py index 5435a2a..78ca065 100644 --- a/crans_utils.py +++ b/crans_utils.py @@ -41,6 +41,7 @@ import ldap.filter sys.path.append('/usr/scripts/gestion') import config from unicodedata import normalize +import subprocess DEVNULL = open(os.devnull, 'w') @@ -208,7 +209,7 @@ def process_status(pid): """ Vérifie l'état du processus pid """ - cmd = subprocess.check(['ps', '%s' % pid], stdout=DEVNULL, stderr=subprocess.STDOUT) + cmd = subprocess.call(['ps', '%s' % pid], stdout=DEVNULL, stderr=subprocess.STDOUT) if cmd != 0: return False else: diff --git a/lc_ldap.py b/lc_ldap.py index c525098..490afbd 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -84,6 +84,8 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): """ ldap.ldapobject.LDAPObject.__init__(self, uri) + + self.lockholder = ldap_locks.LdapLockHolder(self) if user and not re.match('[a-z_][a-z0-9_-]*', user): raise ValueError('Invalid user name: %r' % user) @@ -205,7 +207,7 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): s'éxécuter le plus rapidement possible. On dumpe malgré tout toute la base, c'est pour pouvoir aussi rajouter à moindre coût les propriétaires.""" - machines,_ = self.allMachinesAdherents(mode) + machines, _ = self.allMachinesAdherents(mode) return machines def allAdherents(self, mode='ro'): @@ -213,7 +215,7 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): s'éxécuter le plus rapidement possible. On dumpe malgré tout toute la base, c'est pour pouvoir aussi rajouter à moindre coût les machines.""" - _,adherents = self.allMachinesAdherents(mode) + _, adherents = self.allMachinesAdherents(mode) return adherents def newMachine(self, parent, realm, uldif, login=None): @@ -295,6 +297,13 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): '''Crée une nouvelle entité ldap avec le dn ``dn`` et les attributs de ``ldif``. Attention, ldif doit contenir des données encodées.''' + for key, values in uldif.iteritems(): + if key.endswith('id'): + continue + attribs = [attributs.attrify(val, key, self) for val in values] + for attribut in attribs: + if attribut.unique: + self.lockholder.addlock(key, str(attribut)) return objets.new_cransldapobject(self, dn, 'rw', uldif) def _find_id(self, attr, plage=None): @@ -314,12 +323,17 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): if my_id.value != i: continue else: - break + try: + self.lockholder.addlock(attr, str(i)) + break + except: + continue else: raise EnvironmentError('Aucun %s libre dans la plage [%d, %d]' % (attr, plage[0], i)) else: i = nonfree[-1]+1 + self.lockholder.addlock(attr, str(i)) return i def _check_parent(self, objdn): diff --git a/ldap_locks.py b/ldap_locks.py index 24801b3..f709739 100644 --- a/ldap_locks.py +++ b/ldap_locks.py @@ -38,6 +38,8 @@ import os import exceptions import socket import crans_utils +import collections +import subprocess class LdapLockedByYou(exceptions.StandardError): """ @@ -67,24 +69,37 @@ class LdapLockHolder: """ On crée la connexion, et on crée un dico vide. """ - self.locks = {} - self.host = socket.gethostbyname() + self.locks = collections.defaultdict(dict, {}) + self.host = socket.gethostname() self.pid = os.getpid() self.conn = conn - def __del__(self): + def purge(self, Id=None): """ On essaye de détruire tous les verrous hébergés par - l'objet mourant. + l'objet. """ - for item, value in self.locks: - try: - self.removelock(item, value) - except: - pass - del self.conn + if Id == None: + for key, subdict in self.locks['default'].keys(): + for item, value in subdict: + try: + self.removelock(item, value, key) + except: + pass + else: + for item, value in self.locks[Id].items(): + try: + self.removelock(item, value, Id) + except: + pass - def addlock(self, item, value): + def __del__(self): + """ + En cas de destruction du lockholder + """ + self.purge() + + def addlock(self, item, value, Id='default'): """ Applique un verrou sur "$item=$value,$LOCKS_DN", si le précédent verrou était géré par la session @@ -97,21 +112,21 @@ class LdapLockHolder: """ try: - host, pid = self.getlock(item, value) - if host == self.host and pid == self.pid(): + host, pid = self.getlock(item, value, Id) + if host == self.host and pid == self.pid: raise LdapLockedByYou("La donnée %r=%r est lockée par vous-même." % (item, value)) elif host == self.host: status = crans_utils.process_status(pid) if status: raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value)) else: - self.removelock(item, value) + self.removelock(item, value, Id, True) else: raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value)) except ldap.NO_SUCH_OBJECT: pass except LockFormatError: - self.removelock(item, value) + self.removelock(item, value, Id) dn = "%s=%s,%s" % (item, value, LOCKS_DN) lockid = "%s-%s" % (self.host, self.pid) @@ -121,38 +136,49 @@ class LdapLockHolder: try: self.conn.add_s(dn, modlist) - self.locks[item] = value + self.locks[Id][item] = value except ldap.ALREADY_EXISTS: status = crans_utils.process_status(pid) if status: raise LdapLockedByOther("La donnée %r=%r est lockée par quelqu'un d'autre ou un processus." % (item, value)) else: - self.removelock(item, value) + self.removelock(item, value, Id) try: self.conn.add_s(dn, modlist) - self.locks[item] = value + + # Si on a un pointeur vers l'id de l'objet, on en tient compte + self.locks[Id][item] = value except: raise StandardError("Quelque chose a planté durant la pose du lock %s=%s" % (item, value)) - def removelock(self, item, value): + def removelock(self, item, value, Id='default', force=False): """ Libère le lock "$item=$value,$LOCKS_DN". """ - print "Deleting %s=%s,%s\n" % (item, value, LOCKS_DN) - if self.locks.get(item, "") == value: - self.locks.pop(item) - self.conn.delete_s("%s=%s,%s" % (item, value, LOCKS_DN)) + if not force: + if self.locks[Id].get(item, "") == str(value): + _ = self.locks[Id].pop(item) + self.conn.delete_s("%s=%s,%s" % (item, value, LOCKS_DN)) + else: + pass else: - print "Lock %s=%s,%s does not exist on this session" % (item, value, LOCKS_DN) + try: + self.conn.delete_s("%s=%s,%s" % (item, value, LOCKS_DN)) + except: + pass - def getlock(self, item, value): + def getlock(self, item, value, Id): """ Trouve le lock item=value, et renvoie le contenu de lockinfo via un couple host, pid """ result = self.conn.search_s('%s=%s,%s' % (item, value, LOCKS_DN), 0) + if not result: + _ = self.locks[Id].pop(item) + return None, 0 try: - return result[0][1]['lockid'][0].split('-') + host, pid = result[0][1]['lockid'][0].split('-') + return host, int(pid) except: raise LockFormatError diff --git a/objets.py b/objets.py index 005d281..9cc1d0f 100644 --- a/objets.py +++ b/objets.py @@ -63,6 +63,12 @@ from gestion.gen_confs.dhcpd_new import dydhcp #: Champs à ignorer dans l'historique HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"] +crans_account_attribs = [attributs.uid, attributs.canonicalAlias, attributs.solde, + attributs.contourneGreylist, attributs.derniereConnexion, + attributs.homepageAlias, attributs.loginShell, attributs.gecos, + attributs.uidNumber, attributs.homeDirectory, + attributs.gidNumber, attributs.userPassword, + attributs.mailAlias, attributs.cn] def new_cransldapobject(conn, dn, mode='ro', uldif=None): """Crée un objet :py:class:`CransLdapObject` en utilisant la classe correspondant à @@ -159,7 +165,7 @@ class CransLdapObject(object): assert isinstance(login, unicode) assert isinstance(chain, unicode) - new_line = u"%s, %s : %s" % (time.strftime("%d/%m/%Y %H:%M"), login, chain) + new_line = u"%s, %s : %s" % (time.strftime("%d/%m/%Y %H:%M:%S"), login, chain) # Attention, le __setitem__ est surchargé, mais pas .append sur l'historique self["historique"] = self.get("historique", []) + [new_line] @@ -193,6 +199,12 @@ class CransLdapObject(object): modlist = addModlist(self._modifs.to_ldif()) # Requête LDAP de création de l'objet self.conn.add_s(self.dn, modlist) + # On nettoie les locks + for key, values in self._modifs.to_ldif().iteritems(): + for value in values: + self.conn.lockholder.removelock(key, value) + self.conn.lockholder.purge(id(self)) + # Services à relancer services.services_to_restart(self.conn, {}, self._modifs) self._post_creation() @@ -221,6 +233,7 @@ class CransLdapObject(object): raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn) self.bury(comm, login) self.conn.delete_s(self.dn) + self.conn.lockholder.purge(id(self)) self._post_deletion() services.services_to_restart(self.conn, self.attrs, {}) @@ -243,6 +256,9 @@ class CransLdapObject(object): # On programme le redémarrage des services services.services_to_restart(self.conn, self.attrs, self._modifs) + # On nettoie les locks + self.conn.lockholder.purge(id(self)) + # Vérification des modifications old_ldif = self.conn.search_s(self.dn, ldap.SCOPE_BASE)[0][1] old_uldif = lc_ldap.ldif_to_uldif(old_ldif) @@ -330,6 +346,9 @@ class CransLdapObject(object): if not mixed_attrs[0].is_modifiable(self.conn.droits + self.conn._check_parent(self.dn) + self.conn._check_self(self.dn)): raise EnvironmentError("Vous ne pouvez pas toucher aux attributs de type %r." % (attr)) self._modifs[attr] = attrs_before_verif + for attribut in attrs_before_verif: + if attribut.unique: + self.conn.lockholder.addlock(attr, str(attribut), id(self)) def search_historique(self, ign_fields=HIST_IGNORE_FIELDS): u"""Récupère l'historique @@ -617,15 +636,16 @@ class adherent(proprio): attributs.mail, attributs.mailInvalide, attributs.charteMA, attributs.derniereConnexion, attributs.gpgFingerprint, attributs.carteEtudiant, attributs.droits, attributs.etudes, - attributs.postalAddress, attributs.mailExt, attributs.compteWiki] + attributs.postalAddress, attributs.mailExt, attributs.compteWiki, + ] ldap_name = "adherent" def __init__(self, conn, dn, mode='ro', ldif = None): super(adherent, self).__init__(conn, dn, mode, ldif) + self.full = False if u'cransAccount' in [ unicode(o) for o in self['objectClass']]: - self.attribs = self.attribs + [attributs.uid, attributs.canonicalAlias, attributs.solde, - attributs.contourneGreylist, attributs.derniereConnexion, - attributs.homepageAlias, attributs.mailAlias, attributs.loginShell ] + self.attribs = self.attribs + crans_account_attribs + self.full = True def compte(self, login = None, uidNumber=0, hash_pass = '', shell=config.login_shell): u"""Renvoie le nom du compte crans. S'il n'existe pas, et que uid @@ -649,18 +669,21 @@ class adherent(proprio): if os.path.exists("/var/mail/" + login): raise ValueError('Création du compte impossible : /var/mail/%s existant' % login) - - self._modifs['homeDirectory'] = [home] - self._modifs['mail'] = [login] - self._modifs['uid' ] = [login] + + if not self.full: + self.attribs = self.attribs + crans_account_attribs + self.full = True + self['homeDirectory'] = [home] + self['mail'] = [login + u"@crans.org"] + self['uid' ] = [login] calias = crans_utils.strip_spaces(fn) + u'.' + crans_utils.strip_spaces(ln) if crans_utils.mailexist(calias): calias = login - self._modifs['canonicalAlias'] = [calias] - self._modifs['objectClass'] = [u'adherent', u'cransAccount', u'posixAccount', u'shadowAccount'] - self._modifs['cn'] = [ fn + u' ' + ln ] - self._modifs['loginShell'] = [unicode(shell)] - self._modifs['userPassword'] = [unicode(hash_pass)] + self['canonicalAlias'] = [calias] + self['objectClass'] = [u'adherent', u'cransAccount', u'posixAccount', u'shadowAccount'] + self['cn'] = [ fn + u' ' + ln ] + self['loginShell'] = [unicode(shell)] + self['userPassword'] = [unicode(hash_pass)] if uidNumber: if self.conn.search('(uidNumber=%s)' % uidNumber): @@ -675,18 +698,11 @@ class adherent(proprio): if not len(pool_uid): raise ValueError("Plus d'uid disponibles !") - ## try: - ## self.lock('uidNumber', str(uidNumber)) - ## except: - ## # Quelqu'un nous a piqué l'uid que l'on venait de choisir ! - ## return self.compte(login, uidNumber, hash_pass, shell) - - self._modifs['uidNumber'] = [unicode(uidNumber)] - self._modifs['gidNumber'] = [unicode(config.gid)] - self._modifs['gecos'] = [self._modifs['cn'][0] + u',,,'] - - self.save() + self['uidNumber'] = [unicode(uidNumber)] + self['gidNumber'] = [unicode(config.gid)] + self['gecos'] = [self._modifs['cn'][0] + u',,,'] + #self.save() else: raise EnvironmentError("L'adhérent n'a pas de compte crans") diff --git a/test.py b/test.py index 3bb6bba..dd98d98 100755 --- a/test.py +++ b/test.py @@ -53,7 +53,7 @@ borne_ldif = { 'macAddress' : [randomMAC()], 'host' : ["autotest-%s.crans.org" % randomStr() ], 'canal' : ["11"], - 'puissance' : ["52 khz"], + 'puissance' : [u"52"], } club_ldif = { @@ -97,7 +97,6 @@ def test_list_of_dict(keys, list): anim("\tTest de l'attribut %s" % key) ok = True for item in list: - if key == "chbre": print item['aid'][0] try: item.get(key, []) except psycopg2.OperationalError as error: diff --git a/variables.py b/variables.py index 18c07ed..2e84ac4 100644 --- a/variables.py +++ b/variables.py @@ -33,3 +33,4 @@ deleted = 'deleted' #: Mot de passe de la base de tests ldap_test_password = '75bdb64f32' +