lc_ldap.py 25 KB
Newer Older
1 2 3 4 5
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# LC_LDAP.PY-- LightWeight CransLdap
#
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
6 7 8 9 10 11 12
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
# Authors: Antoine Durand-Gasselin <adg@crans.org>
#          Nicolas Dandrimont <olasd@crans.org>
#          Olivier Iffrig <iffrig@crans.org>
#          Valentin Samir <samir@crans.org>
#          Daniel Stan <dstan@crans.org>
#          Vincent Le Gallic <legallic@crans.org>
13
#          Pierre-Elliott Bécue <becue@crans.org>
14 15 16 17 18 19 20 21 22 23 24 25
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
# * Neither the name of the Cr@ns nor the names of its contributors may
#   be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
26 27 28 29 30 31 32 33 34 35
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 37
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

38
## import de la lib standard
39
import os
40
import sys
41
import re
42
from contextlib import contextmanager
43 44 45

import ldap

46
## import locaux
47
import crans_utils
48
import attributs
49
import cimetiere
50
import objets
51
import ldap_locks
52
import variables
53
import copy
54
import itertools
55

56 57 58 59 60
## import de /usr/scripts/
if not "/usr/scripts/" in sys.path:
    sys.path.append('/usr/scripts/')

import gestion.config as config
61
import cranslib.deprecated
62

63 64
# A priori, ldif_to_uldif et ldif_to_cldif sont obsolètes,
# du fait de l'apparition de AttrsDict dans attributs.py
65
def ldif_to_uldif(ldif):
66 67 68 69
    """
    Transforme un dictionnaire ldif en un dictionnaire
    ldif unicode.
    """
70 71
    uldif = {}
    for attr, vals in ldif.items():
72 73 74 75 76 77 78 79
        if attr.endswith(';binary'):
            binary = True
            attr=attr[:-7]
        else:
            binary = False
        attr_class = attributs.AttributeFactory.get(attr, fallback=attributs.Attr)
        binary = binary or attr_class.binary
        uldif[attr] = [ unicode(val, 'utf-8') if not binary else val for val in vals ]
80 81
    return uldif

82
class lc_ldap(ldap.ldapobject.LDAPObject, object):
83 84
    """Connexion à la base ldap crans, chaque instance représente une connexion
    """
85
    __slots__ = ("lockholder", "conn", "dn", "droits", "current_login", "_username_given")
86 87
    def __init__(self, dn=None, user=None, cred=None, uri=variables.uri,
                 readonly_dn=None, readonly_password=None):
88
        """Initialise la connexion ldap,
89 90 91 92
         - En authentifiant avec ``dn`` et ``cred`` s'ils sont précisés
         - Si ``dn`` n'est pas précisé, mais que ``user`` est précisé, récupère
           le ``dn`` associé à l'uid ``user``, et effectue l'authentification
           avec ce ``dn`` et ``cred``
93
         - Sinon effectue une authentification anonyme
94

95 96
         Si on ne se binde pas en anonyme, il faut de toutes façons fournir un ``user``.
         Il sert à savoir qui fait les modifications (utilisé dans les champs historique).
97

98 99
        """
        ldap.ldapobject.LDAPObject.__init__(self, uri)
100

101
        self.lockholder = ldap_locks.LdapLockHolder(self)
102 103

        if user and not re.match('[a-z_][a-z0-9_-]*', user):
104
            raise ValueError('Invalid user name: %r' % user)
105

106
        # Si un username, on récupère le dn associé…
107
        if user and not dn:
108
            dn = self.user_to_dn(user, readonly_dn, readonly_password)
109 110 111

        # Si on a un dn, on se connecte avec à la base ldap sinon on s'y
        # connecte en anonyme
112
        if dn:
113
            self.conn = self.bind_s(dn, cred)
114
            self.dn = dn
115
            self.droits = self.search_s(dn, ldap.SCOPE_BASE, attrlist=['droits'])[0][1].get('droits', [])
116
            if dn == variables.admin_dn:
117
                self.droits += [attributs.nounou]
