From a5b7e3f56b8a3bddfafa1811061cd146f7c818e1 Mon Sep 17 00:00:00 2001 From: "rcritten@redhat.com" Date: Mon, 27 Aug 2007 13:45:28 -0400 Subject: Include any LDAP error strings in XML-RPC Fault exceptions Put a try/except around attempts to determine user uniqueness --- ipa-server/xmlrpc-server/funcs.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'ipa-server/xmlrpc-server/funcs.py') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index f388f3f4..8994be91 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -192,11 +192,10 @@ class IPAServer: uid = self.__safe_filter(uid) filter = "(&(uid=%s)(objectclass=posixAccount))" % uid - entry = self.__get_entry(self.basedn, filter, ['dn','uid'], opts) - - if entry is not None: + try: + entry = self.__get_entry(self.basedn, filter, ['dn','uid'], opts) return 0 - else: + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): return 1 def get_user_by_uid (self, uid, sattrs=None, opts=None): -- cgit From e31b526c8174e7c55f69b1fdf31a6ee78197e8bc Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Mon, 27 Aug 2007 11:30:26 -0700 Subject: 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 --- ipa-server/xmlrpc-server/funcs.py | 117 ++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 24 deletions(-) (limited to 'ipa-server/xmlrpc-server/funcs.py') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 8994be91..a0049833 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -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) + # 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) + criteria_words = re.split(r'\s+', criteria) + criteria_words = filter(lambda value:value!="", criteria_words) + if len(criteria_words) == 0: + return [] - filter = "(|(uid=%s)(cn=%s))" % (criteria, criteria) + (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 -- cgit From 6eea6664e079d187c3b0420b4283af35205d3b03 Mon Sep 17 00:00:00 2001 From: Karl MacMillan Date: Tue, 28 Aug 2007 09:58:10 -0400 Subject: This patch wraps binary data in an xmlrpclib Binary object. This removes the need for LDIF conversion. It will make TurboGears direct code faster, but should keep xmlrpc about the same speed. The patch also swaps out ldap.cidict for the IPA CIDict class. IPA code should only use the CIDict class now. --- ipa-server/xmlrpc-server/funcs.py | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) (limited to 'ipa-server/xmlrpc-server/funcs.py') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index a0049833..fe48a1ff 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -98,36 +98,19 @@ class IPAServer: return "dn:" + ent.dn def convert_entry(self, ent): - - # Convert to LDIF - entry = str(ent) + entry = dict(ent.data) + entry['dn'] = ent.dn + # For now convert single entry lists to a string for the ui. + # TODO: we need to deal with multi-values better + for key,value in entry.iteritems(): + if isinstance(value,list) or isinstance(value,tuple): + if len(value) == 0: + entry[key] = '' + elif len(value) == 1: + entry[key] = value[0] + return entry - # Strip off any junk - entry = entry.strip() - # Don't need to identify binary fields and this breaks the parser so - # remove double colons - entry = entry.replace('::', ':') - specs = [spec.split(':') for spec in entry.split('\n')] - - # Convert into a dict. We need to handle multi-valued attributes as well - # so we'll convert those into lists. - obj={} - for (k,v) in specs: - k = k.lower() - if obj.get(k) is not None: - if isinstance(obj[k],list): - obj[k].append(v.strip()) - else: - first = obj[k] - obj[k] = [] - obj[k].append(first) - obj[k].append(v.strip()) - else: - obj[k] = v.strip() - - return obj - def __get_entry (self, base, filter, sattrs=None, opts=None): """Get a specific entry. Return as a dict of values. Multi-valued fields are represented as lists. -- cgit