Added dynldap python library to ldap::client::python.
This commit is contained in:
parent
6d75b559b1
commit
31434a1b0b
2 changed files with 373 additions and 0 deletions
363
ldap/files/dynldap.py
Normal file
363
ldap/files/dynldap.py
Normal file
|
@ -0,0 +1,363 @@
|
|||
|
||||
|
||||
from copy import copy
|
||||
import ldap
|
||||
import ldap.modlist
|
||||
import ldap.sasl
|
||||
import os
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
from types import ListType
|
||||
|
||||
|
||||
OPENLDAPCONF = (os.path.join(os.getenv('HOME', ''), '.ldaprc'),
|
||||
'/usr/local/etc/openldap/ldap.conf',
|
||||
'/etc/openldap/ldap.conf')
|
||||
|
||||
SAMBASECRETS = '/var/lib/samba/private/secrets.tdb'
|
||||
|
||||
|
||||
def auth_callback(conn, username=None, password=None):
|
||||
from os import getlogin
|
||||
from getpass import getpass
|
||||
if username is None:
|
||||
try:
|
||||
username = conn.searchone('(&(objectClass=posixAccount)(uid=%s))' \
|
||||
% getlogin(), attrs=[]).dn
|
||||
except IndexError:
|
||||
if getlogin() == 'root':
|
||||
username = 'cn=manager,%s' % conn.basedn
|
||||
else:
|
||||
username = raw_input('Enter bind DN: ')
|
||||
if password is None:
|
||||
password = getpass('Enter LDAP password: ')
|
||||
return (username, password)
|
||||
|
||||
|
||||
def auth_samba_callback(conn, username=None, password=None):
|
||||
backend = get_samba_conf_param('passdb backend')
|
||||
m = re.match('^ldapsam:"(.*)"$', backend)
|
||||
if m is None:
|
||||
raise ValueError('Samba not configured for LDAP backend')
|
||||
binddn = get_samba_conf_param('ldap admin dn')
|
||||
authpw = get_samba_secrets_param('SECRETS/LDAP_BIND_PW/%s' % binddn)
|
||||
return (binddn, authpw)
|
||||
|
||||
|
||||
def get_samba_conf_param(key, section = 'global'):
|
||||
cmd = Popen(['testparm', '-s', '-v', '--section-name=%s' % section,
|
||||
'-d', '0', '--parameter-name=%s' % key], stdout=PIPE,
|
||||
stderr=PIPE)
|
||||
cmd.wait()
|
||||
for line in cmd.stdout.readlines():
|
||||
m = re.match('^"(.*)"$', line)
|
||||
if m is not None:
|
||||
return m.group(1)
|
||||
return line.strip()
|
||||
for line in cmd.stderr.readlines():
|
||||
if re.search('Parameter .* unknown for section [^ ]', line):
|
||||
raise KeyError(line.strip())
|
||||
|
||||
|
||||
def get_samba_secrets_param(key):
|
||||
cmd = Popen(['tdbdump', SAMBASECRETS], stdout=PIPE)
|
||||
cmd.wait()
|
||||
for line in cmd.stdout.readlines():
|
||||
line = line.strip()
|
||||
if line == '{':
|
||||
k = None
|
||||
v = None
|
||||
continue
|
||||
elif line == '}':
|
||||
if k == key:
|
||||
return v
|
||||
continue
|
||||
line = re.match('^(key|data)\(\d+\) = "(.*)"$', line)
|
||||
if line.group(1) == 'key':
|
||||
k = line.group(2)
|
||||
elif line.group(1) == 'data':
|
||||
v = line.group(2)
|
||||
if v[len(v)-3:] == '\\00':
|
||||
v = v[0:len(v)-3]
|
||||
raise KeyError()
|
||||
|
||||
|
||||
def connect(uris = None, basedn = None):
|
||||
if uris is None:
|
||||
(uris, basedn) = parse_openldap_config()
|
||||
return Connection(uris, basedn)
|
||||
|
||||
|
||||
def parse_openldap_config():
|
||||
for name in OPENLDAPCONF:
|
||||
ret = [None, None]
|
||||
try:
|
||||
f = open(name, 'r')
|
||||
except IOError:
|
||||
continue
|
||||
for line in f.readlines():
|
||||
m = re.match('^\s*(URI|BASE)\s+(.*)', line)
|
||||
if m is None:
|
||||
continue
|
||||
if m.group(1) == 'URI':
|
||||
ret[0] = m.group(2).split()
|
||||
elif m.group(1) == 'BASE':
|
||||
ret[1] = m.group(2)
|
||||
f.close()
|
||||
if ret[0] is not None:
|
||||
return tuple(ret)
|
||||
raise ldap.SERVER_DOWN('Could not determine LDAP server address')
|
||||
|
||||
|
||||
class LdapEntry:
|
||||
|
||||
def __init__(self, conn, entry, new=False):
|
||||
for (attr, values) in entry[1].items():
|
||||
self._set(attr, values)
|
||||
if new:
|
||||
self._orig = {}
|
||||
else:
|
||||
self._orig = copy(entry[1])
|
||||
self._dn = entry[0]
|
||||
self._conn = conn
|
||||
|
||||
def _del(self, key):
|
||||
if key in self._attrs():
|
||||
del(self.__dict__[key])
|
||||
|
||||
__delitem__ = _del
|
||||
|
||||
def _get(self, key):
|
||||
if key in self._attrs():
|
||||
keys = {}
|
||||
for v in self.__dict__[key]:
|
||||
keys[v] = True
|
||||
value = keys.keys()
|
||||
value.sort()
|
||||
return value
|
||||
return None
|
||||
|
||||
__getitem__ = _get
|
||||
|
||||
def __iter__(self):
|
||||
return self._attrs().__iter__()
|
||||
|
||||
def __str__(self):
|
||||
ret = 'dn: %s\n' % self.dn
|
||||
for (key, values) in self.items():
|
||||
for value in values:
|
||||
ret += '%s: %s\n' % (key, value)
|
||||
return ret
|
||||
|
||||
def _set(self, key, value):
|
||||
if key[0] == '_':
|
||||
self.__dict__[key] = value
|
||||
elif value is None:
|
||||
del(self.__dict__[key])
|
||||
else:
|
||||
if not isinstance(value, ListType):
|
||||
value = [ value ]
|
||||
self.__dict__[key] = map(lambda x: str(x), value)
|
||||
|
||||
__setattr__ = _set
|
||||
__setitem__ = __setattr__
|
||||
|
||||
@property
|
||||
def dn(self):
|
||||
return self._dn
|
||||
|
||||
@property
|
||||
def modified(self):
|
||||
if len(self.changes()) > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _attrs(self):
|
||||
return map(lambda (k, v): k, self.items())
|
||||
attrs = _attrs
|
||||
|
||||
|
||||
def changes(self):
|
||||
new = {}
|
||||
for k in self._attrs():
|
||||
new[k] = self._get(k)
|
||||
return ldap.modlist.modifyModlist(self._orig, new)
|
||||
|
||||
def items(self):
|
||||
return filter(lambda (k, v): k[0] != '_', self.__dict__.items())
|
||||
|
||||
def revert(self):
|
||||
for k in self._attrs():
|
||||
del(self.__dict__[k])
|
||||
for (k, v) in self._orig.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
def save(self):
|
||||
changes = self.changes()
|
||||
if len(changes) > 0:
|
||||
if self._orig == {}:
|
||||
self._conn._conn.add_s(self.dn, self.items())
|
||||
else:
|
||||
self._conn._conn.modify_s(self.dn, changes)
|
||||
self._orig = copy(self.__dict__)
|
||||
|
||||
|
||||
class Connection:
|
||||
|
||||
def __init__(self, uri, basedn = None):
|
||||
self.auth = (None, None)
|
||||
self.reconnect(uri)
|
||||
if basedn is None:
|
||||
entry = self.get('', attrs=['namingContexts'])
|
||||
basedn = entry.namingContexts[0]
|
||||
self.basedn = basedn
|
||||
self.set_option(ldap.OPT_REFERRALS, False)
|
||||
|
||||
def __del__(self):
|
||||
self.unbind()
|
||||
|
||||
def reconnect(self, uris = None):
|
||||
if uris is None:
|
||||
uris = self.uri
|
||||
elif not hasattr(uris, '__iter__'):
|
||||
uris = [ uris ]
|
||||
for uri in uris:
|
||||
try:
|
||||
self._conn = ldap.initialize(uri)
|
||||
self.whoami()
|
||||
if self.auth[0] is not None and self.auth[1] is not None:
|
||||
self.bind(self.auth[0], self.auth[1])
|
||||
self.uri = uri
|
||||
return
|
||||
except ldap.SERVER_DOWN:
|
||||
continue
|
||||
raise ldap.SERVER_DOWN({'desc': "Can't contact LDAP server"})
|
||||
|
||||
|
||||
def bind(self, username=None, password=None, callback=auth_callback):
|
||||
if username is None or password is None:
|
||||
(username, password) = callback(self, username, password)
|
||||
if password is '':
|
||||
raise ldap.INVALID_CREDENTIALS(
|
||||
{'desc': 'Empty passwords not allowed'})
|
||||
self._conn.bind_s(username, password)
|
||||
self.auth = (username, password)
|
||||
|
||||
def delete(self, dn):
|
||||
return self._conn.delete_s(dn)
|
||||
|
||||
def get(self, dn, attrs = None):
|
||||
return self.search(scope=ldap.SCOPE_BASE, basedn=dn, attrs=attrs)[0]
|
||||
|
||||
def get_option(self, option):
|
||||
return self._conn.get_option(option)
|
||||
|
||||
def new(self, dn, entry = {}, **kwargs):
|
||||
if not 'entryclass' in kwargs:
|
||||
kwargs['entryclass'] = LdapEntry
|
||||
return kwargs['entryclass'](self, [dn, entry], True)
|
||||
|
||||
def passwd(self, newpw, **kwargs):
|
||||
if 'user' not in kwargs:
|
||||
kwargs['user'] = self.whoami()
|
||||
if 'oldpw' not in kwargs:
|
||||
kwargs['oldpw'] = None
|
||||
self._conn.passwd_s(kwargs['user'], kwargs['oldpw'], newpw)
|
||||
|
||||
def ping(self, reconnect=True):
|
||||
try:
|
||||
self.whoami()
|
||||
return True
|
||||
except ldap.SERVER_DOWN:
|
||||
return False
|
||||
|
||||
def search(self, filterstr = '(objectClass=*)', **kwargs):
|
||||
if not 'basedn' in kwargs:
|
||||
kwargs['basedn'] = self.basedn
|
||||
if not 'scope' in kwargs:
|
||||
kwargs['scope'] = ldap.SCOPE_SUBTREE
|
||||
if not 'attrs' in kwargs:
|
||||
kwargs['attrs'] = None
|
||||
if not 'timeout' in kwargs:
|
||||
kwargs['timeout'] = self._conn.timeout
|
||||
if not 'entryclass' in kwargs:
|
||||
kwargs['entryclass'] = LdapEntry
|
||||
msgid = self._conn.search_ext(kwargs['basedn'], kwargs['scope'],
|
||||
filterstr, kwargs['attrs'], 0, None,
|
||||
None, kwargs['timeout'], 1000000)
|
||||
return LdapResult(self, msgid, kwargs['entryclass'])
|
||||
|
||||
def searchone(self, filterstr, **kwargs):
|
||||
result = self.search(filterstr, **kwargs)
|
||||
if len(result) == 0:
|
||||
raise ldap.NO_SUCH_OBJECT({'desc': 'No such object'})
|
||||
elif len(result) == 1:
|
||||
return result[0]
|
||||
else:
|
||||
raise ldap.NO_SUCH_OBJECT({'desc': 'Multiple objects matched'})
|
||||
|
||||
def set_option(self, option, value):
|
||||
self._conn.set_option(option, value)
|
||||
|
||||
def unbind(self):
|
||||
self._conn.unbind_s()
|
||||
self.auth = (None, None)
|
||||
|
||||
def whoami(self):
|
||||
try:
|
||||
return self._conn.whoami_s().split(':')[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
class LdapResult:
|
||||
|
||||
def __init__(self, conn, msgid, entryclass = LdapEntry):
|
||||
self._conn = conn
|
||||
self._msgid = msgid
|
||||
self._entries = []
|
||||
self._entryclass = entryclass
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self._conn._conn.abandon(self._msgid)
|
||||
except ldap.LDAPError:
|
||||
pass
|
||||
|
||||
def __getitem__(self, idx):
|
||||
while len(self._entries)-1 < idx:
|
||||
try:
|
||||
self.next()
|
||||
except StopIteration:
|
||||
raise IndexError('list index out of range')
|
||||
return self._entries[idx]
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
try:
|
||||
self.next()
|
||||
except StopIteration:
|
||||
pass
|
||||
return len(self._entries)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.keys())
|
||||
|
||||
def keys(self):
|
||||
r = []
|
||||
for entry in self:
|
||||
r.append(entry.dn)
|
||||
return r
|
||||
|
||||
def next(self):
|
||||
try:
|
||||
(rtype, entry) = self._conn._conn.result(self._msgid, all=0,
|
||||
timeout=-1)
|
||||
if rtype == ldap.RES_SEARCH_RESULT:
|
||||
raise StopIteration
|
||||
except IndexError:
|
||||
raise StopIteration
|
||||
self._entries.append(self._entryclass(self._conn, entry[0]))
|
||||
return self._entries[len(self._entries)-1]
|
|
@ -282,6 +282,16 @@ class ldap::client::python {
|
|||
ensure => installed,
|
||||
}
|
||||
|
||||
file { "${::pythonsitedir}/dynldap.py":
|
||||
ensure => present,
|
||||
source => "puppet:///modules/ldap/dynldap.py",
|
||||
mode => "0644",
|
||||
owner => "root",
|
||||
group => "root",
|
||||
require => Package["python-ldap"],
|
||||
}
|
||||
python::compile { "${::pythonsitedir}/dynldap.py": }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue