summaryrefslogtreecommitdiffstats
path: root/ipa-server/xmlrpc-server
diff options
context:
space:
mode:
authorrcritten@redhat.com <rcritten@redhat.com>2007-08-24 15:42:56 -0400
committerrcritten@redhat.com <rcritten@redhat.com>2007-08-24 15:42:56 -0400
commit861cda3cb5256a177845029ddf1900f51271b56c (patch)
tree7cf9e2004705e129511d8c25ac69232af8901317 /ipa-server/xmlrpc-server
parent240a99b6f35fe7a7de37c83e69471b84298d0f56 (diff)
downloadfreeipa-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.py348
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py9
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