diff options
author | rcritten@redhat.com <rcritten@redhat.com> | 2007-08-24 15:42:56 -0400 |
---|---|---|
committer | rcritten@redhat.com <rcritten@redhat.com> | 2007-08-24 15:42:56 -0400 |
commit | 861cda3cb5256a177845029ddf1900f51271b56c (patch) | |
tree | 7cf9e2004705e129511d8c25ac69232af8901317 /ipa-server/xmlrpc-server | |
parent | 240a99b6f35fe7a7de37c83e69471b84298d0f56 (diff) | |
download | freeipa-861cda3cb5256a177845029ddf1900f51271b56c.tar.gz freeipa-861cda3cb5256a177845029ddf1900f51271b56c.tar.xz freeipa-861cda3cb5256a177845029ddf1900f51271b56c.zip |
Initial support for Groups
Create separate object for Users and Groups (using same base class)
Check for uniqueness before adding new users and groups
Remove user_container from everything but add operations
Abstract out a number of functions that are common across users and groups
Make sure all strings passed in to be in a filter are checked
Add new error message: No modifications specified
Diffstat (limited to 'ipa-server/xmlrpc-server')
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 348 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 9 |
2 files changed, 302 insertions, 55 deletions
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index aa5633453..f388f3f4f 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -26,6 +26,7 @@ import ipaserver.ipaldap import ipaserver.util import xmlrpclib import ipa.config +import copy from ipa import ipaerror import string @@ -36,7 +37,8 @@ import re # Need a global to store this between requests _LDAPPool = None -DefaultContainer = "ou=users,ou=default" +DefaultUserContainer = "ou=users,ou=default" +DefaultGroupContainer = "ou=groups,ou=default" # # Apache runs in multi-process mode so each process will have its own @@ -85,7 +87,6 @@ class IPAServer: """Given a kerberls principal get the LDAP uid""" global _LDAPPool - # FIXME: should we search for this in a specific area of the tree? filter = "(krbPrincipalName=" + princ + ")" # The only anonymous search we should have m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None) @@ -93,7 +94,7 @@ class IPAServer: _LDAPPool.releaseConn(m1) return "dn:" + ent.dn - + def convert_entry(self, ent): # Convert to LDIF @@ -109,24 +110,24 @@ class IPAServer: # Convert into a dict. We need to handle multi-valued attributes as well # so we'll convert those into lists. - user={} + obj={} for (k,v) in specs: k = k.lower() - if user.get(k) is not None: - if isinstance(user[k],list): - user[k].append(v.strip()) + if obj.get(k) is not None: + if isinstance(obj[k],list): + obj[k].append(v.strip()) else: - first = user[k] - user[k] = [] - user[k].append(first) - user[k].append(v.strip()) + first = obj[k] + obj[k] = [] + obj[k].append(first) + obj[k].append(v.strip()) else: - user[k] = v.strip() + obj[k] = v.strip() - return user + return obj - def __get_user (self, base, filter, sattrs=None, opts=None): - """Get a specific user's entry. Return as a dict of values. + 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. """ global _LDAPPool @@ -142,14 +143,70 @@ class IPAServer: _LDAPPool.releaseConn(m1) return self.convert_entry(ent) + + def __update_entry (self, oldentry, newentry, opts=None): + """Update an LDAP entry + + oldentry is a dict + newentry is a dict + """ + global _LDAPPool + + oldentry = self.convert_scalar_values(oldentry) + newentry = self.convert_scalar_values(newentry) + + # Should be able to get this from either the old or new entry + # but just in case someone has decided to try changing it, use the + # original + try: + moddn = oldentry['dn'] + except KeyError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN) + + if opts: + self.set_principal(opts['remoteuser']) + + 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) + return res + + def __safe_filter(self, criteria): + """Make sure any arguments used when creating a filter are safe.""" + + # TODO: this escaper assumes the python-ldap library will error out + # on invalid codepoints. we need to check malformed utf-8 input + # 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) + + return criteria +# User support + + def __is_user_unique(self, uid, opts): + """Return 1 if the uid is unique in the tree, 0 otherwise.""" + 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: + return 0 + else: + return 1 + def get_user_by_uid (self, uid, sattrs=None, opts=None): """Get a specific user's entry. Return as a dict of values. Multi-valued fields are represented as lists. """ + uid = self.__safe_filter(uid) filter = "(uid=" + uid + ")" - return self.__get_user(self.basedn, filter, sattrs, opts) + return self.__get_entry(self.basedn, filter, sattrs, opts) def get_user_by_dn (self, dn, sattrs=None, opts=None): """Get a specific user's entry. Return as a dict of values. @@ -157,7 +214,7 @@ class IPAServer: """ filter = "(objectClass=*)" - return self.__get_user(dn, filter, sattrs, opts) + return self.__get_entry(dn, filter, sattrs, opts) def add_user (self, user, user_container=None, opts=None): """Add a user in LDAP. Takes as input a dict where the key is the @@ -167,7 +224,10 @@ class IPAServer: global _LDAPPool if user_container is None: - user_container = DefaultContainer + user_container = DefaultUserContainer + + if self.__is_user_unique(user['uid'], opts) == 0: + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn) entry = ipaserver.ipaldap.Entry(dn) @@ -282,7 +342,6 @@ class IPAServer: dn = self.get_dn_from_principal(self.princ) - # FIXME: Is this the filter we want or should it be more specific? filter = "(objectclass=posixAccount)" m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) @@ -295,34 +354,23 @@ class IPAServer: return users - def find_users (self, criteria, sattrs=None, user_container=None, opts=None): + def find_users (self, criteria, sattrs=None, opts=None): """Return a list containing a User object for each existing user that matches the criteria. """ global _LDAPPool - if user_container is None: - user_container = DefaultContainer - if opts: self.set_principal(opts['remoteuser']) dn = self.get_dn_from_principal(self.princ) - # TODO: this escaper assumes the python-ldap library will error out - # on invalid codepoints. we need to check malformed utf-8 input - # 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 = self.__safe_filter(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) - 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) + results = m1.getList(self.basedn, self.scope, filter, sattrs) _LDAPPool.releaseConn(m1) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): results = [] @@ -346,28 +394,7 @@ class IPAServer: def update_user (self, olduser, newuser, opts=None): """Update a user in LDAP""" - global _LDAPPool - - olduser = self.convert_scalar_values(olduser) - newuser = self.convert_scalar_values(newuser) - - # Should be able to get this from either the old or new user - # but just in case someone has decided to try changing it, use the - # original - try: - moddn = olduser['dn'] - except KeyError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN) - - if opts: - self.set_principal(opts['remoteuser']) - - 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) - return res + return self.__update_entry(olduser, newuser, opts) def mark_user_deleted (self, uid, opts=None): """Mark a user as inactive in LDAP. We aren't actually deleting @@ -394,6 +421,217 @@ class IPAServer: _LDAPPool.releaseConn(m1) return res +# Group support + + def __is_group_unique(self, cn, opts): + """Return 1 if the cn is unique in the tree, 0 otherwise.""" + cn = self.__safe_filter(cn) + filter = "(&(cn=%s)(objectclass=posixGroup))" % cn + + entry = self.__get_entry(self.basedn, filter, ['dn','cn'], opts) + + if entry is not None: + return 0 + else: + return 1 + + def get_group_by_cn (self, cn, sattrs=None, opts=None): + """Get a specific group's entry. Return as a dict of values. + Multi-valued fields are represented as lists. + """ + + cn = self.__safe_filter(cn) + filter = "(cn=" + cn + ")" + return self.__get_entry(self.basedn, filter, sattrs, opts) + + def get_group_by_dn (self, dn, sattrs=None, opts=None): + """Get a specific group's entry. Return as a dict of values. + Multi-valued fields are represented as lists. + """ + + filter = "(objectClass=*)" + return self.__get_entry(dn, filter, sattrs, opts) + + def add_group (self, group, group_container=None, opts=None): + """Add a group in LDAP. Takes as input a dict where the key is the + attribute name and the value is either a string or in the case + of a multi-valued field a list of values. group_container sets + where in the tree the group is placed.""" + global _LDAPPool + + if group_container is None: + group_container = DefaultGroupContainer + + if self.__is_group_unique(group['cn'], opts) == 0: + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + + dn="cn=%s,%s,%s" % (group['cn'], group_container,self.basedn) + entry = ipaserver.ipaldap.Entry(dn) + + # some required objectclasses + entry.setValues('objectClass', 'top', 'groupofuniquenames', 'posixGroup') + + # FIXME, need a gidNumber generator + if group.get('gidnumber') is None: + entry.setValues('gidNumber', '501') + + # fill in our new entry with everything sent by the user + for g in group: + entry.setValues(g, group[g]) + + if opts: + self.set_principal(opts['remoteuser']) + + 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) + + def find_groups (self, criteria, sattrs=None, opts=None): + """Return a list containing a User object for each + existing group that matches the criteria. + """ + global _LDAPPool + + if opts: + self.set_principal(opts['remoteuser']) + + dn = self.get_dn_from_principal(self.princ) + + 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) + results = m1.getList(self.basedn, self.scope, filter, sattrs) + _LDAPPool.releaseConn(m1) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + results = [] + + groups = [] + for u in results: + groups.append(self.convert_entry(u)) + + return groups + + def add_user_to_group(self, user, group, opts=None): + """Add a user to an existing group. + user is a uid of the user to add + group is the cn of the group to be added to + """ + + if opts: + self.set_principal(opts['remoteuser']) + + old_group = self.get_group_by_cn(group, None, opts) + if old_group is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + new_group = copy.deepcopy(old_group) + + user_dn = self.get_user_by_uid(user, ['dn', 'uid', 'objectclass'], opts) + if user_dn is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + if new_group.get('uniquemember') is not None: + if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))): + new_group['uniquemember'] = [new_group['uniquemember']] + new_group['uniquemember'].append(user_dn['dn']) + else: + new_group['uniquemember'] = user_dn['dn'] + + try: + ret = self.__update_entry(old_group, new_group, opts) + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST): + raise + return ret + + def add_users_to_group(self, users, group, opts=None): + """Given a list of user uid's add them to the group cn denoted by group + Returns a list of the users were not added to the group. + """ + + failed = [] + + if (isinstance(users, str)): + users = [users] + + for user in users: + try: + self.add_user_to_group(user, group, opts) + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST): + # User is already in the group + failed.append(user) + except ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND): + # User or the group does not exist + failed.append(user) + + return failed + + def remove_user_from_group(self, user, group, opts=None): + """Remove a user from an existing group. + user is a uid of the user to remove + group is the cn of the group to be removed from + """ + + if opts: + self.set_principal(opts['remoteuser']) + + old_group = self.get_group_by_cn(group, None, opts) + if old_group is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + new_group = copy.deepcopy(old_group) + + user_dn = self.get_user_by_uid(user, ['dn', 'uid', 'objectclass'], opts) + if user_dn is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + if new_group.get('uniquemember') is not None: + if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))): + new_group['uniquemember'] = [new_group['uniquemember']] + try: + new_group['uniquemember'].remove(user_dn['dn']) + except ValueError: + # User is not in the group + # FIXME: raise more specific error? + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + else: + # Nothing to do if the group has no members + # FIXME raise SOMETHING? + return "Success" + + try: + ret = self.__update_entry(old_group, new_group, opts) + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST): + raise + return ret + + def remove_users_from_group(self, users, group, opts=None): + """Given a list of user uid's remove them from the group cn denoted + by group + Returns a list of the users were not removed from the group. + """ + + failed = [] + + if (isinstance(users, str)): + users = [users] + + for user in users: + try: + self.remove_user_from_group(user, group, opts) + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST): + # User is not in the group + failed.append(user) + except ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND): + # User or the group does not exist + failed.append(user) + + return failed + + def update_group (self, oldgroup, newgroup, opts=None): + """Update a group in LDAP""" + return self.__update_entry(oldgroup, newgroup, opts) def ldap_search_escape(match): """Escapes out nasty characters from the ldap search. diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 7998dd7e0..3f30f8fbd 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -295,6 +295,15 @@ def handler(req, profiling=False): h.register_function(f.find_users) h.register_function(f.update_user) h.register_function(f.mark_user_deleted) + h.register_function(f.get_group_by_cn) + h.register_function(f.get_group_by_dn) + h.register_function(f.add_group) + h.register_function(f.find_groups) + h.register_function(f.add_user_to_group) + h.register_function(f.add_users_to_group) + h.register_function(f.remove_user_from_group) + h.register_function(f.remove_users_from_group) + h.register_function(f.update_group) h.handle_request(req) finally: pass |