Introduction"Ldaptor is a set of pure-Python LDAP client programs, applications and a programming library." (
Ldaptor on Tv's blog)
Here we will see how to build a simple pythonic ldap server using twisted and ldaptor.
The ldiftree
>>> from ldaptor.ldiftree import LDIFTreeEntry
>>> tree = LDIFTreeEntry('/tmp/mytemporaryldaptordir')
>>> print tree
dn:
>>>
You can then add childs to your root :
>>> com = tree.addChild('dc=com', {'objectClass':['dcObject'],'dc':['com']})
>>> print com
dn: dc=com
objectClass: dcObject
dc: com
>>>
Note : the addChild returns the added node so you can then add childs to it ...
First step : we need seeds to make our tree growThe goal of this article isn't to provide a curse about ldap (you'll find hundreds of them googling).
Here is the basic tree structure we will use :
COUNTRY = ('dc=fr',
{'objectclass':['dcObject','country'],
'dc':['fr'],
'description':["French country 2 letters iso description"]})
COMPANY = ('dc=example',
{'objectclass':['dcObject','organization'],
'dc':['example'],
'description':["My organisation"],
'o':"Example, Inc"})
PEOPLE = ('ou=people',
{'ou':'people',
'description':'People from Example Inc',
'objectclass':'organizationalunit'})
USERS = [
('uid=yoen', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Yoen Van der Weld'],
'sn':['Van der Weld'],
'givenName':['Yoen'],
'uid':['yoen'],
'mailDir':['/home/yoen/mailDir'],
'userpassword': ['secret']}),
('uid=esteban', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Esteban Garcia Marquez'],
'sn':['Garcia Marquez'],
'givenName':['Esteban'],
'uid':['esteban'],
'mailDir':['/home/esteban/mailDir'],
'userpassword':['secret2']}),
('uid=mohamed', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Mohamed Al Ghâlib'],
'sn':['Al Ghâlib'],
'givenName':['mohamed'],
'uid':['mohamed'],
'mailDir':['/home/mohamed/mailDir'],
'userpassword':['secret3']}),
]
Country->Company->People populated with three accounts.
Each node is described with two datas its rdn (relative distinguished name) and its attributes.
We will not provide a schema entry for simplicity.
Second step : let the tree growLet's build our structure based on the scheme we defined here above :
import tempfile
from ldaptor.ldiftree import LDIFTreeEntry
class Tree:
def __init__(self, path='/tmp'):
dirname = tempfile.mkdtemp('.ldap', 'test-server', '/tmp')
self.db = LDIFTreeEntry(dirname)
self.init_db()
def init_db(self):
"""
Add subtrees to the top entry
top->country->company->people
"""
country = self.db.addChild(COUNTRY[0], COUNTRY[1])
company = country.addChild(COMPANY[0], COMPANY[1])
people = company.addChild(PEOPLE[0], PEOPLE[1])
for user in USERS:
people.addChild(user[0], user[1])
tree = Tree()
Note that we use the tempfile standard lib to avoid erasing existing datas.
A in-memory tree could also be used (see test_server.py)
Third step : Creating the factory used to serve To understand what protocol and factories are in twisted read this :
Writing Servers.
from twisted.internet.protocol import ServerFactory
from ldaptor.protocols.ldap.ldapserver import LDAPServer
class LDAPServerFactory(ServerFactory):
protocol = LDAPServer
def __init__(self, root):
self.root = root
Our factory simply store persistently the root (the tree in fact).
When the ldap protocol handle the ldap tree, it retrieves it from the factory adapting the factory to the IConnectedLDAPEntry interface. So we need to register an adapter for our factory to match the IConnectedLDAPEntry.
See
Components: Interfaces and Adapters for further informations.
from twisted.python.components import registerAdapter
from ldaptor.interfaces import IConnectedLDAPEntry
registerAdapter(lambda x: x.root,
LDAPServerFactory,
IConnectedLDAPEntry)
We will say now that all is prepared to run our ldap server.
Fourth step : running our server
from twisted.application import service, internet
from twisted.internet import protocol, reactor
factory = LDAPServerFactory(tree.db)
application = service.Application("ldaptor-server")
myService = service.IServiceCollection(application)
reactor.listenTCP(8080, factory)
reactor.run()
Final step : let's tryTo test that all, use a ldap client.
For example install the ldap-utils package and in another prompt, launch:
ldapsearch -x -h 127.0.0.1 -p 8080 'uid=esteban'
Our two filesschema.py :
#-*-coding:utf-8-*-
COUNTRY = ('dc=fr',
{'objectclass':['dcObject','country'],
'dc':['fr'],
'description':["French country 2 letters iso description"]})
COMPANY = ('dc=example',
{'objectclass':['dcObject','organization'],
'dc':['example'],
'description':["My organisation"],
'o':"Example, Inc"})
PEOPLE = ('ou=people',
{'ou':'people',
'description':'People from Example Inc',
'objectclass':'organizationalunit'})
USERS = [
('uid=yoen', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Yoen Van der Weld'],
'sn':['Van der Weld'],
'givenName':['Yoen'],
'uid':['yoen'],
'mail':['/home/yoen/mailDir'],
'userpassword': ['secret']}),
('uid=esteban', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Esteban Garcia Marquez'],
'sn':['Garcia Marquez'],
'givenName':['Esteban'],
'uid':['esteban'],
'mail':['/home/esteban/mailDir'],
'userpassword':['secret2']}),
('uid=mohamed', {'objectClass': ['people', 'inetOrgPerson'],
'cn':['Mohamed Al Ghâlib'],
'sn':['Al Ghâlib'],
'givenName':['mohamed'],
'uid':['mohamed'],
'mail':['/home/mohamed/mailDir'],
'userpassword':['secret3']}),
]
server.py :
#-*-coding:utf-8-*-
"""
Testing a simple ldaptor ldap server
"""
import tempfile, sys
from twisted.application import service, internet
from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory
from twisted.python.components import registerAdapter
from twisted.python import log
from ldaptor.interfaces import IConnectedLDAPEntry
from ldaptor.protocols.ldap.ldapserver import LDAPServer
from ldaptor.ldiftree import LDIFTreeEntry
from schema import COUNTRY, COMPANY, PEOPLE, USERS
class Tree:
def __init__(self, path='/tmp'):
dirname = tempfile.mkdtemp('.ldap', 'test-server', '/tmp')
self.db = LDIFTreeEntry(dirname)
self.init_db()
def init_db(self):
"""
Add subtrees to the top entry
top->country->company->people
"""
country = self.db.addChild(COUNTRY[0], COUNTRY[1])
company = country.addChild(COMPANY[0], COMPANY[1])
people = company.addChild(PEOPLE[0], PEOPLE[1])
for user in USERS:
people.addChild(user[0], user[1])
class LDAPServerFactory(ServerFactory):
"""
Our Factory is meant to persistently store the ldap tree
"""
protocol = LDAPServer
def __init__(self, root):
self.root = root
if __name__ == '__main__':
# First of all, to show logging info in stdout :
log.startLogging(sys.stderr)
# We initialize our tree
tree = Tree()
# When the ldap protocol handle the ldap tree,
# it retrieves it from the factory adapting
# the factory to the IConnectedLDAPEntry interface
# So we need to register an adapter for our factory
# to match the IConnectedLDAPEntry
registerAdapter(lambda x: x.root,
LDAPServerFactory,
IConnectedLDAPEntry)
# Run it !!
factory = LDAPServerFactory(tree.db)
application = service.Application("ldaptor-server")
myService = service.IServiceCollection(application)
reactor.listenTCP(8080, factory)
reactor.run()
In a first prompt launch the server :
python server.py