On 01/13/2014 05:11 PM, Rob Crittenden wrote:
Petr Viktorin wrote:
See commit message & ticket.

https://fedorahosted.org/freeipa/ticket/4069

Our handling of XML-RPC introspection is iffy as it is and this would
remove those methods completely. Can you add them back into the
xmlserver class?

Not just iffy, it's non-existent. See https://fedorahosted.org/freeipa/ticket/2937. The patch removes the __system dict which has been unused since at least Jason's web UI work in 2009, Git tells me. The patch did leave the actual methods.


Well, actually it's not that hard to get these working again; see attached patch (which can be reviewed separately).

--
PetrĀ³

P.S. to test raw XML, you can use:

curl -v \
    -H "referer:https://`hostname`/ipa"; \
    -H "Content-Type:text/xml" \
    -H "Accept:applicaton/xml" \
    --negotiate -u : \
    --delegation always \
    --cacert /etc/ipa/ca.crt \
    -d "<?xml version='1.0' encoding='UTF-8'?>
    <methodCall>
        <methodName>system.methodHelp</methodName>
        <params>
            <param><type>string</type><value>ping</value></param>
        </params>
    </methodCall>"     -X POST       https://`hostname`/ipa/xml

From 5b85bb1afc5112e79177bd5995712b9a202661cb Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Tue, 14 Jan 2014 13:41:19 +0100
Subject: [PATCH] Implement XML introspection

https://fedorahosted.org/freeipa/ticket/2937
---
 ipaserver/rpcserver.py           | 57 ++++++++++++++++++++++---
 ipatests/test_ipalib/test_rpc.py | 92 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 140 insertions(+), 9 deletions(-)

diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index a2a9db8ae4b0bac8402b7bee1caae45d27ce450a..05fbdfedb7871f48286c6414ae1b2392d449f17d 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -31,7 +31,7 @@
 import time
 import json
 
