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

View File

@ -85,3 +85,14 @@ class IPAClient:
def get_add_schema(self): def get_add_schema(self):
result = self.transport.get_add_schema() result = self.transport.get_add_schema()
return result 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) raise xmlrpclib.Fault(value, msg)
return result 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): def __str__(self):
return self.host + ":" + str(self.port) return self.host + ":" + str(self.port)
def toLDAPURL(self): def __get_server_controls__(self):
return "ldap://%s:%d/" % (self.host,self.port) """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): import sys
"""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
if self.proxydn is not None: if self.proxydn is not None:
proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn; proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn;
@ -247,7 +246,24 @@ class IPAdmin(SimpleLDAPObject):
else: else:
sctrl=None 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) type, obj = self.result(res)
if not obj: if not obj:
@ -260,6 +276,11 @@ class IPAdmin(SimpleLDAPObject):
def getList(self,*args): def getList(self,*args):
"""This wraps the search function to find all users.""" """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) res = self.search(*args)
type, obj = self.result(res) type, obj = self.result(res)
if not obj: if not obj:
@ -274,18 +295,8 @@ class IPAdmin(SimpleLDAPObject):
def addEntry(self,*args): def addEntry(self,*args):
"""This wraps the add function. It assumes that the entry is already """This wraps the add function. It assumes that the entry is already
populated with all of the desired objectclasses and attributes""" 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 = self.__get_server_controls__()
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))
try: try:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)

View File

@ -28,10 +28,37 @@ import string
from types import * from types import *
import xmlrpclib import xmlrpclib
import ipa.config 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: class IPAServer:
def __init__(self): def __init__(self):
global _LDAPPool
# FIXME, this needs to be auto-discovered # FIXME, this needs to be auto-discovered
self.host = 'localhost' self.host = 'localhost'
self.port = 636 self.port = 636
@ -39,6 +66,8 @@ class IPAServer:
self.bindkey = "/usr/share/ipa/key.pem" self.bindkey = "/usr/share/ipa/key.pem"
self.bindca = "/usr/share/ipa/cacert.asc" self.bindca = "/usr/share/ipa/cacert.asc"
if _LDAPPool is None:
_LDAPPool = IPAConnPool()
ipa.config.init_config() ipa.config.init_config()
self.basedn = ipaserver.util.realm_to_suffix(ipa.config.config.get_realm()) self.basedn = ipaserver.util.realm_to_suffix(ipa.config.config.get_realm())
self.scope = ldap.SCOPE_SUBTREE self.scope = ldap.SCOPE_SUBTREE
@ -49,10 +78,15 @@ class IPAServer:
def get_dn_from_principal(self, princ): def get_dn_from_principal(self, princ):
"""Given a kerberls principal get the LDAP uid""" """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 + ")" filter = "(krbPrincipalName=" + princ + ")"
try: try:
m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey) # The only anonymous search we should have
ent = m1.getEntry(self.basedn, self.scope, filter, None) 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: except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e) raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError: except ipaserver.ipaldap.NoSuchEntryError:
@ -95,6 +129,7 @@ class IPAServer:
"""Get a specific user's entry. Return as a dict of values. """Get a specific user's entry. Return as a dict of values.
Multi-valued fields are represented as lists. Multi-valued fields are represented as lists.
""" """
global _LDAPPool
ent="" ent=""
if opts: if opts:
self.set_principal(opts['remoteuser']) self.set_principal(opts['remoteuser'])
@ -110,8 +145,9 @@ class IPAServer:
filter = "(uid=" + username + ")" filter = "(uid=" + username + ")"
try: 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) ent = m1.getEntry(self.basedn, self.scope, filter, None)
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e: except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e) raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError: except ipaserver.ipaldap.NoSuchEntryError:
@ -121,6 +157,7 @@ class IPAServer:
def add_user (self, user, user_container="ou=users,ou=default",opts=None): def add_user (self, user, user_container="ou=users,ou=default",opts=None):
"""Add a user in LDAP""" """Add a user in LDAP"""
global _LDAPPool
if (isinstance(user, tuple)): if (isinstance(user, tuple)):
user = user[0] user = user[0]
dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn) dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn)
@ -154,8 +191,9 @@ class IPAServer:
raise xmlrpclib.Fault(2, "No such user") raise xmlrpclib.Fault(2, "No such user")
try: 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) res = m1.addEntry(entry)
_LDAPPool.releaseConn(m1)
return res return res
except ldap.ALREADY_EXISTS: except ldap.ALREADY_EXISTS:
raise xmlrpclib.Fault(3, "User already exists") raise xmlrpclib.Fault(3, "User already exists")
@ -210,12 +248,24 @@ class IPAServer:
"""Return a list containing a User object for each """Return a list containing a User object for each
existing user. 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? # FIXME: Is this the filter we want or should it be more specific?
filter = "(objectclass=posixAccount)" filter = "(objectclass=posixAccount)"
try: 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) all_users = m1.getList(self.basedn, self.scope, filter, None)
_LDAPPool.releaseConn(m1)
except ldap.LDAPError, e: except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, e) raise xmlrpclib.Fault(1, e)
except ipaserver.ipaldap.NoSuchEntryError: except ipaserver.ipaldap.NoSuchEntryError:
@ -226,3 +276,52 @@ class IPAServer:
users.append(self.convert_entry(u)) users.append(self.convert_entry(u))
return users 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.add_user)
h.register_function(f.get_add_schema) h.register_function(f.get_add_schema)
h.register_function(f.get_all_users) h.register_function(f.get_all_users)
h.register_function(f.find_users)
h.handle_request(req) h.handle_request(req)
finally: finally:
pass pass