Handle optional arguments by using the value __NONE__ over XML-RPC.

rpcclient.py must call XML-RPC functions with all arguments.
Removed encode_args and decode_args. They were the source of most of the
  argument pain. Now opts is alwyas appended to the end of the arguments
  so MUST be the last argument in any server-side function (can be None)
Allow the User object to handle unicode data
Small fixes to command-line tools to be friendlier
Broke out get_user() into get_user_by_dn() and get_user_by_uid()
Need to request more than just 'nsAccountLock' attribute when trying to
  see if a user is already marked deleted. If it is blank the record
  coming back is empty. Add 'uid' to the list to guarantee something coming
  back (dn is handled specially)
Added user_container attribute to get_user_* and add_user so the caller
  can specify where in the tree the user will be searched for/added.
Added global default value for user_container
This commit is contained in:
rcritten@redhat.com
2007-08-23 09:44:00 -04:00
parent 23508d33b5
commit 8879ee173e
9 changed files with 102 additions and 141 deletions

View File

@@ -49,8 +49,11 @@ def main():
try: try:
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
client.mark_user_deleted(args[1]) ret = client.mark_user_deleted(args[1])
print args[1] + " successfully deleted" if (ret == "Success"):
print args[1] + " successfully deleted"
else:
print args[1] + " " + ret
except xmlrpclib.Fault, f: except xmlrpclib.Fault, f:
print f.faultString print f.faultString
return 1 return 1

View File

@@ -49,6 +49,11 @@ def main():
try: try:
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory']) users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory'])
if len(users) == 0:
print "No entries found for", args[1]
return 0
for ent in users: for ent in users:
attr = ent.attrList() attr = ent.attrList()

View File

@@ -54,7 +54,7 @@ def main():
usage() usage()
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
user = client.get_user(args[1]) user = client.get_user_by_uid(args[1])
if options.gecos: if options.gecos:
user.setValue('gecos', options.gecos) user.setValue('gecos', options.gecos)

View File

@@ -54,13 +54,19 @@ class IPAClient:
if self.local: if self.local:
self.transport.set_principal(princ) self.transport.set_principal(princ)
def get_user(self,uid,sattrs=None): def get_user_by_uid(self,uid,sattrs=None):
"""Get a specific user by uid. If sattrs is set then only those """Get a specific user by uid. If sattrs is set then only those
attributes will be returned.""" attributes will be returned."""
result = self.transport.get_user(uid,sattrs) result = self.transport.get_user_by_uid(uid,sattrs)
return user.User(result) return user.User(result)
def add_user(self,user): def get_user_by_dn(self,dn,sattrs=None):
"""Get a specific user by uid. If sattrs is set then only those
attributes will be returned."""
result = self.transport.get_user_by_dn(dn,sattrs)
return user.User(result)
def add_user(self,user,user_container=None):
"""Add a user. user is a ipa.user object""" """Add a user. user is a ipa.user object"""
realm = config.config.get_realm() realm = config.config.get_realm()
@@ -87,7 +93,7 @@ class IPAClient:
del user_dict['dn'] del user_dict['dn']
# convert to a regular dict before sending # convert to a regular dict before sending
result = self.transport.add_user(user_dict) result = self.transport.add_user(user_dict, user_container)
return result return result
def get_all_users(self): def get_all_users(self):
@@ -107,10 +113,10 @@ class IPAClient:
result = self.transport.get_add_schema() result = self.transport.get_add_schema()
return result return result
def find_users(self, criteria, sattrs=None): def find_users(self, criteria, sattrs=None, user_container=None):
"""Find users whose uid matches the criteria. Wildcards are """Find users whose uid matches the criteria. Wildcards are
acceptable. Returns a list of User objects.""" acceptable. Returns a list of User objects."""
result = self.transport.find_users(criteria, sattrs) result = self.transport.find_users(criteria, sattrs, user_container)
users = [] users = []
for (attrs) in result: for (attrs) in result:

View File