118 119 120 121 122 123 124 125
            # Il faut peupler current_login, qui sera utilisé pour écrire dans l'historique qui fait des modifications
            if dn in [variables.admin_dn, variables.readonly_dn]:
                # À ce stade, l'utilsateur qui appelle le script a réussi à se binder en cn=admin ou cn=readonly,
                # c'est donc qu'il a pu lire les secrets, (directement ou par un sudo idoine)
                # on lui fait donc confiance sur l'username qu'il fournit à condition qu'il en ait fournit un, quand même
                if user is None:
                    raise ValueError("Même root doit préciser qui il est pour se connecter à la base LDAP.")
                self.current_login = user
126
                real_user = self.search(u'(&(uid=%s)(objectClass=cransAccount))' % user)
127
                # Si l'utilisteur existe vraiement, on utilise les droits de cet utilisateur
128
                if real_user:
129
                    self.droits = map(unicode, real_user[0]['droits'])
130
                    self.dn = real_user[0].dn
131
            else:
Valentin Samir's avatar
Valentin Samir committed
132
                current_user = self.search(u'uid=%s' % user)
133 134 135
                if len(current_user) != 1:
                    raise ValueError("L'utilisateur %s n'est pas présent dans la base en *1* exemplaire." % user)
                else:
136
                    self.current_login = unicode(current_user[0]["uid"][0])
137 138
        else:
            self.conn = self.simple_bind_s()
139
            self.dn = None
140
            self.droits = []
141 142
        self._username_given = user

143 144 145 146 147 148
    def __repr__(self):
        if self.dn:
            return str(self.__class__) + " : " + self.dn
        else:
            return super(lc_ldap, self).__repr__()

149 150 151 152 153 154 155 156 157
    def gravedig(self, type, filter=None, date=None):
        """Cherche dans le cimetière un objet de type ``type``,
        correspondant au filtre ``filter`` entre les dates ``date[0]`` et ``date[1]``
        où la date est de la forme YYYY-MM-JJ ou - pour l'infini"""
        valid=cimetiere.find(type, filter, date)
        return [self.ressuscite(item) for item in valid]

    @staticmethod
    def ressuscite_build_ldif(ldif_file):
158 159 160 161 162 163
        ldif={}
        for line in open(ldif_file).readlines():
            line = line.split(':',1)
            if len(line)==2:
                (key, value) = line
                ldif[key]=ldif.get(key, []) + [value.strip()]
164 165 166 167 168 169 170 171 172 173 174 175
        try:
           dn = ldif['dn'][0]
           del(ldif['dn'])
           return (dn,ldif)
        except KeyError as error:
            raise KeyError("%s in %s" % (error, ldif_file))

    def ressuscite(self, ldif_file, login=None):
        if login is None:
            login = self.current_login
        (dn, ldif)= self.ressuscite_build_ldif(ldif_file)
        # On définit de nouveaux dn si ceux-ci sont déjà pris
176
        lockId = self.lockholder.newid()
177 178
        try:
            if self.search(dn=dn):
179
                for id in ["aid", "mid", "fid", "cid", "xid"]:
180
                    if dn.startswith("%s=" % id):
181
                        ldif[id]=[str(self._find_id(id, lockId=lockId))]
182
                        dn="%s=%s,%s" % (id, ldif[id][0], dn.split(',',1)[1])
183 184
        except ldap.NO_SUCH_OBJECT:
            pass
185 186 187 188 189
        if "rid" in ldif and self.search(u"rid=%s" % ldif["rid"][0]):
            realm = crans_utils.find_rid_plage(int(ldif["rid"][0]))[0]
            ldif["rid"]=[str(self._find_id("rid", realm=realm, lockId=lockId))]
            ldif['ipHostNumber'] = [ str(crans_utils.ip4_of_rid(int(ldif["rid"][0]))) ]

190
        obj = self._create_entity(dn, ldif_to_uldif(ldif), lockId)
191 192 193
        obj.history_add(login, u"resurrection")
        return obj

194 195 196 197 198 199
    def user_to_dn(self, user, readonly_dn, readonly_password):
        """Cherche le dn à partir de l'username.
           Cette méthode est utilisée par l'intranet2 (en mode sans CAS) car il donne l'username."""
        # On commence par se binder en readonly
        self.simple_bind_s(who=readonly_dn, cred=readonly_password)
        res = self.search_s(variables.base_dn, 1, 'uid=%s' % user)
