Basic LDAP connection pooling

Implement user search
This commit is contained in:
rcritten@redhat.com 2007-08-13 16:41:38 -04:00
parent 794ef65abc
commit cfaa28150b
6 changed files with 178 additions and 36 deletions

View File

@ -48,19 +48,22 @@ def main():
try:
client = ipaclient.IPAClient()
ent = client.get_user(args[1])
attr = ent.attrList()
users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory'])
for ent in users:
attr = ent.attrList()
print "dn: " + ent.dn
print "dn: " + ent.dn
for a in attr:
value = ent.getValues(a)
if isinstance(value,str):
print a + ": " + value
else:
print a + ": "
for l in value:
print "\t" + l
for a in attr:
value = ent.getValues(a)
if isinstance(value,str):
print a + ": " + value
else:
print a + ": "
for l in value:
print "\t" + l
# blank line between results
print
except xmlrpclib.Fault, fault:
print fault.faultString

View File

@ -85,3 +85,14 @@ class IPAClient:
def get_add_schema(self):
result = self.transport.get_add_schema()
return result
def find_users(self, criteria, sattrs=None):
result = self.transport.find_users(criteria, sattrs)
users = []
for (attrs) in result:
if attrs is not None:
users.append(user.User(attrs))
return users

View File

@ -118,3 +118,20 @@ class RPCClient:
raise xmlrpclib.Fault(value, msg)
return result
def find_users (self, criteria, sattrs=None):
"""Return a list containing a User object for each user that matches
the criteria."""
server = self.setup_server()
try:
if sattrs is not None:
result = server.find_users(criteria, sattrs)
else:
result = server.find_users(criteria)
except xmlrpclib.Fault, fault:
raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
except socket.error, (value, msg):
raise xmlrpclib.Fault(value, msg)
return result

View File

@ -228,15 +228,14 @@ class IPAdmin(SimpleLDAPObject):
def __str__(self):
return self.host + ":" + str(self.port)
def toLDAPURL(self):
return "ldap://%s:%d/" % (self.host,self.port)
def __get_server_controls__(self):
"""Create the proxy user server control. The control has the form
0x04 = Octet String
4|0x80 sets the length of the string length field at 4 bytes
the struct() gets us the length in bytes of string self.proxydn
self.proxydn is the proxy dn to send"""
def getEntry(self,*args):
"""This wraps the search function. It is common to just get one entry"""
# 0x04 = Octet String
# 4|0x80 sets the length of the length at 4 bytes
# the struct() gets us the length in bytes of string s
# s is the proxy dn to send
import sys
if self.proxydn is not None:
proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn;
@ -247,7 +246,24 @@ class IPAdmin(SimpleLDAPObject):
else:
sctrl=None
res = self.search_ext(args[0], args[1], filterstr=args[2], serverctrls=sctrl)
return sctrl
def toLDAPURL(self):
return "ldap://%s:%d/" % (self.host,self.port)
def set_proxydn(self, proxydn):
self.proxydn = proxydn
def getEntry(self,*args):
"""This wraps the search function. It is common to just get one entry"""
sctrl = self.__get_server_controls__()
if sctrl is not None:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
res = self.search(*args)
# res = self.search_ext(args[0], args[1], filterstr=args[2], attrlist=args[3], serverctrls=sctrl)
type, obj = self.result(res)
if not obj:
@ -260,6 +276,11 @@ class IPAdmin(SimpleLDAPObject):
def getList(self,*args):
"""This wraps the search function to find all users."""
sctrl = self.__get_server_controls__()
if sctrl is not None:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
res = self.search(*args)
type, obj = self.result(res)
if not obj:
@ -274,18 +295,8 @@ class IPAdmin(SimpleLDAPObject):
def addEntry(self,*args):
"""This wraps the add function. It assumes that the entry is already
populated with all of the desired objectclasses and attributes"""
if self.proxydn is not None:
proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn;
# Create the proxy control
sctrl=[]
sctrl.append(LDAPControl('2.16.840.1.113730.3.4.18',True,proxydn))
else:
sctrl=None
# Create the proxy control
sctrl=[]
sctrl.append(LDAPControl('2.16.840.1.113730.3.4.18',True,proxydn))
sctrl = self.__get_server_controls__()
try:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)

View File

