|
|
|
|
# mod_python script
|
|
|
|
|
|
|
|
|
|
# ipaxmlrpc - an XMLRPC interface for ipa.
|
|
|
|
|
# Copyright (c) 2007 Red Hat
|
|
|
|
|
#
|
|
|
|
|
# IPA is free software; you can redistribute it and/or
|
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
# License as published by the Free Software Foundation;
|
|
|
|
|
# version 2.1 of the License.
|
|
|
|
|
#
|
|
|
|
|
# This software 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
|
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
# License along with this software; if not, write to the Free Software
|
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
#
|
|
|
|
|
# Based on kojixmlrpc - an XMLRPC interface for koji by
|
|
|
|
|
# Mike McLean <mikem@redhat.com>
|
|
|
|
|
#
|
|
|
|
|
# Authors:
|
|
|
|
|
# Rob Crittenden <rcritten@redhat.com>
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
sys.path.append("/usr/share/ipa")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
import traceback
|
|
|
|
|
import pprint
|
|
|
|
|
from xmlrpclib import Marshaller,loads,dumps,Fault
|
|
|
|
|
from mod_python import apache
|
2007-12-11 09:56:37 -05:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
import ipaserver
|
|
|
|
|
import funcs
|
|
|
|
|
from ipa import ipaerror, ipautil
|
2007-08-27 13:45:28 -04:00
|
|
|
import ldap
|
2007-08-22 10:30:51 -07:00
|
|
|
|
|
|
|
|
import string
|
|
|
|
|
import base64
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# An override so we can base64 encode all outgoing values.
|
|
|
|
|
# This is set by calling: Marshaller._Marshaller__dump = xmlrpclib_dump
|
|
|
|
|
#
|
|
|
|
|
# Not currently used.
|
|
|
|
|
#
|
|
|
|
|
def xmlrpclib_escape(s, replace = string.replace):
|
|
|
|
|
"""
|
|
|
|
|
xmlrpclib only handles certain characters. Lets encode the whole
|
|
|
|
|
blob
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return base64.encodestring(s)
|
|
|
|
|
|
|
|
|
|
def xmlrpclib_dump(self, value, write):
|
|
|
|
|
"""
|
|
|
|
|
xmlrpclib cannot marshal instances of subclasses of built-in
|
|
|
|
|
types. This function overrides xmlrpclib.Marshaller.__dump so that
|
|
|
|
|
any value that is an instance of one of its acceptable types is
|
|
|
|
|
marshalled as that type.
|
|
|
|
|
|
|
|
|
|
xmlrpclib also cannot handle invalid 7-bit control characters. See
|
|
|
|
|
above.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Use our escape function
|
|
|
|
|
args = [self, value, write]
|
|
|
|
|
if isinstance(value, (str, unicode)):
|
|
|
|
|
args.append(xmlrpclib_escape)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Try for an exact match first
|
|
|
|
|
f = self.dispatch[type(value)]
|
|
|
|
|
except KeyError:
|
|
|
|
|
# Try for an isinstance() match
|
|
|
|
|
for Type, f in self.dispatch.iteritems():
|
|
|
|
|
if isinstance(value, Type):
|
|
|
|
|
f(*args)
|
|
|
|
|
return
|
|
|
|
|
raise TypeError, "cannot marshal %s objects" % type(value)
|
|
|
|
|
else:
|
|
|
|
|
f(*args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModXMLRPCRequestHandler(object):
|
|
|
|
|
"""Simple XML-RPC handler for mod_python environment"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.funcs = {}
|
|
|
|
|
self.traceback = False
|
|
|
|
|
#introspection functions
|
|
|
|
|
self.register_function(self.list_api, name="_listapi")
|
|
|
|
|
self.register_function(self.system_listMethods, name="system.listMethods")
|
|
|
|
|
self.register_function(self.system_methodSignature, name="system.methodSignature")
|
|
|
|
|
self.register_function(self.system_methodHelp, name="system.methodHelp")
|
|
|
|
|
self.register_function(self.multiCall)
|
|
|
|
|
|
|
|
|
|
def register_function(self, function, name = None):
|
|
|
|
|
if name is None:
|
|
|
|
|
name = function.__name__
|
|
|
|
|
self.funcs[name] = function
|
|
|
|
|
|
|
|
|
|
def register_module(self, instance, prefix=None):
|
|
|
|
|
"""Register all the public functions in an instance with prefix prepended
|
|
|
|
|
|
|
|
|
|
For example
|
|
|
|
|
h.register_module(exports,"pub.sys")
|
|
|
|
|
will register the methods of exports with names like
|
|
|
|
|
pub.sys.method1
|
|
|
|
|
pub.sys.method2
|
|
|
|
|
...etc
|
|
|
|
|
"""
|
|
|
|
|
for name in dir(instance):
|
|
|
|
|
if name.startswith('_'):
|
|
|
|
|
continue
|
|
|
|
|
function = getattr(instance, name)
|
|
|
|
|
if not callable(function):
|
|
|
|
|
continue
|
|
|
|
|
if prefix is not None:
|
|
|
|
|
name = "%s.%s" %(prefix,name)
|
|
|
|
|
self.register_function(function, name=name)
|
|
|
|
|
|
|
|
|
|
def register_instance(self,instance):
|
|
|
|
|
self.register_module(instance)
|
|
|
|
|
|
2007-09-05 13:14:23 -04:00
|
|
|
def _marshaled_dispatch(self, data, req):
|
|
|
|
|
"""Dispatches an XML-RPC method from marshalled (XML) data."""
|
|
|
|
|
|
|
|
|
|
params, method = loads(data)
|
2007-09-21 14:39:52 -04:00
|
|
|
pythonopts = req.get_options()
|
|
|
|
|
|
2007-09-05 13:14:23 -04:00
|
|
|
# Populate the Apache environment variables
|
|
|
|
|
req.add_common_vars()
|
|
|
|
|
|
2007-08-06 10:05:53 -04:00
|
|
|
opts={}
|
2007-09-05 13:14:23 -04:00
|
|
|
opts['remoteuser'] = req.user
|
|
|
|
|
|
|
|
|
|
if req.subprocess_env.get("KRB5CCNAME") is not None:
|
2007-09-14 17:19:02 -04:00
|
|
|
opts['krbccache'] = req.subprocess_env.get("KRB5CCNAME")
|
2007-09-24 15:24:44 -04:00
|
|
|
else:
|
2007-11-09 14:55:41 -05:00
|
|
|
response = dumps(Fault(5, "Did not receive Kerberos credentials."))
|
|
|
|
|
return response
|
2007-08-06 10:05:53 -04:00
|
|
|
|
2007-09-21 14:39:52 -04:00
|
|
|
if pythonopts.get("IPADebug"):
|
|
|
|
|
opts['ipadebug'] = pythonopts.get("IPADebug")
|
|
|
|
|
|
2007-09-24 15:24:44 -04:00
|
|
|
if opts['ipadebug'].lower() == "on":
|
2007-12-11 09:56:37 -05:00
|
|
|
logging.basicConfig(level=logging.DEBUG,
|
|
|
|
|
format='[%(asctime)s] [%(levelname)s] %(message)s',
|
|
|
|
|
datefmt='%a %b %d %H:%M:%S %Y',
|
|
|
|
|
stream=sys.stderr)
|
|
|
|
|
|
2007-09-24 15:24:44 -04:00
|
|
|
for o in opts:
|
2007-12-11 09:56:37 -05:00
|
|
|
logging.debug("IPA: setting option %s: %s" % (o, opts[o]))
|
|
|
|
|
# for e in req.subprocess_env:
|
|
|
|
|
# logging.debug("IPA: environment %s: %s" % (e, req.subprocess_env[e]))
|
2007-09-24 15:24:44 -04:00
|
|
|
|
2007-08-23 09:44:00 -04:00
|
|
|
# Tack onto the end of the passed-in arguments any options we also
|
|
|
|
|
# need
|
|
|
|
|
params = params + (opts,)
|
2007-08-06 10:05:53 -04:00
|
|
|
|
|
|
|
|
# special case
|
|
|
|
|
# if method == "get_user":
|
|
|
|
|
# Marshaller._Marshaller__dump = xmlrpclib_dump
|
|
|
|
|
|
|
|
|
|
start = time.time()
|
|
|
|
|
# generate response
|
|
|
|
|
try:
|
|
|
|
|
response = self._dispatch(method, params)
|
|
|
|
|
# wrap response in a singleton tuple
|
|
|
|
|
response = (response,)
|
|
|
|
|
response = dumps(response, methodresponse=1, allow_none=1)
|
2007-08-22 10:30:51 -07:00
|
|
|
except ipaerror.IPAError, e:
|
|
|
|
|
self.traceback = True
|
2007-08-27 13:45:28 -04:00
|
|
|
|
|
|
|
|
if (isinstance(e.detail, ldap.LDAPError)):
|
|
|
|
|
err = ": %s: %s" % (e.detail.args[0]['desc'], e.detail.args[0].get('info',''))
|
2007-08-27 13:45:28 -04:00
|
|
|
response = dumps(Fault(e.code, str(e) + err))
|
|
|
|
|
else:
|
|
|
|
|
response = dumps(Fault(e.code, str(e)))
|
|
|
|
|
except:
|
|
|
|
|
self.traceback = True
|
|
|
|
|
# report exception back to server
|
|
|
|
|
e_class, e = sys.exc_info()[:2]
|
|
|
|
|
faultCode = getattr(e_class,'faultCode',1)
|
|
|
|
|
tb_str = ''.join(traceback.format_exception(*sys.exc_info()))
|
|
|
|
|
faultString = tb_str
|
|
|
|
|
response = dumps(Fault(faultCode, faultString))
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
def _dispatch(self,method,params):
|
|
|
|
|
func = self.funcs.get(method,None)
|
|
|
|
|
if func is None:
|
|
|
|
|
raise Fault(1, "Invalid method: %s" % method)
|
2007-08-23 09:44:00 -04:00
|
|
|
|
|
|
|
|
args = list(ipautil.unwrap_binary_data(params))
|
2007-08-23 09:44:00 -04:00
|
|
|
for i in range(len(args)):
|
|
|
|
|
if args[i] == '__NONE__':
|
|
|
|
|
args[i] = None
|
|
|
|
|
|
|
|
|
|
ret = func(*args)
|
|
|
|
|
|
|
|
|
|
return ipautil.wrap_binary_data(ret)
|
|
|
|
|
|
|
|
|
|
def multiCall(self, calls):
|
|
|
|
|
"""Execute a multicall. Execute each method call in the calls list, collecting
|
|
|
|
|
results and errors, and return those as a list."""
|
|
|
|
|
results = []
|
|
|
|
|
for call in calls:
|
|
|
|
|
try:
|
|
|
|
|
result = self._dispatch(call['methodName'], call['params'])
|
|
|
|
|
except Fault, fault:
|
|
|
|
|
results.append({'faultCode': fault.faultCode, 'faultString': fault.faultString})
|
|
|
|
|
except:
|
|
|
|
|
# transform unknown exceptions into XML-RPC Faults
|
|
|
|
|
# don't create a reference to full traceback since this creates
|
|
|
|
|
# a circular reference.
|
|
|
|
|
exc_type, exc_value = sys.exc_info()[:2]
|
|
|
|
|
faultCode = getattr(exc_type, 'faultCode', 1)
|
|
|
|
|
faultString = ', '.join(exc_value.args)
|
|
|
|
|
trace = traceback.format_exception(*sys.exc_info())
|
|
|
|
|
# traceback is not part of the multicall spec, but we include it for debugging purposes
|
|
|
|
|
results.append({'faultCode': faultCode, 'faultString': faultString, 'traceback': trace})
|
|
|
|
|
else:
|
|
|
|
|
results.append([result])
|
|
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
2007-09-26 16:31:43 -04:00
|
|
|
def list_api(self,opts):
|
|
|
|
|
funcs = []
|
|
|
|
|
for name,func in self.funcs.items():
|
|
|
|
|
#the keys in self.funcs determine the name of the method as seen over xmlrpc
|
|
|
|
|
#func.__name__ might differ (e.g. for dotted method names)
|
|
|
|
|
args = self._getFuncArgs(func)
|
|
|
|
|
funcs.append({'name': name,
|
|
|
|
|
'doc': func.__doc__,
|
|
|
|
|
'args': args})
|
|
|
|
|
return funcs
|
|
|
|
|
|
|
|
|
|
def _getFuncArgs(self, func):
|
|
|
|
|
args = []
|
|
|
|
|
for x in range(0, func.func_code.co_argcount):
|
|
|
|
|
if x == 0 and func.func_code.co_varnames[x] == "self":
|
|
|
|
|
continue
|
2007-10-01 13:34:43 -04:00
|
|
|
# opts is a name we tack on internally. Don't publish it.
|
|
|
|
|
if func.func_code.co_varnames[x] == "opts":
|
|
|
|
|
continue
|
|
|
|
|
if func.func_defaults and func.func_code.co_argcount - x <= len(func.func_defaults):
|
|
|
|
|
args.append((func.func_code.co_varnames[x], func.func_defaults[x - func.func_code.co_argcount + len(func.func_defaults)]))
|
|
|
|
|
else:
|
|
|
|
|
args.append(func.func_code.co_varnames[x])
|
|
|
|
|
return args
|
|
|
|
|
|
2007-09-26 16:31:43 -04:00
|
|
|
def system_listMethods(self, opts):
|
|
|
|
|
return self.funcs.keys()
|
|
|
|
|
|
2007-09-26 16:31:43 -04:00
|
|
|
def system_methodSignature(self, method, opts):
|
|
|
|
|
#it is not possible to autogenerate this data
|
|
|
|
|
return 'signatures not supported'
|
|
|
|
|
|
2007-09-26 16:31:43 -04:00
|
|
|
def system_methodHelp(self, method, opts):
|
|
|
|
|
func = self.funcs.get(method)
|
|
|
|
|
if func is None:
|
|
|
|
|
return ""
|
|
|
|
|
arglist = []
|
|
|
|
|
for arg in self._getFuncArgs(func):
|
|
|
|
|
if isinstance(arg,str):
|
|
|
|
|
arglist.append(arg)
|
|
|
|
|
else:
|
|
|
|
|
arglist.append('%s=%s' % (arg[0], arg[1]))
|
|
|
|
|
ret = '%s(%s)' % (method, ", ".join(arglist))
|
|
|
|
|
if func.__doc__:
|
|
|
|
|
ret += "\ndescription: %s" % func.__doc__
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
def handle_request(self,req):
|
|
|
|
|
"""Handle a single XML-RPC request"""
|
|
|
|
|
|
|
|
|
|
# XMLRPC uses POST only. Reject anything else
|
|
|
|
|
if req.method != 'POST':
|
|
|
|
|
req.allow_methods(['POST'],1)
|
|
|
|
|
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
|
|
|
|
|
|
2007-11-09 14:55:41 -05:00
|
|
|
# The LDAP connection pool is not thread-safe. Avoid problems and
|
|
|
|
|
# force the forked model for now.
|
|
|
|
|
if apache.mpm_query(apache.AP_MPMQ_IS_THREADED):
|
|
|
|
|
response = dumps(Fault(3, "Apache must use the forked model"))
|
|
|
|
|
else:
|
|
|
|
|
response = self._marshaled_dispatch(req.read(), req)
|
|
|
|
|
|
|
|
|
|
req.content_type = "text/xml"
|
|
|
|
|
req.set_content_length(len(response))
|
|
|
|
|
req.write(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# mod_python handler
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
def handler(req, profiling=False):
|
|
|
|
|
if profiling:
|
|
|
|
|
import profile, pstats, StringIO, tempfile
|
|
|
|
|
global _profiling_req
|
|
|
|
|
_profiling_req = req
|
|
|
|
|
temp = tempfile.NamedTemporaryFile()
|
|
|
|
|
profile.run("import ipxmlrpc; ipaxmlrpc.handler(ipaxmlrpc._profiling_req, False)", temp.name)
|
|
|
|
|
stats = pstats.Stats(temp.name)
|
|
|
|
|
strstream = StringIO.StringIO()
|
|
|
|
|
sys.stdout = strstream
|
|
|
|
|
stats.sort_stats("time")
|
|
|
|
|
stats.print_stats()
|
|
|
|
|
req.write("<pre>" + strstream.getvalue() + "</pre>")
|
|
|
|
|
_profiling_req = None
|
|
|
|
|
else:
|
|
|
|
|
opts = req.get_options()
|
|
|
|
|
try:
|
2007-08-06 10:05:53 -04:00
|
|
|
f = funcs.IPAServer()
|
|
|
|
|
h = ModXMLRPCRequestHandler()
|
2007-10-12 15:11:55 -07:00
|
|
|
h.register_function(f.get_aci_entry)
|
2007-10-09 09:26:16 -07:00
|
|
|
h.register_function(f.get_entry_by_dn)
|
|
|
|
|
h.register_function(f.get_entry_by_cn)
|
2007-10-15 09:04:13 -07:00
|
|
|
h.register_function(f.update_entry)
|
2007-08-23 09:44:00 -04:00
|
|
|
h.register_function(f.get_user_by_uid)
|
2007-10-01 17:33:16 -04:00
|
|
|
h.register_function(f.get_user_by_principal)
|
2007-10-18 14:33:55 -07:00
|
|
|
h.register_function(f.get_user_by_email)
|
2007-09-25 15:44:49 -07:00
|
|
|
h.register_function(f.get_users_by_manager)
|
2007-08-06 10:05:53 -04:00
|
|
|
h.register_function(f.add_user)
|
2007-11-16 12:59:32 -05:00
|
|
|
h.register_function(f.get_custom_fields)
|
|
|
|
|
h.register_function(f.set_custom_fields)
|
2007-08-06 10:05:53 -04:00
|
|
|
h.register_function(f.get_all_users)
|
2007-08-13 16:41:38 -04:00
|
|
|
h.register_function(f.find_users)
|
2007-08-14 17:22:05 -04:00
|
|
|
h.register_function(f.update_user)
|
2007-08-28 13:52:08 -04:00
|
|
|
h.register_function(f.delete_user)
|
2007-11-20 22:45:29 -05:00
|
|
|
h.register_function(f.mark_user_active)
|
|
|
|
|
h.register_function(f.mark_user_inactive)
|
|
|
|
|
h.register_function(f.mark_group_active)
|
|
|
|
|
h.register_function(f.mark_group_inactive)
|
2007-09-11 02:48:53 -04:00
|
|
|
h.register_function(f.modifyPassword)
|
2007-09-25 13:35:43 -07:00
|
|
|
h.register_function(f.get_groups_by_member)
|
2007-08-24 15:42:56 -04:00
|
|
|
h.register_function(f.add_group)
|
|
|
|
|
h.register_function(f.find_groups)
|
2007-09-26 15:47:34 -07:00
|
|
|
h.register_function(f.add_member_to_group)
|
|
|
|
|
h.register_function(f.add_members_to_group)
|
|
|
|
|
h.register_function(f.remove_member_from_group)
|
|
|
|
|
h.register_function(f.remove_members_from_group)
|
2007-08-24 15:42:56 -04:00
|
|
|
h.register_function(f.add_user_to_group)
|
|
|
|
|
h.register_function(f.add_users_to_group)
|
2007-08-28 13:52:08 -04:00
|
|
|
h.register_function(f.add_group_to_group)
|
2007-08-24 15:42:56 -04:00
|
|
|
h.register_function(f.remove_user_from_group)
|
|
|
|
|
h.register_function(f.remove_users_from_group)
|
2007-09-28 16:01:42 -07:00
|
|
|
h.register_function(f.add_groups_to_user)
|
|
|
|
|
h.register_function(f.remove_groups_from_user)
|
2007-08-24 15:42:56 -04:00
|
|
|
h.register_function(f.update_group)
|
2007-08-28 13:52:08 -04:00
|
|
|
h.register_function(f.delete_group)
|
2007-10-22 17:06:52 -04:00
|
|
|
h.register_function(f.attrs_to_labels)
|
2008-01-04 16:39:41 -05:00
|
|
|
h.register_function(f.get_all_attrs)
|
|
|
|
|
h.register_function(f.group_members)
|
2007-11-16 12:59:32 -05:00
|
|
|
h.register_function(f.get_ipa_config)
|
|
|
|
|
h.register_function(f.update_ipa_config)
|
|
|
|
|
h.register_function(f.get_password_policy)
|
|
|
|
|
h.register_function(f.update_password_policy)
|
|
|
|
|
h.register_function(f.add_service_principal)
|
2007-12-05 15:17:11 -05:00
|
|
|
h.register_function(f.find_service_principal)
|
2007-11-14 00:04:19 -05:00
|
|
|
h.register_function(f.get_radius_client_by_ip_addr)
|
2007-11-13 20:05:02 -05:00
|
|
|
h.register_function(f.add_radius_client)
|
2007-11-14 00:04:19 -05:00
|
|
|
h.register_function(f.update_radius_client)
|
|
|
|
|
h.register_function(f.delete_radius_client)
|
2007-11-14 15:32:08 -05:00
|
|
|
h.register_function(f.find_radius_clients)
|
Add radius profile implementations:
get_radius_profile_by_uid
add_radius_profile
update_radius_profile
delete_radius_profile
find_radius_profiles
Rewrite command line arg handling, now support pair entry, interactive
mode with auto completion, reading pairs from a file, better handling
of mandatory values, better help, long arg names now match attribute
name in pairs
Establish mappings for all attributes and names used in clients and
profiles
Add notion of containers to radius clients and profiles in LDAP
Move common code, variables, constants, and strings into the files
radius_client.py, radius_util.py, ipautil.py to eliminate redundant
elements which could get out of sync if modified and to provide access
to other code which might benefit from using these items in the
future.
Add utility functions:
format_list()
parse_key_value_pairs()
Add utility class:
AttributeValueCompleter
Unify attribute usage in radius ldap schema
2007-11-21 13:11:10 -05:00
|
|
|
h.register_function(f.get_radius_profile_by_uid)
|
|
|
|
|
h.register_function(f.add_radius_profile)
|
|
|
|
|
h.register_function(f.update_radius_profile)
|
|
|
|
|
h.register_function(f.delete_radius_profile)
|
|
|
|
|
h.register_function(f.find_radius_profiles)
|
|
|
|
|
h.handle_request(req)
|
|
|
|
|
finally:
|
|
|
|
|
pass
|
|
|
|
|
return apache.OK
|