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,
|
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