[Freeipa-devel] [PATCH] enhanced user search
Kevin McCarthy
kmccarth at redhat.com
Mon Aug 27 18:28:21 UTC 2007
This patch replaces the other two patches in this thread, and is updated
to resolve merge conflicts.
It breaks out the filter generation into a separate method so other
parts can use it too.
-Kevin
-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1188239426 25200
# Node ID a5d0d46b898803ed96e217f21f156efe19db181b
# Parent 7bcee29af7f0ec2279277ff0b5c580e145815485
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
diff -r 7bcee29af7f0 -r a5d0d46b8988 ipa-server/ipa-gui/ipagui/controllers.py
--- a/ipa-server/ipa-gui/ipagui/controllers.py Mon Aug 27 09:49:35 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/controllers.py Mon Aug 27 11:30:26 2007 -0700
@@ -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))
diff -r 7bcee29af7f0 -r a5d0d46b8988 ipa-server/xmlrpc-server/funcs.py
--- a/ipa-server/xmlrpc-server/funcs.py Mon Aug 27 09:49:35 2007 -0700
+++ b/ipa-server/xmlrpc-server/funcs.py Mon Aug 27 11:30:26 2007 -0700
@@ -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
@@ -283,8 +314,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):
@@ -345,8 +378,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:
@@ -365,20 +400,46 @@ class IPAServer:
dn = self.get_dn_from_principal(self.princ)
+ # 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, ",")
+
criteria = self.__safe_filter(criteria)
-
- filter = "(|(uid=%s)(cn=%s))" % (criteria, 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 ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
- results = []
+ 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:
+ 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)
+
+ # 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):
@@ -417,8 +478,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
@@ -485,8 +548,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
@@ -502,12 +567,13 @@ class IPAServer:
criteria = self.__safe_filter(criteria)
filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria
- try:
- m1 = _LDAPPool.getConn(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)
+ try:
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:
@@ -646,5 +712,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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 2228 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20070827/e32df276/attachment.bin>
More information about the Freeipa-devel
mailing list