200 201 202 203 204 205 206 207
        if len(res) < 1:
            raise ldap.INVALID_CREDENTIALS({'desc': 'No such user: %s' % user })
        elif len(res) > 1:
            raise ldap.INVALID_CREDENTIALS({'desc': 'Too many matches: uid=%s' % user })
        else:
            dn = res[0][0]
        return dn

208
    def search(self, filterstr=u'(objectClass=*)', mode='ro', dn=variables.base_dn, scope=ldap.SCOPE_SUBTREE, sizelimit=1000):
209 210 211
        """La fonction de recherche dans la base LDAP qui renvoie un liste de
        :py:class:`CransLdapObject`. On utilise la feature de ``sizelimit`` de
        ``python-ldap``"""
212
        if not isinstance(filterstr, unicode):
213
            cranslib.deprecated.usage("search ne devrait utiliser que des unicode comme filtre(%r)" % filterstr, level=2)
214 215
            filterstr = filterstr.decode('utf-8')
        ldap_res = self.search_ext_s(dn, scope, filterstr.encode('utf-8'), sizelimit=sizelimit)
216 217
        ret = []
        for dn, ldif in ldap_res:
218 219
            uldif = ldif_to_uldif(ldif)
            ret.append(objets.new_cransldapobject(self, dn, mode, uldif))
220
        return ret
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    def machinesMulticast(self):
        import cPickle
        import tv.dns
        import config.dns
        machines = []
        sap=cPickle.load(open('/usr/scripts/var/tv/sap.pickel'))
        for name_ip in sap.values():
            for (nom, ip) in name_ip.items():
                nom=unicode(nom, 'utf-8')
                nom_ascii=tv.dns.ascii(nom)
                nom_punycode=tv.dns.punycode(nom)
                ldif = {
                    'ipHostNumber' : [unicode(ip)],
                    'objectClass': [u'machineFixe'],
                    'host': [u"%s.%s" % (nom_ascii, config.dns.zone_tv)]
                   }
                if nom_punycode:
                    ldif['hostAlias']=[u"%s.%s" % (nom_punycode, config.dns.zone_tv)]
240
                machines.append(objets.machineMulticast(self, "", uldif=ldif))
241 242
        return machines

243
    def allMachinesAdherents(self, mode='ro'):
244
        """Renvoie la liste de toutes les machines et de tous les adherents
245 246
        (club et Association Crans compris). Conçue pour s'éxécuter le plus
        rapidement possible. On dumpe malgré tout toute la base."""
247
        res = {}
248
        parent = {}
249
        machines = {}
250
        # (proxying de la base ldap)
251
        for dn, attrs in self.search_s(variables.base_dn, scope=2):
252 253
        # On crée les listes des machines et propriétaires
            if dn.startswith('mid='): # les machines
254
                m = objets.new_cransldapobject(self, dn, mode, uldif=ldif_to_uldif(attrs))
255
                parent_dn = dn.split(',', 1)[1]
256
                if not machines.has_key(parent_dn):
257
                   machines[parent_dn] = []
258
                machines[parent_dn].append(m)
259
            elif (dn.startswith('aid=') or dn.startswith('cid=') or dn == variables.base_dn) and not parent.has_key(dn):
260
                parent[dn] = objets.new_cransldapobject(self, dn, mode, uldif=ldif_to_uldif(attrs))
261
        allmachines = []
262
        for dn, mlist in machines.iteritems(): # on associe propriétaires et machines
263
            parent[dn]._machines = mlist
264
            for m in mlist:
265
                m._proprio = parent[dn]
266
                allmachines.append(m)
267
        return allmachines, parent.values() # on renvoie la liste des machines et des adherents (dont club et crans)
268

269
    def allMachines(self, mode='ro'):
270 271 272 273
        """Renvoie la liste de toutes les machines, Conçue pour
        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."""
274
        machines, _ = self.allMachinesAdherents(mode)
275 276
        return machines

277
    def allAdherents(self, mode='ro'):
278 279 280 281
        """Renvoie la liste de toutes les adherents, Conçue pour
        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."""
282
        _, adherents = self.allMachinesAdherents(mode)
283 284
        return adherents

285
    @contextmanager
286
    def newMachine(self, parent, realm, mldif, login=None):