@@ -67,15 +67,29 @@ class RPCClient:
return user return user
def get_user(self,username,sattrs=None): def get_user_by_uid(self,uid,sattrs=None):
"""Get a specific user. If sattrs is not None then only those """Get a specific user. If sattrs is not None then only those
attributes will be returned. The result is a dict.""" attributes will be returned. The result is a dict."""
server = self.setup_server() server = self.setup_server()
if sattrs is None:
sattrs = "__NONE__"
try: try:
if sattrs is not None: result = server.get_user_by_uid(uid, sattrs)
result = server.get_user(username,sattrs) except xmlrpclib.Fault, fault:
else: raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
result = server.get_user(username) except socket.error, (value, msg):
raise xmlrpclib.Fault(value, msg)
return result
def get_user_by_dn(self,dn,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()
if sattrs is None:
sattrs = "__NONE__"
try:
result = server.get_user_by_dn(dn, sattrs)
except xmlrpclib.Fault, fault: except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString) raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg): except socket.error, (value, msg):
@@ -83,14 +97,17 @@ class RPCClient:
return result return result
def add_user(self,user): def add_user(self,user,user_container=None):
"""Add a new user. Takes as input a dict where the key is the """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 attribute name and the value is either a string or in the case
of a multi-valued field a list of values""" of a multi-valued field a list of values"""
server = self.setup_server() server = self.setup_server()
if user_container is None:
user_container = "__NONE__"
try: try:
result = server.add_user(user) result = server.add_user(user, user_container)
except xmlrpclib.Fault, fault: except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString) raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg): except socket.error, (value, msg):
@@ -128,16 +145,18 @@ class RPCClient:
return result return result
def find_users (self, criteria, sattrs=None): def find_users (self, criteria, sattrs=None, user_container=None):
"""Return a list containing a User object for each user that matches """Return a list containing a User object for each user that matches
the criteria.""" the criteria."""
server = self.setup_server() server = self.setup_server()
try: try:
if sattrs is not None: # None values are not allowed in XML-RPC
result = server.find_users(criteria, sattrs) if sattrs is None:
else: sattrs = "__NONE__"
result = server.find_users(criteria) if user_container is None:
user_container = "__NONE__"
result = server.find_users(criteria, sattrs, user_container)
except xmlrpclib.Fault, fault: except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString) raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg): except socket.error, (value, msg):

View File

@@ -33,7 +33,7 @@ class User:
if isinstance(entrydata,tuple): if isinstance(entrydata,tuple):
self.dn = entrydata[0] self.dn = entrydata[0]
self.data = ldap.cidict.cidict(entrydata[1]) self.data = ldap.cidict.cidict(entrydata[1])
elif isinstance(entrydata,str): elif isinstance(entrydata,str) or isinstance(entrydata,unicode):
self.dn = entrydata self.dn = entrydata
self.data = ldap.cidict.cidict() self.data = ldap.cidict.cidict()
elif isinstance(entrydata,dict): elif isinstance(entrydata,dict):

View File

@@ -20,38 +20,3 @@
# #
__all__ = ["dsinstance", "krbinstance"] __all__ = ["dsinstance", "krbinstance"]
#
# Functions common for the XML RPC client and server
#
# Authors:
# Mike McLean <mikem@redhat.com> (from koji)
# functions for encoding/decoding optional arguments
def encode_args(*args,**opts):
"""The function encodes optional arguments as regular arguments.
This is used to allow optional arguments in xmlrpc calls
Returns a tuple of args
"""
if opts:
opts['__starstar'] = True
args = args + (opts,)
return args
def decode_args(*args):
"""Decodes optional arguments from a flat argument list
Complementary to encode_args
Returns a tuple (args,opts) where args is a tuple and opts is a dict
"""
opts = {}
if len(args) > 0:
last = args[-1]
if type(last) == dict and last.get('__starstar',False):
del last['__starstar']
opts = last
args = args[:-1]
return args,opts

View File