-from ipalib import plugable, capabilities
+from ipalib import plugable, capabilities, errors
 from ipalib.backend import Executioner
 from ipalib.errors import (PublicError, InternalError, CommandError, JSONError,
     CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError,
@@ -290,6 +290,8 @@ class WSGIExecutioner(Executioner):
     content_type = None
     key = ''
 
+    _system_commands = {}
+
     def set_api(self, api):
         super(WSGIExecutioner, self).set_api(api)
         if 'wsgi_dispatch' in self.api.Backend:
@@ -331,9 +333,12 @@ def wsgi_execute(self, environ):
                 (name, args, options, _id) = self.unmarshal(data)
             else:
                 (name, args, options, _id) = self.simple_unmarshal(environ)
-            if name not in self.Command:
+            if name in self._system_commands:
+                result = self._system_commands[name](self, *args, **options)
+            elif name not in self.Command:
                 raise CommandError(name=name)
-            result = self.Command[name](*args, **options)
+            else:
+                result = self.Command[name](*args, **options)
         except PublicError, e:
             error = e
         except StandardError, e:
@@ -650,16 +655,56 @@ class xmlserver(KerberosWSGIExecutioner):
     key = '/xml'
 
     def listMethods(self, *params):
-        return tuple(name.decode('UTF-8') for name in self.Command)
+        """list methods for XML-RPC introspection"""
+        if params:
+            raise errors.ZeroArgumentError(name='system.listMethods')
+        return (tuple(unicode(name) for name in self.Command) +
+                tuple(unicode(name) for name in self._system_commands))
+
+    def _get_method_name(self, name, *params):
+        """Get a method name for XML-RPC introspection commands"""
+        if not params:
+            raise errors.RequirementError(name='method name')
+        elif len(params) > 1:
+            raise errors.MaxArgumentError(name=name, count=1)
+        [method_name] = params
+        return method_name
 
     def methodSignature(self, *params):
-        return u'methodSignature not implemented'
+        """get method signature for XML-RPC introspection"""
+        method_name = self._get_method_name('system.methodSignature', *params)
+        if method_name in self._system_commands:
+            # TODO
+            # for now let's not go out of our way to document standard XML-RPC
+            return u'undef'
+        elif method_name in self.Command:
+            # All IPA commands return a dict (struct),
+            # and take a params, options - list and dict (array, struct)
+            return [u'struct', u'array', u'struct']
+        else:
+            raise errors.CommandError(name=method_name)
 
     def methodHelp(self, *params):
-        return u'methodHelp not implemented'
+        """get method docstring for XML-RPC introspection"""
+        method_name = self._get_method_name('system.methodHelp', *params)
+        if method_name in self._system_commands:
+            return u''
+        elif method_name in self.Command:
+            return unicode(self.Command[method_name].__doc__ or '')
+        else:
+            raise errors.CommandError(name=method_name)
+
+    _system_commands = {
+        'system.listMethods': listMethods,
+        'system.methodSignature': methodSignature,
+        'system.methodHelp': methodHelp,
+    }
 
     def unmarshal(self, data):
         (params, name) = xml_loads(data)
+        if name in self._system_commands:
+            # For XML-RPC introspection, return params directly
+            return (name, params, {}, None)
         (args, options) = params_2_args_options(params)
         if 'version' not in options:
             # Keep backwards compatibility with client containing
diff --git a/ipatests/test_ipalib/test_rpc.py b/ipatests/test_ipalib/test_rpc.py
index 56b8184cf787b842f4818ff9f4fea6ca151926fa..c874262a45cd8688938acddc849c973e6257d2cf 100644
--- a/ipatests/test_ipalib/test_rpc.py
+++ b/ipatests/test_ipalib/test_rpc.py
@@ -21,13 +21,14 @@
 Test the `ipalib.rpc` module.
 """
 
-import threading
-from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy
+from xmlrpclib import Binary, Fault, dumps, loads
+
+import nose
 from ipatests.util import raises, assert_equal, PluginTester, DummyClass
 from ipatests.data import binary_bytes, utf8_bytes, unicode_str
 from ipalib.frontend import Command
 from ipalib.request import context, Connection
-from ipalib import rpc, errors
+from ipalib import rpc, errors, api, request
 
 
 std_compound = (binary_bytes, utf8_bytes, unicode_str)
@@ -242,3 +243,88 @@ class user_add(Command):
         assert_equal(e.error, u'no such error')
 
         assert context.xmlclient.conn._calledall() is True
+
+
+class test_xml_introspection(object):
+    @classmethod
+    def setUpClass(self):
+        try:
+            api.Backend.xmlclient.connect(fallback=False)
+        except (errors.NetworkError, IOError):
+            raise nose.SkipTest('%r: Server not available: %r' %
+                                (__name__, api.env.xmlrpc_uri))
+
+    @classmethod
+    def tearDownClass(self):
+        request.destroy_context()
+
+    def test_list_methods(self):
+        result = api.Backend.xmlclient.conn.system.listMethods()
+        assert len(result)
+        assert 'ping' in result
+        assert 'user_add' in result
+        assert 'system.listMethods' in result
+        assert 'system.methodSignature' in result
+        assert 'system.methodHelp' in result
+
+    def test_list_methods_many_params(self):
+        try:
+            result = api.Backend.xmlclient.conn.system.listMethods('foo')
+        except Fault, f:
+            print f
+            assert f.faultCode == 3003
+            assert f.faultString == (
+                "command 'system.listMethods' takes no arguments")
+        else:
+            raise AssertionError('did not raise')
+
+    def test_ping_signature(self):
+        result = api.Backend.xmlclient.conn.system.methodSignature('ping')
+        assert result == ['struct', 'array', 'struct']
+
+
+    def test_ping_help(self):
+        result = api.Backend.xmlclient.conn.system.methodHelp('ping')
+        assert result == 'Ping a remote server.'
+
+    def test_signature_no_params(self):
+        try:
+            result = api.Backend.xmlclient.conn.system.methodSignature()
+        except Fault, f:
+            print f
+            assert f.faultCode == 3007
+            assert f.faultString == "'method name' is required"
+        else:
+            raise AssertionError('did not raise')
+
+    def test_signature_many_params(self):
+        try:
+            result = api.Backend.xmlclient.conn.system.methodSignature('a', 'b')
+        except Fault, f:
+            print f
+            assert f.faultCode == 3004
+            assert f.faultString == (
+                "command 'system.methodSignature' takes at most 1 argument")
+        else:
+            raise AssertionError('did not raise')
+
+    def test_help_no_params(self):
+        try:
+            result = api.Backend.xmlclient.conn.system.methodHelp()
+        except Fault, f:
+            print f
+            assert f.faultCode == 3007
+            assert f.faultString == "'method name' is required"
+        else:
+            raise AssertionError('did not raise')
+
+    def test_help_many_params(self):
+        try:
+            result = api.Backend.xmlclient.conn.system.methodHelp('a', 'b')
+        except Fault, f:
+            print f
+            assert f.faultCode == 3004
+            assert f.faultString == (
+                "command 'system.methodHelp' takes at most 1 argument")
+        else:
+            raise AssertionError('did not raise')
-- 
1.8.4.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to