287 288
        """
        Crée une nouvelle machine: ``realm`` peut être:
289
        fil, adherents-v6, wifi, wifi-adh-v6, adm, gratuit, personnel-ens, special
290
        mldif est un uldif pour la machine
291 292 293 294 295 296 297 298 299 300 301 302
        --Partiellement implémenté

        Doit être utilisé avec un context manager, c'est à dire comme ci-dessous :
            1: with newMachine(parent, realm, mldif) as machine:
            2:    machine.create()
            3: print machine
        La fonction est executé jusqu'au yield à la ligne 1, puis son exécution reprend
        au niveau du yield jusqu'à la fin de la fonction à la ligne 3, en sortant du contexte
        Une fois sorti du contexte, il n'est plus possible d'effectuer des actions d'écriture
        sur l'objet.
        """
        lockId = self.lockholder.newid()
303 304
        # On ne veut pas modifier mldif directement
        uldif = copy.deepcopy(mldif)
305
        if login is None:
306
            login = self.current_login
307
        #adm, serveurs, bornes, wifi, adherents, gratuit ou personnel-ens"""
Valentin Samir's avatar
Valentin Samir committed
308
        owner = self.search(u'objectClass=*', dn=parent, scope=0)[0]
309

310
        if realm in ["adm", "serveurs", "serveurs-v6", "adm-v6"]:
311
            uldif['objectClass'] = [u'machineCrans']
312
            assert isinstance(owner, objets.AssociationCrans)
313 314

        elif realm == "bornes":
315
            uldif['objectClass'] = [u'borneWifi']
316
            assert isinstance(owner, objets.AssociationCrans)
317

318
        elif realm in ["wifi-adh", "wifi-adh-v6"]:
319
            uldif['objectClass'] = [u'machineWifi']
320
            assert isinstance(owner, objets.adherent) or isinstance(owner, objets.club)
321

322
        elif realm in ["adherents", "adherents-v6", "personnel-ens"]:
323
            uldif['objectClass'] = [u'machineFixe']
324
            assert isinstance(owner, objets.adherent) or isinstance(owner, objets.club)
325

326 327
        else:
            raise ValueError("Realm inconnu: %r" % realm)
328

329 330 331 332
        try:
            # On récupère le premier id libre dans la plages s'il n'est pas
            # déjà précisé dans le ldif
            rid = uldif.setdefault('rid', [unicode(self._find_id('rid', realm, lockId=lockId))])
333

334 335 336
            # La machine peut-elle avoir une ipv4 ?
            if 'v6' not in realm:
                uldif['ipHostNumber'] = [ unicode(crans_utils.ip4_of_rid(int(rid[0]))) ]
337

338 339
            ip6 = unicode(crans_utils.ip6_of_mac(uldif['macAddress'][0], int(rid[0])))
            uldif['ip6HostNumber'] = [ ip6 ] if ip6 else []
340

341 342
            # Mid
            uldif['mid'] = [ unicode(self._find_id('mid', lockId=lockId)) ]
343

344 345 346 347 348 349 350 351 352 353 354 355 356 357
            # Tout doit disparaître !!
            machine = self._create_entity('mid=%s,%s' % (uldif['mid'][0], parent), uldif, lockId)
            machine.__enter__()
            if machine.may_be(variables.created, self.droits + self._check_parent(machine.dn)):
                yield machine
            else:
                raise EnvironmentError("Vous n'avez pas le droit de créer cette machine.")
        finally:
            try:
                 machine.__exit__(None, None, None)
            except NameError:
                self.lockholder.purge(lockId)

    @contextmanager
358
    def newAdherent(self, aldif):
359 360
        """Crée un nouvel adhérent
        Doit être utilisé avec un context manager, voir newMachine pour plus de détails"""
361
        lockId = self.lockholder.newid()
362
        uldif = copy.deepcopy(aldif)
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
        try:
            aid = uldif.setdefault('aid', [ unicode(self._find_id('aid', lockId=lockId)) ])
            uldif['objectClass'] = [u'adherent']
            adherent = self._create_entity('aid=%s,%s' % (aid[0], variables.base_dn), uldif, lockId)
            adherent.__enter__()
            if adherent.may_be(variables.created, self.droits):
                yield adherent
            else:
                raise EnvironmentError("Vous n'avez pas le droit de créer cet adhérent.")
        finally:
            try:
                adherent.__exit__(None, None, None)
            except NameError:
                self.lockholder.purge(lockId)

    @contextmanager
379
    def newClub(self, cldif):