@@ -36,6 +36,8 @@ import re
# Need a global to store this between requests # Need a global to store this between requests
_LDAPPool = None _LDAPPool = None
DefaultContainer = "ou=users,ou=default"
# #
# Apache runs in multi-process mode so each process will have its own # Apache runs in multi-process mode so each process will have its own
# connection. This could theoretically drive the total number of connections # connection. This could theoretically drive the total number of connections
@@ -96,10 +98,10 @@ class IPAServer:
# Convert to LDIF # Convert to LDIF
entry = str(ent) entry = str(ent)
# Strip off any junk # Strip off any junk
entry = entry.strip() entry = entry.strip()
# Don't need to identify binary fields and this breaks the parser so # Don't need to identify binary fields and this breaks the parser so
# remove double colons # remove double colons
entry = entry.replace('::', ':') entry = entry.replace('::', ':')
@@ -123,67 +125,49 @@ class IPAServer:
return user return user
def get_user (self, args, sattrs=None, opts=None): def __get_user (self, base, filter, sattrs=None, opts=None):
"""Get a specific user's entry. Return as a dict of values. """Get a specific user's entry. Return as a dict of values.
Multi-valued fields are represented as lists. Multi-valued fields are represented as lists.
""" """
global _LDAPPool global _LDAPPool
ent="" 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: if opts:
self.set_principal(opts['remoteuser']) self.set_principal(opts['remoteuser'])
if (isinstance(username, tuple)):
username = username[0]
dn = self.get_dn_from_principal(self.princ) dn = self.get_dn_from_principal(self.princ)
filter = "(uid=" + username + ")"
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
ent = m1.getEntry(self.basedn, self.scope, filter, sattrs) ent = m1.getEntry(base, self.scope, filter, sattrs)
_LDAPPool.releaseConn(m1) _LDAPPool.releaseConn(m1)
return self.convert_entry(ent) return self.convert_entry(ent)
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.
"""
filter = "(uid=" + uid + ")"
return self.__get_user(self.basedn, filter, sattrs, opts)
def add_user (self, args, user_container="ou=users,ou=default",opts=None): def get_user_by_dn (self, dn, sattrs=None, opts=None):
"""Get a specific user's entry. Return as a dict of values.
Multi-valued fields are represented as lists.
"""
filter = "(objectClass=*)"
return self.__get_user(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 """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 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 of a multi-valued field a list of values. user_container sets
where in the tree the user is placed.""" where in the tree the user is placed."""
global _LDAPPool global _LDAPPool
# The XML-RPC server marshals the arguments into one variable if user_container is None:
# while the direct caller has them separate. So do a little user_container = DefaultContainer
# bit of gymnastics to figure things out. There has to be a
# better way, so FIXME
if isinstance(args,tuple):
opts = user_container
if len(args) == 2:
user = args[0]
user_container = args[1]
else:
user = args
user_container = "ou=users,ou=default"
else:
user = args
if (isinstance(user, tuple)):
user = user[0]
dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn) dn="uid=%s,%s,%s" % (user['uid'], user_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn) entry = ipaserver.ipaldap.Entry(dn)
@@ -283,26 +267,14 @@ class IPAServer:
return users return users
def find_users (self, args, sattrs=None, opts=None): def find_users (self, criteria, sattrs=None, user_container=None, opts=None):
"""Return a list containing a User object for each """Return a list containing a User object for each
existing user that matches the criteria. existing user that matches the criteria.
""" """
global _LDAPPool global _LDAPPool
# The XML-RPC server marshals the arguments into one variable if user_container is None:
# while the direct caller has them separate. So do a little user_container = DefaultContainer
# 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:
criteria = args[0]
sattrs = args[1]
else:
criteria = args
sattrs = None
else:
criteria = args
if opts: if opts:
self.set_principal(opts['remoteuser']) self.set_principal(opts['remoteuser'])
@@ -319,9 +291,10 @@ class IPAServer:
# FIXME: Is this the filter we want or do we want to do searches of # 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? # cn as well? Or should the caller pass in the filter?
filter = "(|(uid=%s)(cn=%s))" % (criteria, criteria) filter = "(|(uid=%s)(cn=%s))" % (criteria, criteria)
basedn = user_container + "," + self.basedn
try: try:
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
results = m1.getList(self.basedn, self.scope, filter, sattrs) results = m1.getList(basedn, self.scope, filter, sattrs)
_LDAPPool.releaseConn(m1) _LDAPPool.releaseConn(m1)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
results = [] results = []
@@ -343,27 +316,10 @@ class IPAServer:
return new_dict return new_dict
def update_user (self, args, newuser=None, opts=None): def update_user (self, olduser, newuser, opts=None):
"""Update a user in LDAP""" """Update a user in LDAP"""
global _LDAPPool 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]
olduser = self.convert_scalar_values(olduser) olduser = self.convert_scalar_values(olduser)
newuser = self.convert_scalar_values(newuser) newuser = self.convert_scalar_values(newuser)
@@ -385,22 +341,22 @@ class IPAServer:
_LDAPPool.releaseConn(m1) _LDAPPool.releaseConn(m1)
return res return res
def mark_user_deleted (self, args, opts=None): def mark_user_deleted (self, uid, opts=None):
"""Mark a user as inactive in LDAP. We aren't actually deleting """Mark a user as inactive in LDAP. We aren't actually deleting
users here, just making it so they can't log in, etc.""" users here, just making it so they can't log in, etc."""
global _LDAPPool global _LDAPPool
uid = args[0]
if opts: if opts:
self.set_principal(opts['remoteuser']) self.set_principal(opts['remoteuser'])
proxydn = self.get_dn_from_principal(self.princ) proxydn = self.get_dn_from_principal(self.princ)
user = self.get_user(uid, ['dn', 'nsAccountlock'], opts) user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
# Are we doing an add or replace operation? # Are we doing an add or replace operation?
if user.has_key('nsaccountlock'): if user.has_key('nsaccountlock'):
if user['nsaccountlock'] == "true":
return "already marked as deleted"
has_key = True has_key = True
else: else:
has_key = False has_key = False

View File

@@ -133,7 +133,9 @@ class ModXMLRPCRequestHandler(object):
opts={} opts={}
opts['remoteuser'] = remoteuser opts['remoteuser'] = remoteuser
params = ipaserver.encode_args(params, opts) # Tack onto the end of the passed-in arguments any options we also
# need
params = params + (opts,)
# special case # special case
# if method == "get_user": # if method == "get_user":
@@ -164,9 +166,13 @@ class ModXMLRPCRequestHandler(object):
func = self.funcs.get(method,None) func = self.funcs.get(method,None)
if func is None: if func is None:
raise Fault(1, "Invalid method: %s" % method) raise Fault(1, "Invalid method: %s" % method)
params,opts = ipaserver.decode_args(*params)
args = list(params)
ret = func(*params,**opts) for i in range(len(args)):
if args[i] == '__NONE__':
args[i] = None
ret = func(*args)
return ret return ret
@@ -281,7 +287,8 @@ def handler(req, profiling=False):
try: try:
f = funcs.IPAServer() f = funcs.IPAServer()
h = ModXMLRPCRequestHandler() h = ModXMLRPCRequestHandler()
h.register_function(f.get_user) h.register_function(f.get_user_by_uid)
h.register_function(f.get_user_by_dn)
h.register_function(f.add_user) h.register_function(f.add_user)
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)