Enhanced user search:

- "configurable" fields to search on
- tokenize search words
- prioritize exact matches over partial matches
- split match filter generation into a re-usable function.
Other updates:
- use finally block to return ldap connections
- update web gui to use new get_user methods
This commit is contained in:
Kevin McCarthy 2007-08-27 11:30:26 -07:00
parent f437ecfad1
commit e31b526c81
2 changed files with 96 additions and 27 deletions

View File

@ -92,7 +92,7 @@ class Root(controllers.RootController):
if tg_errors:
turbogears.flash("There was a problem with the form!")
user = client.get_user(uid)
user = client.get_user_by_uid(uid)
user_dict = user.toDict()
# store a copy of the original user for the update later
user_data = b64encode(dumps(user_dict))
@ -155,7 +155,7 @@ class Root(controllers.RootController):
def usershow(self, uid):
"""Retrieve a single user for display"""
try:
user = client.get_user(uid)
user = client.get_user_by_uid(uid)
return dict(user=user.toDict(), fields=forms.user.UserFields())
except ipaerror.IPAError, e:
turbogears.flash("User show failed: " + str(e))

View File

@ -90,8 +90,10 @@ class IPAServer:
filter = "(krbPrincipalName=" + princ + ")"
# 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)
try:
ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
finally:
_LDAPPool.releaseConn(m1)
return "dn:" + ent.dn
@ -139,8 +141,10 @@ class IPAServer:
dn = self.get_dn_from_principal(self.princ)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
ent = m1.getEntry(base, self.scope, filter, sattrs)
_LDAPPool.releaseConn(m1)
try:
ent = m1.getEntry(base, self.scope, filter, sattrs)
finally:
_LDAPPool.releaseConn(m1)
return self.convert_entry(ent)
@ -169,8 +173,10 @@ class IPAServer:
proxydn = self.get_dn_from_principal(self.princ)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
res = m1.updateEntry(moddn, oldentry, newentry)
_LDAPPool.releaseConn(m1)
try:
res = m1.updateEntry(moddn, oldentry, newentry)
finally:
_LDAPPool.releaseConn(m1)
return res
def __safe_filter(self, criteria):
@ -181,9 +187,34 @@ class IPAServer:
# where the second byte in a multi-byte character
# is (illegally) ')' and make sure python-ldap
# bombs out.
criteria = re.sub(r'[\(\)\\]', ldap_search_escape, criteria)
criteria = re.sub(r'[\(\)\\\*]', ldap_search_escape, criteria)
return criteria
def __generate_match_filters(self, search_fields, criteria_words):
"""Generates a search filter based on a list of words and a list
of fields to search against.
Returns a tuple of two filters: (exact_match, partial_match)"""
# construct search pattern for a single word
# (|(f1=word)(f2=word)...)
search_pattern = "(|"
for field in search_fields:
search_pattern += "(" + field + "=%(match)s)"
search_pattern += ")"
gen_search_pattern = lambda word: search_pattern % {'match':word}
# construct the giant match for all words
exact_match_filter = "(&"
partial_match_filter = "(&"
for word in criteria_words:
exact_match_filter += gen_search_pattern(word)
partial_match_filter += gen_search_pattern("*%s*" % word)
exact_match_filter += ")"
partial_match_filter += ")"
return (exact_match_filter, partial_match_filter)
# User support
@ -282,8 +313,10 @@ class IPAServer:
dn = self.get_dn_from_principal(self.princ)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
res = m1.addEntry(entry)
_LDAPPool.releaseConn(m1)
try:
res = m1.addEntry(entry)
finally:
_LDAPPool.releaseConn(m1)
return res
def get_add_schema (self):
@ -344,8 +377,10 @@ class IPAServer:
filter = "(objectclass=posixAccount)"
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)
try:
all_users = m1.getList(self.basedn, self.scope, filter, None)
finally:
_LDAPPool.releaseConn(m1)
users = []
for u in all_users:
@ -364,20 +399,46 @@ class IPAServer:
dn = self.get_dn_from_principal(self.princ)
criteria = self.__safe_filter(criteria)
# Assume the list of fields to search will come from a central
# configuration repository. A good format for that would be
# a comma-separated list of fields
search_fields_conf_str = "uid,givenName,sn,telephoneNumber"
search_fields = string.split(search_fields_conf_str, ",")
filter = "(|(uid=%s)(cn=%s))" % (criteria, criteria)
criteria = self.__safe_filter(criteria)
criteria_words = re.split(r'\s+', criteria)
criteria_words = filter(lambda value:value!="", criteria_words)
if len(criteria_words) == 0:
return []
(exact_match_filter, partial_match_filter) = self.__generate_match_filters(
search_fields, criteria_words)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
try:
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
results = m1.getList(self.basedn, self.scope, filter, sattrs)
try:
exact_results = m1.getList(self.basedn, self.scope,
exact_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
exact_results = []
try:
partial_results = m1.getList(self.basedn, self.scope,
partial_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
partial_results = []
finally:
_LDAPPool.releaseConn(m1)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
results = []
# Remove exact matches from the partial_match list
exact_dns = set(map(lambda e: e.dn, exact_results))
partial_results = filter(lambda e: e.dn not in exact_dns,
partial_results)
users = []
for u in results:
for u in exact_results + partial_results:
users.append(self.convert_entry(u))
return users
def convert_scalar_values(self, orig_dict):
@ -416,8 +477,10 @@ class IPAServer:
has_key = False
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
res = m1.inactivateEntry(user['dn'], has_key)
_LDAPPool.releaseConn(m1)
try:
res = m1.inactivateEntry(user['dn'], has_key)
finally:
_LDAPPool.releaseConn(m1)
return res
# Group support
@ -484,8 +547,10 @@ class IPAServer:
dn = self.get_dn_from_principal(self.princ)
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
res = m1.addEntry(entry)
_LDAPPool.releaseConn(m1)
try:
res = m1.addEntry(entry)
finally:
_LDAPPool.releaseConn(m1)
def find_groups (self, criteria, sattrs=None, opts=None):
"""Return a list containing a User object for each
@ -501,12 +566,13 @@ class IPAServer:
criteria = self.__safe_filter(criteria)
filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
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 ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
results = []
finally:
_LDAPPool.releaseConn(m1)
groups = []
for u in results:
@ -645,5 +711,8 @@ def ldap_search_escape(match):
return "\\29"
elif value == "\\":
return "\\5c"
elif value == "*":
# drop '*' from input. search performs its own wildcarding
return ""
else:
return value