[Freeipa-devel] [PATCH] enhanced user search

Kevin McCarthy kmccarth at redhat.com
Thu Aug 23 20:17:13 UTC 2007


Enhanced user search:
- "configurable" fields to search on  (this is hardcoded but can be
  moved to the external configuration easily)
- tokenize search words
- prioritize exact matches over partial matches
Other updates:
- use finally block to return ldap connections
- update web gui to use new get_user methods

-Kevin

-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1187900189 25200
# Node ID 4ef808c8f4f0536c1aed310cf604bb9fdd8fb662
# Parent  e311a6e3555583aff5eb10d390015ce0308d334c
Enhanced user search:
- "configurable" fields to search on
- tokenize search words
- prioritize exact matches over partial matches
Other updates:
- use finally block to return ldap connections
- update web gui to use new get_user methods

diff -r e311a6e35555 -r 4ef808c8f4f0 ipa-server/ipa-gui/ipagui/controllers.py
--- a/ipa-server/ipa-gui/ipagui/controllers.py	Thu Aug 23 11:57:25 2007 -0400
+++ b/ipa-server/ipa-gui/ipagui/controllers.py	Thu Aug 23 13:16:29 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 e311a6e35555 -r 4ef808c8f4f0 ipa-server/xmlrpc-server/funcs.py
--- a/ipa-server/xmlrpc-server/funcs.py	Thu Aug 23 11:57:25 2007 -0400
+++ b/ipa-server/xmlrpc-server/funcs.py	Thu Aug 23 13:16:29 2007 -0700
@@ -89,8 +89,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
     
@@ -138,8 +140,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)
  
@@ -223,8 +227,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):
@@ -286,8 +292,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:
@@ -314,23 +322,63 @@ 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)
-
-        # 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)(cn=%s))" % (criteria, criteria)
+        criteria = re.sub(r'[\(\)\\\*]', ldap_search_escape, criteria)
+        if len(criteria) == 0:
+            return []
+
+        # 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, ",")
+
+        # 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}
+
+        criteria_words = re.split(r'\s+', criteria)
+        criteria_words = filter(lambda value:value!="", criteria_words)
+
+        # 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 += ")"
+
+
         basedn = user_container + "," +  self.basedn
-        try:
-            m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
-            results = m1.getList(basedn, self.scope, filter, sattrs)
-            _LDAPPool.releaseConn(m1)
-        except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
-            results = []
+        m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+        try:
+            try:
+                exact_results = m1.getList(basedn, self.scope,
+                        exact_match_filter, sattrs)
+            except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
+                exact_results = []
+
+            try:
+                partial_results = m1.getList(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):
@@ -365,8 +413,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, olduser, newuser)
-        _LDAPPool.releaseConn(m1)
+        try:
+            res = m1.updateEntry(moddn, olduser, newuser)
+        finally:
+            _LDAPPool.releaseConn(m1)
         return res
 
     def mark_user_deleted (self, uid, opts=None):
@@ -390,8 +440,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
 
 
@@ -408,5 +460,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/20070823/c8ad1fbc/attachment.bin>


More information about the Freeipa-devel mailing list