380 381
        """Crée un nouveau club
        Doit être utilisé avec un context manager, voir newMachine pour plus de détails"""
382
        lockId = self.lockholder.newid()
383
        uldif = copy.deepcopy(cldif)
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
        try:
            cid = uldif.setdefault('cid', [ unicode(self._find_id('cid', lockId=lockId)) ])
            uldif['objectClass'] = [u'club']
            club = self._create_entity('cid=%s,%s' % (cid[0], variables.base_dn), uldif, lockId)
            club.__enter__()
            if club.may_be(variables.created, self.droits):
                yield club
            else:
                raise EnvironmentError("Vous n'avez pas le droit de créer cet adhérent.")
        finally:
            try:
                club.__exit__(None, None, None)
            except NameError:
                self.lockholder.purge(lockId)

    @contextmanager
400
    def newFacture(self, parent, fldif):
401 402
        """Crée une nouvelle facture
        Doit être utilisé avec un context manager, voir newMachine pour plus de détails"""
403
        lockId = self.lockholder.newid()
404
        uldif = copy.deepcopy(fldif)
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
        try:
            # fid
            uldif['fid'] = [ unicode(self._find_id('fid', lockId=lockId)) ]
            uldif['objectClass'] = [u'facture']
            facture = self._create_entity('fid=%s,%s' % (uldif['fid'][0], parent), uldif, lockId)
            facture.__enter__()
            if facture.may_be(variables.created, self.droits + self._check_parent(facture.dn)):
                yield facture
            else:
                raise EnvironmentError("Vous n'avez pas le droit de créer cette facture.")
        finally:
            try:
                facture.__exit__(None, None, None)
            except NameError:
                self.lockholder.purge(lockId)

421

422
    @contextmanager
423
    def newCertificat(self, parent, xldif):
424 425
        """Crée un nouveau certificat x509
        Doit être utilisé avec un context manager, voir newMachine pour plus de détails"""
426
        lockId = self.lockholder.newid()
427
        uldif = copy.deepcopy(xldif)
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
        try:
            # xid
            uldif['xid'] = [ unicode(self._find_id('xid', lockId=lockId)) ]
            uldif['objectClass'] = [u'baseCert']
            baseCert = self._create_entity('xid=%s,%s' % (uldif['xid'][0], parent), uldif, lockId)
            baseCert.__enter__()
            if baseCert.may_be(variables.created, self.droits + self._check_parent(baseCert.dn)):
                yield baseCert
            else:
                raise EnvironmentError("Vous n'avez pas le droit de créer ce certiticat.")
        finally:
            try:
                baseCert.__exit__(None, None, None)
            except NameError:
                self.lockholder.purge(lockId)
443

444
    def _create_entity(self, dn, uldif, lockId):
445 446 447
        '''Crée une nouvelle entité ldap avec le dn ``dn`` et les
        attributs de ``ldif``. Attention, ldif doit contenir des
        données encodées.'''
448 449
        # Ajout des locks, on instancie les attributs qui ne sont pas
        # des id, ceux-ci étant déjà lockés.
450 451 452 453
        try:
            for key, values in uldif.iteritems():
                attribs = [attributs.attrify(val, key, self) for val in values]
                for attribut in attribs:
454
                    if attribut.unique:
455 456 457 458 459 460
                        try:
                            self.lockholder.addlock(key, str(attribut), Id=lockId)
                        except ldap_locks.LdapLockedByMySelf:
                            # On vient juste d'acquérir le lock dans _find_id, ça n'est pas grâve
                            pass
            return objets.new_cransldapobject(self, dn, 'rw', uldif, lockId=lockId)
461 462
        except ldap_locks.LockError:
            # On supprime seulement les locks que l'on vient de poser
463
            self.lockholder.purge(lockId)
464
            raise
465

466
    def _find_id(self, attr, realm=None, lockId=None):
467 468
        '''Trouve un id libre. Si une plage est fournie, cherche
        l'id dans celle-ci, sinon, prend le plus élevé possible.'''
