[Freeipa-users] call implemented methods via xml-rpc

ALAHYANE Rachid afkkir at gmail.com
Wed Apr 21 18:23:30 UTC 2010


Here is my apache logs :
------------------------------------------------------------------------------------------------
==> /var/log/httpd/error_log <==
[Wed Apr 21 20:02:51 2010] [warn] mod_python (pid=1529, interpreter='
rpcserver.domain.org'): Module directory listed in "sys.path". This may
cause problems. Please check code. File being imported is
"/usr/lib/python2.6/site-packages/webservices/account.py".
[Wed Apr 21 20:02:51 2010] [notice] mod_python (pid=1529, interpreter='
rpcserver.domain.org'): Importing module
'/usr/lib/python2.6/site-packages/webservices/account.py'
/usr/lib/python2.6/site-packages/mod_python/importer.py:32:
DeprecationWarning: the md5 module is deprecated; use hashlib instead
  import md5
ipa: ERROR: Could not create log_dir '/root/.ipa/log'
ipa: ERROR: could not load plugin module
'/usr/lib/python2.6/site-packages/ipalib/plugins/migration.py'
Traceback (most recent call last):
  File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 533, in
import_plugins
    __import__(fullname)
  File "/usr/lib/python2.6/site-packages/ipalib/plugins/migration.py", line
33, in <module>
    from ipaserver.plugins.ldap2 import ldap2
  File "/usr/lib/python2.6/site-packages/ipaserver/__init__.py", line 33, in
<module>
    api.bootstrap(context='server', debug=True, log=None)
  File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 380, in
bootstrap
    self.__doing('bootstrap')
  File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 365, in
__doing
    '%s.%s() already called' % (self.__class__.__name__, name)
StandardError: API.bootstrap() already called


==> /var/log/httpd/access_log <==
172.30.0.135 - - [21/Apr/2010:20:02:51 +0200] "POST /xmlrpc HTTP/1.0" 200
348 "-" "xmlrpclib.py/1.0.1 (by www.pythonware.com)"

------------------------------------------------------------------------------------------------


And here my xmlrpchandler
------------------------------------------------------------------------------------------------
import sys
import os
from mod_python import apache
import xmlrpclib
import types

import imp
import re


# Functions we want callable via XML-RPC
__all__ = ['listMethods', 'methodSignature', 'methodHelp', 'multicall']

# For method signatures
INT = 'int'
STRING = 'string'
BOOLEAN = 'boolean'
DOUBLE = 'double'
DATETIME = 'dateTime.iso8601'
BASE64 = 'base64'
ARRAY = 'array'
STRUCT = 'struct'

# Saw this done in mod_python's apache.py. I just fixed it up a little...
_suffixes = map(lambda x: x[0].replace('.', '\\.'), imp.get_suffixes())
_exp = '(' + '|'.join(_suffixes) + ')$'
_suffix_re = re.compile(_exp)

_environ = {}




def listMethods(env=None):
    """Enumerates all available XML-RPC methods."""
    __xmlrpc_signature = '[[ARRAY]]'

    method_list = []

    # scan the directory which this module resides in
    path = os.path.dirname(sys.modules[__name__].__file__)
    try:
        module_list = []

        files = os.listdir(path)
        for f in files:
            # does it have a module suffix?
            if not _suffix_re.search(f):
                continue
            # strip module suffix
            module_name = _suffix_re.sub('', f)
            # ensure it's not private or this module
            if module_name[0] == '_' or module_name == __name__:
                continue
            if module_name not in module_list:
                module_list.append(module_name)

        for module_name in module_list:
            try:
                module = apache.import_module(module_name, path=[path])
            except:
                pass
            else:
                # scan module for non-private functions
                func_list = getattr(module, '__all__', dir(module))
                for func_name in func_list:
                    if func_name[0] != '_' and \
                           callable(getattr(module, func_name, None)):
                        method_list.append('%s.%s' % (module_name,
func_name))
    except:
        pass

    # add system methods
    method_list.extend(map(lambda x: 'system.%s' % x, __all__))

    method_list.sort()
    return method_list

def methodSignature(method, env=None):
    """Returns an XML-RPC method's signature."""
    __xmlrpc_signature = '[[ARRAY, STRING]]'

    func = _map_methodName(method)
    if not func:
        return xmlrpclib.Fault(1, '%s: not implemented' % method)

    return _get_signature(func)

def methodHelp(method, env=None):
    """Returns an XML-RPC method's help string."""
    __xmlrpc_signature = '[[STRING, STRING]]'

    func = _map_methodName(method)
    if not func:
        return xmlrpclib.Fault(1, '%s: not implemented' % method)

    if func.__doc__:
        help = _strip_docstring(func.__doc__)
    else:
        help = ''

    return help

def multicall(call_params, env=None):
    """Executes multiple method calls with a single request."""
    __xmlrpc_signature = '[[ARRAY, ARRAY]]'

    result_list = []

    for param in call_params:
        if type(param) != dict:
            result_list.append(_fault_struct(1, 'struct expected'))
            continue

        if not param.has_key('methodName') or \
               not param.has_key('params'):
            result_list.append(_fault_struct(1, 'methodName/params members '
\
                                             'missing'))
            continue

        method, params = param['methodName'], param['params']

        if method == 'system.multicall':
            result_list.append(_fault_struct(1, 'system.multicall: ' \
                                             'recursion forbidden'))
            continue

        try:
            result = _dispatch(method, params)
            if isinstance(result, xmlrpclib.Fault):
                result = _fault_struct(result.faultCode, result.faultString)
            else:
                result = (result,)
        except:
            result_list.append(_fault_struct(2, '%s: %s: %s' %
                                             (method, sys.exc_type,
                                              sys.exc_value)))
        else:
            result_list.append(result)

    return result_list

def _expand_tabs(s, width=8):
    """Expands tabs to spaces, assuming tabs are of the specified width."""

    o = ''
    col = 0
    for c in s:
        if c == '\t':
            next = width - (col % width)
            o += '        '[:next]
            col += next
        else:
            o += c
            col += 1
    return o

def _strip_docstring(s):
    """Takes a docstring and removes any extraneous indentation."""

    # Break into lines and expand tabs.
    s = s.split('\n')
    s = map(_expand_tabs, s)

    # Convert lines with only spaces to empty strings.
    for i in range(len(s)):
        if s[i] and not s[i].strip():
            s[i] = ''

    # Single line or empty docstring.
    if len(s) == 1:
        return s[0]

    # Take care of the first line.
    o = ''
    o += s[0] + '\n'

    # Go through each line. The first non-blank line determines the indent
for
    # the entire docstring. Unindent each line.
    indent = 0
    for line in s[1:]:
        if line:
            if not indent:
                indent = len(line) - len(line.lstrip())
            if line.startswith(' '*indent):
                line = line[indent:]
            else:
                # Indent was short. Strip as much as we can anyway.
                line = line.lstrip()
            o += line + '\n'
        else:
            o += '\n'

    # If docstring ends with two linefeeds, remove one of them.
    if o[-2:] == '\n\n':
        o = o[:-1]

    return o

def _get_func_const(func, name, default=None):
    """Get the value of a constant variable defined in a function."""

    func_code = getattr(func, 'func_code', None)
    if func_code:
        if name in func_code.co_names:
            i = list(func_code.co_names).index(name) + 1
            return func_code.co_consts[i]
    return default

def _get_signature(func):
    """Return the parameter signature of a function.

    Will first check func.__xmlrpc_signature, expecting it to be a list
    of signatures. Otherwise, it will check a constant variable called
    __xmlrpc_signature defined within the function. The variable MUST
    be a string and must evaluate to a list of signatures.

    Returns an empty list if none neither are a valid signature list.
    """

    if hasattr(func, '__xmlrpc_signature'):
        sig = func.__xmlrpc_signature
    else:
        sig_str = _get_func_const(func, '__xmlrpc_signature')
        sig = []
        if sig_str:
            try:
                sig = eval(sig_str)
                # need to validate the signature someday...
            except:
                pass

    return sig

_type_map = {
    types.IntType: INT,
    types.LongType: INT,
    types.StringType: STRING,
    types.FloatType: DOUBLE,
    types.TupleType: ARRAY,
    types.ListType: ARRAY,
    types.DictType: STRUCT
    }

def _xmlrpc_type(v):
    """Returns an XML-RPC type for a given value."""

    t = type(v)
    if _type_map.has_key(t):
        return _type_map[t]
    if t is types.InstanceType:
        if isinstance(v, xmlrpclib.DateTime):
            return DATETIME
        elif isinstance(v, xmlrpclib.Binary):
            return BASE64
        elif isinstance(v, xmlrpclib.Boolean):
            return BOOLEAN
    # Huh?!
    return STRING

def _match_signature(params, sig_list):
    """Matches an argument list with a signature list.

    If signature list is empty, any sort of argument list is accepted.
    """

    param_types = map(_xmlrpc_type, params)
    empty_sig = 1
    for sig in sig_list:
        empty_sig = 0

        # skip return type
        sig = sig[1:]

        if len(param_types) == len(sig) and param_types == sig:
            return 1

    return empty_sig

def _fault_struct(faultCode, faultString):
    """Returns a Fault as a dictionary."""

    return { 'faultCode': faultCode, 'faultString': faultString }

def _map_methodName(method):
    """Maps a methodName in the form of module.function to a function."""

    # parse methodName as module.function
    method = method.split('.')
    if len(method) != 2:
        return None

    module_name, func_name = method

    if module_name == 'system':
        # reserved functions are implemented in this module
        module = sys.modules[__name__]
    else:
        # attempt to load module from the same directory as this module
        path = os.path.dirname(sys.modules[__name__].__file__)

        module = None
        # ensure it is not private
        if module_name[0] != '_' and module_name != __name__:
            try:
                module = apache.import_module(module_name, path=[path])
            except:
                pass

        if module is None:
            return None

    # now see if module has callable function named func_name
    if hasattr(module, '__all__') and func_name not in module.__all__:
        return None

    if func_name[0] != '_' and hasattr(module, func_name):
        func = getattr(module, func_name)

        if callable(func):
            return func

    return None

def _dispatch(method, params):
    """Calls an XML-RPC method."""
    global _environ

    func = _map_methodName(method)
    if func is None:
        return xmlrpclib.Fault(1, '%s: not implemented' % method)

    # check arguments
    sig_list = _get_signature(func)
    if not _match_signature(params, sig_list):
        return xmlrpclib.Fault(1, '%s: bad arguments' % method )

    # call the function
    result = apply(func, params, {'env':_environ})

    # if result is None, set it to False
    if result is None:
        result = xmlrpclib.False

    return result

# These HTTP headers are required according to the XML-RPC spec
_required_headers = ['Host', 'User-Agent', 'Content-Type', 'Content-Length']

def handler(req):
    """mod_python handler."""
    global _environ
    _environ = dict(apache.build_cgi_env(req))
    # only accept POST requests
    if req.method != 'POST':
        # We set this even though it doesn't seem to do anything...
        req.err_headers_out['Allow'] = 'POST'
        return apache.HTTP_METHOD_NOT_ALLOWED

    # check that all required headers are present
    for h in _required_headers:
        if not req.headers_in.has_key(h):
            return apache.HTTP_BAD_REQUEST

    if not req.headers_in['Content-Type'].startswith('text/xml'):
        return apache.HTTP_UNSUPPORTED_MEDIA_TYPE

    # The structure of the following was inspired by Brian Quinlan's
    # SimpleXMLRPCServer.py (which was inspired by Fredrik Lundh's code).
    try:
        length = int(req.headers_in['Content-Length'])

        # read and parse request
        data = req.read(length)
        params, method = xmlrpclib.loads(data)

        try:
            # dispatch the method
            response = _dispatch(method, params)

            # convert response to singleton, if necessary
            if not isinstance(response, xmlrpclib.Fault):
                response = (response,)
        except:
            # caught an exception, return it as our fault response
            response = xmlrpclib.Fault(2, '%s: %s: %s' %
                                       (method, sys.exc_type,
sys.exc_value))

        # convert response to xml
        response = xmlrpclib.dumps(response, methodresponse=1)
    except:
        # eh?!
        return apache.HTTP_BAD_REQUEST
    else:
        # send response
        req.content_type = 'text/xml'
        req.headers_out['Content-Length'] = str(len(response))
        req.send_http_header()
        req.write(response)
        return apache.OK
------------------------------------------------------------------------------------------------


And here my code of client
------------------------------------------------------------------------------------------------
#!/usr/bin/python



import xmlrpclib as rpc
remote = rpc.ServerProxy('http://rpcserver.domain.org/xmlrpc<http://client2.gamma.agorabox.org/xmlrpc>
')
print "+++++++ remote.account.getUserInfos(u'admin')"
print remote.account.getUserInfos(u'admin')
------------------------------------------------------------------------------------------------



---
Meilleures salutations / Best Regards

Rachid ALAHYANE



2010/4/21 Rob Crittenden <rcritten at redhat.com>

> ALAHYANE Rachid wrote:
>
>> Ok so, my end goal is to use the ipa methods with xml-rpc as following,
>>
>>  *  ipaServer : my ipa server, used to authenticate users and serves
>> response for xml-rpc calls from rpcServer
>>  *  rpcServer : this host is my xml-rpc server, I installed freeipa
>> libraires on it, and an apache server with mod_python and mod_auth_kerb.
>> This hosts will be used as a client ipa, It is for these reasons that i used
>> `account.py` from within apache.
>>  * myClient : this host is the one which will make the rpc calls to
>> rpcServer.
>>
>> NB : 'account.py' is called by xmlrpchandler (it is my python handler)
>> when getUserInfos is called by myclient .
>>
>
> Can you set LogLevel debug on the rpcServer web server and see if you get a
> backtrace (or look in /var/log/httpd/error_log, you may already have one).
>
> What is strange is that this code works fine standalone. Can you show us
> all the code in your xmlrpchandler?
>
> BTW, your English is fine :-)
>
> rob
>
>
>
>>
>> Example :
>> myClient calls the remote.account.getUserInfos(u'admin'), rpcServer (in
>> mode client) intercepts this call and forwards it to the ipaServer. This
>> last one sends the response to rpcServer (via xml-rpc) and then rpcServer
>> responds to myClient.
>>
>>
>> this is my configurations :
>>
>> == On rpcServer ==
>> --------- httpd conf ------------
>> <Files "xmlrpc">
>>  ## python conf
>>
>>                             # ....
>>  SetHandler python-program
>>  PythonHandler xmlrpchandler
>>  PythonDebug on
>> </Files>
>> ------------------------------------
>>
>> the handler xmlrpchandler calls the following method when the client
>> requests for the remote method getUserInfos().
>>
>> --------- account.py ------------
>> def getUserInfos(user_name, env=None):
>>
>>    from ipalib import api
>>
>>    api.bootstrap_with_global_options(context='webservices')
>>    api.finalize()
>>    # mode Server is False. I am not on the server ipa
>>    api.Backend.xmlclient.connect()
>>    return api.Command.user_show(user_name)
>>  ------------------------------------
>>
>>
>> == On myClient ==
>> When I call  this method from my client, I get this exception :
>> ------------------------------------
>> <Fault 2: "remote.account.getUserInfos: <type 'exceptions.StandardError'>:
>> API.bootstrap() already called">
>> ------------------------------------
>>
>>
>> I hope that is clearer now, despite my bad English ;)
>>
>>
>> ---
>> Meilleures salutations / Best Regards
>>
>> Rachid ALAHYANE
>>
>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/freeipa-users/attachments/20100421/1418893f/attachment.htm>


More information about the Freeipa-users mailing list