diff --git a/ipa-admintools/Makefile b/ipa-admintools/Makefile index e0fd405ab..69bf18537 100644 --- a/ipa-admintools/Makefile +++ b/ipa-admintools/Makefile @@ -5,6 +5,8 @@ all: ; install: install -m 755 ipa-adduser $(SBINDIR) install -m 755 ipa-finduser $(SBINDIR) + install -m 755 ipa-usermod $(SBINDIR) + install -m 755 ipa-deluser $(SBINDIR) clean: rm -f *~ *.pyc diff --git a/ipa-admintools/ipa-adduser b/ipa-admintools/ipa-adduser index af922833d..64c94e1d0 100644 --- a/ipa-admintools/ipa-adduser +++ b/ipa-admintools/ipa-adduser @@ -83,6 +83,9 @@ def main(): except kerberos.GSSError, e: print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) return 1 + except xmlrpclib.ProtocolError, e: + print "Unable to connect to IPA server: %s" % (e.errmsg) + return 1 return 0 diff --git a/ipa-admintools/ipa-deluser b/ipa-admintools/ipa-deluser new file mode 100644 index 000000000..c6d5a8d77 --- /dev/null +++ b/ipa-admintools/ipa-deluser @@ -0,0 +1,66 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +from optparse import OptionParser +import ipa +import ipa.ipaclient as ipaclient +import ipa.config + +import xmlrpclib +import kerberos + +def usage(): + print "ipa-adduser user" + sys.exit(1) + +def parse_options(): + parser = OptionParser() + parser.add_option("--usage", action="store_true", + help="Program usage") + + args = ipa.config.init_config(sys.argv) + options, args = parser.parse_args(args) + + return options, args + +def main(): + options, args = parse_options() + + if len(args) != 2: + usage() + + try: + client = ipaclient.IPAClient() + client.mark_user_deleted(args[1]) + print args[1] + " successfully deleted" + except xmlrpclib.Fault, f: + print f.faultString + return 1 + except kerberos.GSSError, e: + print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) + return 1 + except xmlrpclib.ProtocolError, e: + print "Unable to connect to IPA server: %s" % (e.errmsg) + return 1 + + return 0 + +main() diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser index b16c19d99..4b1651c0b 100644 --- a/ipa-admintools/ipa-finduser +++ b/ipa-admintools/ipa-finduser @@ -71,6 +71,9 @@ def main(): except kerberos.GSSError, e: print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) return 1 + except xmlrpclib.ProtocolError, e: + print "Unable to connect to IPA server: %s" % (e.errmsg) + return 1 return 0 diff --git a/ipa-admintools/ipa-usermod b/ipa-admintools/ipa-usermod index d2aff4abf..33d35d21a 100644 --- a/ipa-admintools/ipa-usermod +++ b/ipa-admintools/ipa-usermod @@ -76,6 +76,9 @@ def main(): except kerberos.GSSError, e: print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) return 1 + except xmlrpclib.ProtocolError, e: + print "Unable to connect to IPA server: %s" % (e.errmsg) + return 1 return 0 diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 626e0a111..783c7ec85 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -41,14 +41,19 @@ class IPAClient: self.transport = rpcclient.RPCClient() def set_principal(self,princ): + """Set the name of the principal that will be used for + LDAP proxy authentication""" if self.local: self.transport.set_principal(princ) - def get_user(self,uid): - result = self.transport.get_user(uid) + def get_user(self,uid,sattrs=None): + """Get a specific user by uid. If sattrs is set then only those + attributes will be returned.""" + result = self.transport.get_user(uid,sattrs) return user.User(result) def add_user(self,user): + """Add a user. user is a dict of attribute/value pairs""" realm = config.config.get_realm() @@ -73,32 +78,48 @@ class IPAClient: return result def get_all_users(self): + """Get as a list of User objects all users in the directory""" result = self.transport.get_all_users() all_users = [] for (attrs) in result: - if attrs is not None: - all_users.append(user.User(attrs)) + if attrs is not None: + all_users.append(user.User(attrs)) return all_users def get_add_schema(self): + """Prototype for the GUI. Specify in the directory fields to + be displayed and what data to get for new users.""" result = self.transport.get_add_schema() return result def find_users(self, criteria, sattrs=None): + """Find users whose uid matches the criteria. Wildcards are + acceptable. Returns a list of User objects.""" result = self.transport.find_users(criteria, sattrs) users = [] for (attrs) in result: - if attrs is not None: - users.append(user.User(attrs)) + if attrs is not None: + users.append(user.User(attrs)) return users def update_user(self,olduser,newuser): + """Update a user entry. olduser is a dict of attribute/value pairs + of the original entry. newuser is a dict of attribute/value pairs + of the new entry.""" realm = config.config.get_realm() result = self.transport.update_user(olduser,newuser) return result + + def mark_user_deleted(self,uid): + """Set a user as inactive by uid.""" + + realm = config.config.get_realm() + + result = self.transport.mark_user_deleted(uid) + return result diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index abff0f8fa..477b4e3cf 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -39,9 +39,12 @@ class RPCClient: ipa.config.init_config() def server_url(self): + """Build the XML-RPC server URL from our configuration""" return "http://" + config.config.get_server() + "/ipa" def setup_server(self): + """Create our XML-RPC server connection using kerberos + authentication""" return xmlrpclib.ServerProxy(self.server_url(), KerbTransport()) def convert_entry(self,ent): @@ -63,11 +66,15 @@ class RPCClient: return user - def get_user(self,username): - """Get a specific user""" + def get_user(self,username,sattrs=None): + """Get a specific user. If sattrs is not None then only those + attributes will be returned. The result is a dict.""" server = self.setup_server() try: - result = server.get_user(username) + if sattrs is not None: + result = server.get_user(username,sattrs) + else: + result = server.get_user(username) except xmlrpclib.Fault, fault: raise xmlrpclib.Fault(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -76,7 +83,9 @@ class RPCClient: return result def add_user(self,user): - """Add a new user""" + """Add a new user. 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""" server = self.setup_server() try: @@ -147,3 +156,16 @@ class RPCClient: raise xmlrpclib.Fault(value, msg) return result + + def mark_user_deleted(self,uid): + """Mark a user as deleted/inactive""" + server = self.setup_server() + + try: + result = server.mark_user_deleted(uid) + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return result diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index 08113b4c8..936dd662b 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -321,8 +321,28 @@ class IPAdmin(SimpleLDAPObject): try: self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modify_s(dn, modlist) - except ldap.ALREADY_EXISTS: - raise ldap.ALREADY_EXISTS + except ldap.LDAPError, e: + raise e + return "Success" + + def inactivateEntry(self,dn,has_key): + """Rather than deleting entries we mark them as inactive. + has_key defines whether the entry already has nsAccountlock + set so we can determine which type of mod operation to run.""" + + sctrl = self.__get_server_controls__() + modlist=[] + + if has_key == True: + operation = ldap.MOD_REPLACE + else: + operation = ldap.MOD_ADD + + modlist.append((operation, "nsAccountlock", "true")) + + try: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.modify_s(dn, modlist) except ldap.LDAPError, e: raise e return "Success" diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 358f04f2a..ec099badc 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -125,12 +125,28 @@ class IPAServer: return user - def get_user (self, username, opts=None): + def get_user (self, args, sattrs=None, opts=None): """Get a specific user's entry. Return as a dict of values. Multi-valued fields are represented as lists. """ global _LDAPPool ent="" + + # The XML-RPC server marshals the arguments into one variable + # while the direct caller has them separate. So do a little + # bit of gymnastics to figure things out. There has to be a + # better way, so FIXME + if isinstance(args,tuple): + opts = sattrs + if len(args) == 2: + username = args[0] + sattrs = args[1] + else: + username = args + sattrs = None + else: + username = args + if opts: self.set_principal(opts['remoteuser']) if (isinstance(username, tuple)): @@ -146,7 +162,7 @@ class IPAServer: filter = "(uid=" + username + ")" try: m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) - ent = m1.getEntry(self.basedn, self.scope, filter, None) + ent = m1.getEntry(self.basedn, self.scope, filter, sattrs) _LDAPPool.releaseConn(m1) except ldap.LDAPError, e: raise xmlrpclib.Fault(1, e) @@ -156,7 +172,10 @@ class IPAServer: return self.convert_entry(ent) def add_user (self, user, user_container="ou=users,ou=default",opts=None): - """Add a user in LDAP""" + """Add a user 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. user_container sets + where in the tree the user is placed.""" global _LDAPPool if (isinstance(user, tuple)): user = user[0] @@ -372,3 +391,39 @@ class IPAServer: return res except ldap.LDAPError, e: raise xmlrpclib.Fault(1, str(e)) + + def mark_user_deleted (self, args, opts=None): + """Mark a user as inactive in LDAP. We aren't actually deleting + users here, just making it so they can't log in, etc.""" + global _LDAPPool + + uid = args[0] + + if opts: + self.set_principal(opts['remoteuser']) + + try: + proxydn = self.get_dn_from_principal(self.princ) + except ldap.LDAPError, e: + raise xmlrpclib.Fault(1, e) + except ipaserver.ipaldap.NoSuchEntryError: + raise xmlrpclib.Fault(2, "No such user") + + try: + user = self.get_user(uid, ['dn', 'nsAccountlock'], opts) + except ldap.LDAPError, e: + raise xmlrpclib.Fault(1, str(e)) + + # Are we doing an add or replace operation? + if user.has_key('nsaccountlock'): + has_key = True + else: + has_key = False + + try: + m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn) + res = m1.inactivateEntry(user['dn'], has_key) + _LDAPPool.releaseConn(m1) + return res + except ldap.LDAPError, e: + raise xmlrpclib.Fault(1, str(e)) diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 7d8529d28..03340ca5f 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -285,6 +285,7 @@ def handler(req, profiling=False): h.register_function(f.get_all_users) h.register_function(f.find_users) h.register_function(f.update_user) + h.register_function(f.mark_user_deleted) h.handle_request(req) finally: pass