469 470 471
        if lockId is None:
            raise ValueError("lockId ne devrait pas être à None")

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
        plage = None
        # On récupère la plage des ids
        if realm != None:
            plage = itertools.chain(*[xrange(a,b+1) for (a,b) in config.rid_primaires[realm]])

        # Si plage vaut None, on veux un id strictement croissant
        if plage is None:
            # On essaye de récupérer le dernier id si on l'a déjà vu passer
            try:
                last_id = open('/tmp/lc_ldap_lastid_%s_%s' % (attr, os.getuid())).read().strip()
            except IOError:
                last_id = 0
            # On récupère tous les id plus grand que le dernier que l'on connait
            res = self.search_s(variables.base_dn, ldap.SCOPE_SUBTREE, '%s>=%s' % (attr, last_id), attrlist = [attr])
            # Si jamais id n'a pas de methode ORDERING, on récupère une liste vide et on fallback en récupérant tous les id (c'est lent)
            if res == []:
                res = self.search_s(variables.base_dn, ldap.SCOPE_SUBTREE, '%s=*' % attr, attrlist = [attr])
        else:
            # On récupère tous les id
491
            res = self.search_s(variables.base_dn, ldap.SCOPE_SUBTREE, '%s=*' % attr, attrlist = [attr])
492

493
        if plage != None:
494 495 496
            # On extrait seulement les valeurs des id qui nous intêressent, dans un set
            # car on n'a pas besoin de trier et que et que i in set c'est O(1) contre O(n) pour les list
            nonfree = set(int(r[1].get(attr)[0]) for r in res if r[1].get(attr))
497 498 499 500
            for i in plage:
                if i in nonfree:
                    continue
                else:
501
                    # On crée l'attribut associé, pour parser sa valeur.
502
                    my_id = attributs.attrify(unicode(i), attr, self, None)
503 504 505
                    if my_id.value != i:
                        continue
                    else:
506
                        try:
507
                            self.lockholder.addlock(attr, str(i), lockId)
508
                            break
509
                        except ldap_locks.LockError:
510
                            continue
511
            else:
512 513
                raise EnvironmentError('Aucun %s libre dans la plage %s' %
                                       (attr, realm))
514
        else:
515 516 517 518 519 520 521 522 523 524 525 526 527 528
            # On extrait seulement les valeurs des id qui nous intêressent dans une liste
            nonfree = [ int(r[1].get(attr)[0]) for r in res if r[1].get(attr) ]
            # On trie pour récupérer le dernier
            nonfree.sort()
            try:
                last_id = nonfree[-1]
            except IndexError:
                last_id = 0

            # On écrit le nouveau dernier id connu
            f=os.open('/tmp/lc_ldap_lastid_%s_%s' % (attr, os.getuid()), os.O_WRONLY | os.O_CREAT, 0600)
            os.write(f, str(last_id))
            os.close(f)

529
            i = last_id + 1
530 531
            while True:
                try:
532
                    self.lockholder.addlock(attr, str(i), lockId)
533 534 535
                    break
                except ldap_locks.LockError:
                    i += 1
536
        return i
537

538

539
    def _check_parent(self, objdn):
540
        """
541
        Teste le rapport entre le dn fourni et self
542 543
        Retourne une liste qui s'ajoutera à la liste des droits
        """
544

545
        if objdn.endswith(self.dn) and objdn != self.dn:
546
            return [attributs.parent]
547 548 549 550
        else:
            return []


551
    def _check_self(self, objdn):
552
        """
553
        Teste si le dn fourni est celui de self.
554 555 556
        Retourne une liste qui s'ajoutera à la liste des droits
        """

557
        if objdn == self.dn:
558
            return [attributs.soi]
559 560
        else:
            return []
561

562 563 564 565 566 567 568 569 570 571 572 573 574
    def _check_respo(self, obj):
        """
        Teste si l'objet fourni a pour responsable self.
        Retourne une liste qui s'ajoutera à la liste des droits
        """

        if "cid=" in obj.dn:
            club = obj
            if isinstance(obj, objets.machine) or isinstance(obj, objets.facture):
                club = obj.proprio()
            if isinstance(club, objets.club) and "aid=" + str(club['responsable'][0]) + "," + variables.base_dn == self.dn:
                return [attributs.respo]
            return []
575

576
        return []
577 578 579 580 581 582 583 584

    def get_local_machines(self, mode='ro'):
        filter=""
        for ip in  set(crans_utils.ip4_addresses()):
            filter+=u'(ipHostNumber=%s)' % ip
        filter = u"(|%s)" % filter
        return self.search(filter, mode=mode)