Ensure that the Apache server is in forked mode

Add ability to update existing users
Try to prevent fetching and setting empty strings
This commit is contained in:
rcritten@redhat.com
2007-08-14 17:22:05 -04:00
parent 547f501fae
commit 5f0f23ee91
7 changed files with 188 additions and 2 deletions

View File

@@ -0,0 +1,82 @@
#! /usr/bin/python -E
# Authors: Rob Crittenden <rcritten@redhat.com>
#
# 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-usermod [-c|--gecos STRING] [-d|--directory STRING] user"
sys.exit(1)
def parse_options():
parser = OptionParser()
parser.add_option("-c", "--gecos", dest="gecos",
help="Set the GECOS field")
parser.add_option("-d", "--directory", dest="directory",
help="Set the User's home directory")
parser.add_option("-s", "--shell", dest="shell",
help="Set user's login shell to shell")
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():
olduser={}
newuser={}
options, args = parse_options()
if len(args) != 2:
usage()
client = ipaclient.IPAClient()
u = client.get_user(args[1])
olduser = u.toDict()
newuser = u.toDict()
if options.gecos:
newuser['gecos'] = options.gecos
if options.directory:
newuser['homedirectory'] = options.directory
if options.shell:
newuser['loginshell'] = options.shell
try:
client.update_user(olduser, newuser)
print args[1] + " successfully modified"
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
return 0
main()

View File

@@ -96,3 +96,9 @@ class IPAClient:
return users return users
def update_user(self,olduser,newuser):
realm = config.config.get_realm()
result = self.transport.update_user(olduser,newuser)
return result

View File

@@ -75,7 +75,6 @@ class RPCClient:
return result return result
def add_user(self,user): def add_user(self,user):
"""Add a new user""" """Add a new user"""
server = self.setup_server() server = self.setup_server()
@@ -135,3 +134,16 @@ class RPCClient:
raise xmlrpclib.Fault(value, msg) raise xmlrpclib.Fault(value, msg)
return result return result
def update_user(self,olduser,newuser):
"""Update an existing user. olduser and newuser are dicts of attributes"""
server = self.setup_server()
try:
result = server.update_user(olduser, newuser)
except xmlrpclib.Fault, fault:
raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
except socket.error, (value, msg):
raise xmlrpclib.Fault(value, msg)
return result

View File

@@ -58,6 +58,8 @@ class User:
def getValue(self,name): def getValue(self,name):
"""Get the first value for the attribute named name""" """Get the first value for the attribute named name"""
value = self.data.get(name,[None]) value = self.data.get(name,[None])
if (len(value) < 1):
return value
if isinstance(value[0],list) or isinstance(value[0],tuple): if isinstance(value[0],list) or isinstance(value[0],tuple):
return value[0] return value[0]
else: else:
@@ -72,6 +74,8 @@ class User:
ent.setValue('name', ('value1', 'value2', ..., 'valueN')) ent.setValue('name', ('value1', 'value2', ..., 'valueN'))
Since *value is a tuple, we may have to extract a list or tuple from that Since *value is a tuple, we may have to extract a list or tuple from that
tuple as in the last two examples above""" tuple as in the last two examples above"""
if (len(value[0]) < 1):
return
if isinstance(value[0],list) or isinstance(value[0],tuple): if isinstance(value[0],list) or isinstance(value[0],tuple):
self.data[name] = value[0] self.data[name] = value[0]
else: else:
@@ -85,6 +89,15 @@ class User:
single value or a list of values.""" single value or a list of values."""
return self.data.items() return self.data.items()
def toDict(self):
"""Convert the attrs and values to a dict. The dict is keyed on the
attribute name. The value is either single value or a list of values."""
result = {}
for k in self.data.keys():
result[k] = self.data[k]
result['dn'] = self.dn
return result
def attrList(self): def attrList(self):
"""Return a list of all attributes in the entry""" """Return a list of all attributes in the entry"""
return self.data.keys() return self.data.keys()

View File

@@ -35,6 +35,7 @@ import time
import operator import operator
import struct import struct
from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
from ldap.modlist import modifyModlist
from ldap.ldapobject import SimpleLDAPObject from ldap.ldapobject import SimpleLDAPObject
@@ -307,6 +308,25 @@ class IPAdmin(SimpleLDAPObject):
raise e raise e
return "Success" return "Success"
def updateEntry(self,dn,olduser,newuser):
"""This wraps the mod function. It assumes that the entry is already
populated with all of the desired objectclasses and attributes"""
sctrl = self.__get_server_controls__()
# find the differences but don't remove attributes that are missing
# from the update
modlist = modifyModlist(olduser, newuser, None, 1)
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 __wrapmethods(self): def __wrapmethods(self):
"""This wraps all methods of SimpleLDAPObject, so that we can intercept """This wraps all methods of SimpleLDAPObject, so that we can intercept
the methods that deal with entries. Instead of using a raw list of tuples the methods that deal with entries. Instead of using a raw list of tuples

View File

@@ -325,3 +325,50 @@ class IPAServer:
users.append(self.convert_entry(u)) users.append(self.convert_entry(u))
return users return users
def update_user (self, args, newuser=None, opts=None):
"""Update a user in LDAP"""
global _LDAPPool
# 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 = newuser
if len(args) == 2:
olduser = args[0]
newuser = args[1]
else:
olduser = args
if (isinstance(olduser, tuple)):
olduser = olduser[0]
if (isinstance(newuser, tuple)):
newuser = newuser[0]
# 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 xmlrpclib.Fault(4, "Old user has no dn")
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:
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
except ldap.LDAPError, e:
raise xmlrpclib.Fault(1, str(e))

View File

@@ -239,6 +239,11 @@ class ModXMLRPCRequestHandler(object):
def handle_request(self,req): def handle_request(self,req):
"""Handle a single XML-RPC request""" """Handle a single XML-RPC request"""
# The LDAP connection pool is not thread-safe. Avoid problems and
# force the forked model for now.
if not apache.mpm_query(apache.AP_MPMQ_IS_FORKED):
raise Fault(3, "Apache must use the forked model")
# XMLRPC uses POST only. Reject anything else # XMLRPC uses POST only. Reject anything else
if req.method != 'POST': if req.method != 'POST':
req.allow_methods(['POST'],1) req.allow_methods(['POST'],1)
@@ -279,6 +284,7 @@ def handler(req, profiling=False):
h.register_function(f.get_add_schema) h.register_function(f.get_add_schema)
h.register_function(f.get_all_users) h.register_function(f.get_all_users)
h.register_function(f.find_users) h.register_function(f.find_users)
h.register_function(f.update_user)
h.handle_request(req) h.handle_request(req)
finally: finally:
pass pass