@ -28,10 +28,37 @@ import string
from types import *
import xmlrpclib
import ipa.config
import os
# Need a global to store this between requests
_LDAPPool = None
#
# Apache runs in multi-process mode so each process will have its own
# connection. This could theoretically drive the total number of connections
# very high but since this represents just the administrative interface
# this is not anticipated.
class IPAConnPool:
def __init__(self):
self.numentries = 0
self.freelist = []
def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None):
self.numentries = self.numentries + 1
if len(self.freelist) > 0:
conn = self.freelist.pop()
else:
conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey)
conn.set_proxydn(proxydn)
return conn
def releaseConn(self, conn):
self.freelist.append(conn)
class IPAServer:
def __init__(self):
global _LDAPPool
# FIXME, this needs to be auto-discovered
self.host = 'localhost'
self.port = 636
@ -39,6 +66,8 @@ class IPAServer:
self.bindkey = "/usr/share/ipa/key.pem"
self.bindca = "/usr/share/ipa/cacert.asc"
if _LDAPPool is None:
_LDAPPool = IPAConnPool()
ipa.config.init_config()
self.basedn = ipaserver.util.realm_to_suffix(ipa.config.config.get_realm())
self.scope = ldap.SCOPE_SUBTREE
@ -49,10 +78,15 @@ class IPAServer:
def get_dn_from_principal(self, princ):
"""Given a kerberls principal get the LDAP uid"""
global _LDAPPool
# FIXME: should we search for this in a specific area of the tree?
filter = "(krbPrincipalName=" + princ + ")"
try:
m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey)
ent = m1.getEntry(self.basedn, self.scope, filter, None)
# The only anonymous search we should have
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None)
ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
@ -95,6 +129,7 @@ class IPAServer:
"""Get a specific user's entry. Return as a dict of values.
Multi-valued fields are represented as lists.
"""
global _LDAPPool
ent=""
if opts:
self.set_principal(opts['remoteuser'])
@ -110,8 +145,9 @@ class IPAServer:
filter = "(uid=" + username + ")"
try:
m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
ent = m1.getEntry(self.basedn, self.scope, filter, None)
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
@ -121,6 +157,7 @@ class IPAServer:
def add_user (self, user, user_container="ou=users,ou=default",opts=None):
"""Add a user in LDAP"""
global _LDAPPool
if (isinstance(user, tuple)):
user = user[0]
dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn)
@ -154,8 +191,9 @@ class IPAServer:
raise xmlrpclib.Fault(2, "No such user")
try:
m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
res = m1.addEntry(entry)
_LDAPPool.releaseConn(m1)
return res
except ldap.ALREADY_EXISTS:
raise xmlrpclib.Fault(3, "User already exists")
@ -210,12 +248,24 @@ class IPAServer:
"""Return a list containing a User object for each
existing user.
"""
global _LDAPPool
if opts:
self.set_principal(opts['remoteuser'])
try:
dn = self.get_dn_from_principal(self.princ)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
raise xmlrpclib.Fault(2, "No such user")
# FIXME: Is this the filter we want or should it be more specific?
filter = "(objectclass=posixAccount)"
try:
m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
all_users = m1.getList(self.basedn, self.scope, filter, None)
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
@ -226,3 +276,52 @@ class IPAServer:
users.append(self.convert_entry(u))
return users
def find_users (self, args, sattrs=None, opts=None):
"""Return a list containing a User object for each
existing user that matches the criteria.
"""
global _LDAPPool
# The XML-RPC server marshals the arguments into one variable
# while the direct caller has them separate. So do a little
# bit of gymnastics to figure things out. There has to be a
# better way, so FIXME
if isinstance(args,tuple):
opts = sattrs
if len(args) == 2:
criteria = args[0]
sattrs = args[1]
else:
criteria = args
sattrs = None
else:
criteria = args
if opts:
self.set_principal(opts['remoteuser'])
try:
dn = self.get_dn_from_principal(self.princ)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
raise xmlrpclib.Fault(2, "No such user")
# FIXME: Is this the filter we want or do we want to do searches of
# cn as well? Or should the caller pass in the filter?
filter = "(uid=%s)" % criteria
try:
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
results = m1.getList(self.basedn, self.scope, filter, sattrs)
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError:
raise xmlrpclib.Fault(2, "No such user")
users = []
for u in results:
users.append(self.convert_entry(u))
return users

View File

@ -278,6 +278,7 @@ def handler(req, profiling=False):
h.register_function(f.add_user)
h.register_function(f.get_add_schema)
h.register_function(f.get_all_users)
h.register_function(f.find_users)
h.handle_request(req)
finally:
pass