From 2fbe5cbf492597a87427b61f1e470052b77465b2 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Tue, 4 Dec 2007 13:18:37 -0500 Subject: Phase 1 of allowing admins to set the default object classes for users & groups This adds the UI and does error checking of the selected object classes but it doesn't actually use the values yet. It also generalizes some functions for doing multi-valued fields. --- ipa-server/xmlrpc-server/funcs.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 12131c26..9e9ad27a 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -329,6 +329,32 @@ class IPAServer: return (exact_match_filter, partial_match_filter) + def __get_schema(self, opts=None): + """Retrieves the current LDAP schema from the LDAP server.""" + + schema_entry = self.__get_base_entry("", "objectclass=*", ['dn','subschemasubentry'], opts) + schema_cn = schema_entry.get('subschemasubentry') + schema = self.__get_base_entry(schema_cn, "objectclass=*", ['*'], opts) + + return schema + + def __get_objectclasses(self, opts=None): + """Returns a list of available objectclasses that the LDAP + server supports. This parses out the syntax, attributes, etc + and JUST returns a lower-case list of the names.""" + + schema = self.__get_schema(opts) + + objectclasses = schema.get('objectclasses') + + # Convert this list into something more readable + result = [] + for i in range(len(objectclasses)): + oc = objectclasses[i].lower().split(" ") + result.append(oc[3].replace("'","")) + + return result + # Higher-level API def get_aci_entry(self, sattrs, opts=None): @@ -1397,6 +1423,19 @@ class IPAServer: except: raise + # Run through the list of User and Group object classes to make + # sure they are all valid. This doesn't handle dependencies but it + # will at least catch typos. + classes = self.__get_objectclasses(opts) + oc = newconfig['ipauserobjectclasses'] + for i in range(len(oc)): + if not oc[i].lower() in classes: + raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC) + oc = newconfig['ipagroupobjectclasses'] + for i in range(len(oc)): + if not oc[i].lower() in classes: + raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC) + return self.update_entry(oldconfig, newconfig, opts) def get_password_policy(self, opts=None): -- cgit From 15b7dc6ff9c202dee00f1403139c206b5969c0f3 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 5 Dec 2007 15:17:11 -0500 Subject: Add UI for service principal creation and keytab retrieval --- ipa-server/xmlrpc-server/funcs.py | 73 ++++++++++++++++++++++++++++++++++- ipa-server/xmlrpc-server/ipaxmlrpc.py | 1 + 2 files changed, 73 insertions(+), 1 deletion(-) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 9e9ad27a..d046b518 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -1358,7 +1358,78 @@ class IPAServer: finally: self.releaseConnection(conn) return res - + + def find_service_principal(self, criteria, sattrs, searchlimit=-1, + timelimit=-1, opts=None): + """Returns a list: counter followed by the results. + If the results are truncated, counter will be set to -1.""" + + config = self.get_ipa_config(opts) + if timelimit < 0: + timelimit = float(config.get('ipasearchtimelimit')) + if searchlimit < 0: + searchlimit = float(config.get('ipasearchrecordslimit')) + + search_fields = ["krbprincipalname"] + + 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 [0] + + (exact_match_filter, partial_match_filter) = self.__generate_match_filters( + search_fields, criteria_words) + + # + # further constrain search to just the objectClass + # TODO - need to parameterize this into generate_match_filters, + # and work it into the field-specification search feature + # + exact_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % exact_match_filter + partial_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % partial_match_filter + print exact_match_filter + print partial_match_filter + + conn = self.getConnection(opts) + try: + try: + exact_results = conn.getListAsync(self.basedn, self.scope, + exact_match_filter, sattrs, 0, None, None, timelimit, + searchlimit) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + exact_results = [0] + + try: + partial_results = conn.getListAsync(self.basedn, self.scope, + partial_match_filter, sattrs, 0, None, None, timelimit, + searchlimit) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + partial_results = [0] + finally: + self.releaseConnection(conn) + + exact_counter = exact_results[0] + partial_counter = partial_results[0] + + exact_results = exact_results[1:] + partial_results = partial_results[1:] + + # 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) + + if (exact_counter == -1) or (partial_counter == -1): + counter = -1 + else: + counter = len(exact_results) + len(partial_results) + + entries = [counter] + for e in exact_results + partial_results: + entries.append(self.convert_entry(e)) + + return entries def get_keytab(self, name, opts=None): """get a keytab""" diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index c6f0ec2c..31cfbae6 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -360,6 +360,7 @@ def handler(req, profiling=False): h.register_function(f.get_password_policy) h.register_function(f.update_password_policy) h.register_function(f.add_service_principal) + h.register_function(f.find_service_principal) h.register_function(f.get_keytab) h.handle_request(req) finally: -- cgit From eb141b02ff3e21196fece1a6edf19f469efdc220 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 5 Dec 2007 17:26:39 -0500 Subject: Move dn removal to the XML-RPC side and remove empty attributes --- ipa-server/xmlrpc-server/funcs.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index d046b518..7be75ddc 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -449,6 +449,19 @@ class IPAServer: if self.__is_user_unique(user['uid'], opts) == 0: raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + # dn is set here, not by the user + try: + del user['dn'] + except KeyError: + pass + + # No need to set empty fields, and they can cause issues when they + # get to LDAP, like: + # TypeError: ('expected a string in the list', None) + for k in user.keys(): + if not user[k] or len(user[k]) == 0 or (len(user[k]) == 1 and '' in user[k]): + del user[k] + dn="uid=%s,%s,%s" % (ldap.dn.escape_dn_chars(user['uid']), user_container,self.basedn) entry = ipaserver.ipaldap.Entry(dn) @@ -502,8 +515,16 @@ class IPAServer: conn = self.getConnection(opts) try: - res = conn.addEntry(entry) - self.add_user_to_group(user.get('uid'), group_dn, opts) + try: + res = conn.addEntry(entry) + except TypeError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "There is a problem with one of the data types.") + except Exception, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, e) + try: + self.add_user_to_group(user.get('uid'), group_dn, opts) + except Exception, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "The user was created but adding to group %s failed" % group_dn) finally: self.releaseConnection(conn) return res -- cgit From 6ea3d9610e62322b843b22b6acf531dce384305c Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 6 Dec 2007 00:30:26 -0500 Subject: Utilize user and group objectclass lists in cn=ipaconfig Change the syntax on user and group objectclasses in cn=ipaconfig --- ipa-server/xmlrpc-server/funcs.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 7be75ddc..d247878e 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -506,8 +506,7 @@ class IPAServer: del user['gn'] # some required objectclasses - entry.setValues('objectClass', 'top', 'person', 'organizationalPerson', - 'inetOrgPerson', 'inetUser', 'posixAccount', 'krbPrincipalAux', 'radiusprofile') + entry.setValues('objectClass', (config.get('ipauserobjectclasses'))) # fill in our new entry with everything sent by the user for u in user: @@ -719,6 +718,12 @@ class IPAServer: finally: self.releaseConnection(conn) + # Get our configuration + config = self.get_ipa_config(opts) + + # Make sure we have the latest object classes + newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipauserobjectclasses')) + try: rv = self.update_entry(oldentry, newentry, opts) return rv @@ -878,13 +883,15 @@ class IPAServer: if self.__is_group_unique(group['cn'], opts) == 0: raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + # Get our configuration + config = self.get_ipa_config(opts) + dn="cn=%s,%s,%s" % (ldap.dn.escape_dn_chars(group['cn']), group_container,self.basedn) entry = ipaserver.ipaldap.Entry(dn) # some required objectclasses - entry.setValues('objectClass', 'top', 'groupofnames', 'posixGroup', - 'inetUser') + entry.setValues('objectClass', (config.get('ipagroupobjectclasses'))) # No need to explicitly set gidNumber. The dna_plugin will do this # for us if the value isn't provided by the user. @@ -1226,6 +1233,12 @@ class IPAServer: finally: self.releaseConnection(conn) + # Get our configuration + config = self.get_ipa_config(opts) + + # Make sure we have the latest object classes + newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipauserobjectclasses')) + try: rv = self.update_entry(oldentry, newentry, opts) return rv @@ -1590,3 +1603,8 @@ def ldap_search_escape(match): return r'\00' else: return value + +def uniq_list(x): + """Return a unique list, preserving order and ignoring case""" + set = {} + return [set.setdefault(e,e) for e in x if e.lower() not in set] -- cgit From 2a2d8665522a239aad56954cadfc618f220841d3 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 6 Dec 2007 15:36:42 -0500 Subject: Make uid an editable field in the Edit UI so we can do RDN changes Fix group RDN changes Remove a copy/paste error in the group UI update that caused 2 updates Fix variable name so groups don't get user objectclasses Remove color CSS for field backgrounds as they override disabled field display --- ipa-server/xmlrpc-server/funcs.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index d247878e..485b6e25 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -1216,19 +1216,22 @@ class IPAServer: try: res = conn.updateRDN(oldentry.get('dn'), "cn=" + newcn[0]) newdn = oldentry.get('dn') + newcn = newentry.get('cn') + if isinstance(newcn, str): + newcn = [newcn] # Ick. Need to find the exact cn used in the old DN so we'll # walk the list of cns and skip the obviously bad ones: for c in oldentry.get('dn').split("cn="): if c and c != "groups" and not c.startswith("accounts"): - newdn = newdn.replace("cn=%s" % c, "uid=%s" % newentry.get('cn')[0]) + newdn = newdn.replace("cn=%s" % c, "cn=%s," % newcn[0]) break # Now fix up the dns and cns so they aren't seen as having # changed. oldentry['dn'] = newdn newentry['dn'] = newdn - oldentry['cn'] = newentry['cn'] + oldentry['cn'] = newentry.get('cn') newrdn = 1 finally: self.releaseConnection(conn) @@ -1237,7 +1240,7 @@ class IPAServer: config = self.get_ipa_config(opts) # Make sure we have the latest object classes - newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipauserobjectclasses')) + newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipagroupobjectclasses')) try: rv = self.update_entry(oldentry, newentry, opts) -- cgit From c95550a16491c409459b3b4e1d0bf4f76954c568 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 10 Dec 2007 11:54:42 -0500 Subject: Remove some debugging statements --- ipa-server/xmlrpc-server/funcs.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'ipa-server/xmlrpc-server') diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 485b6e25..8a49488b 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -1425,8 +1425,6 @@ class IPAServer: # exact_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % exact_match_filter partial_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % partial_match_filter - print exact_match_filter - print partial_match_filter conn = self.getConnection(opts) try: -- cgit