diff options
author | Jan Cholasta <jcholast@redhat.com> | 2014-03-28 09:51:10 +0100 |
---|---|---|
committer | Petr Viktorin <pviktori@redhat.com> | 2014-04-18 14:59:20 +0200 |
commit | 8b6dc819d5e1a74935b270593ca0e6d3f5e5d9d7 (patch) | |
tree | cfb83a335b08e993bf719d90ecdbe64fcf617faa | |
parent | 4314d02fbf9ef1cb9543ecf76a8d22e79d250214 (diff) | |
download | freeipa-8b6dc819d5e1a74935b270593ca0e6d3f5e5d9d7.tar.gz freeipa-8b6dc819d5e1a74935b270593ca0e6d3f5e5d9d7.tar.xz freeipa-8b6dc819d5e1a74935b270593ca0e6d3f5e5d9d7.zip |
Support API version-specific RPC marshalling.
Reviewed-By: Tomas Babej <tbabej@redhat.com>
-rw-r--r-- | ipalib/rpc.py | 24 | ||||
-rw-r--r-- | ipaserver/rpcserver.py | 21 | ||||
-rw-r--r-- | ipatests/test_ipalib/test_rpc.py | 29 |
3 files changed, 42 insertions, 32 deletions
diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 2b47d1c0e..73ae115b3 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -63,6 +63,7 @@ from ipapython.nsslib import NSSHTTPS, NSSConnection from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT_EXPIRED, \ KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE from ipapython.dn import DN +from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES COOKIE_NAME = 'ipa_session' KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME @@ -126,7 +127,7 @@ def delete_persistent_client_session_data(principal): # kernel_keyring only raises ValueError (why??) kernel_keyring.del_key(keyname) -def xml_wrap(value): +def xml_wrap(value, version): """ Wrap all ``str`` in ``xmlrpclib.Binary``. @@ -148,10 +149,10 @@ def xml_wrap(value): :param value: The simple scalar or simple compound value to wrap. """ if type(value) in (list, tuple): - return tuple(xml_wrap(v) for v in value) + return tuple(xml_wrap(v, version) for v in value) if isinstance(value, dict): return dict( - (k, xml_wrap(v)) for (k, v) in value.iteritems() + (k, xml_wrap(v, version)) for (k, v) in value.iteritems() ) if type(value) is str: return Binary(value) @@ -199,7 +200,8 @@ def xml_unwrap(value, encoding='UTF-8'): return value -def xml_dumps(params, methodname=None, methodresponse=False, encoding='UTF-8'): +def xml_dumps(params, version, methodname=None, methodresponse=False, + encoding='UTF-8'): """ Encode an XML-RPC data packet, transparently wraping ``params``. @@ -219,7 +221,7 @@ def xml_dumps(params, methodname=None, methodresponse=False, encoding='UTF-8'): :param encoding: The Unicode encoding to use (defaults to ``'UTF-8'``). """ if type(params) is tuple: - params = xml_wrap(params) + params = xml_wrap(params, version) else: assert isinstance(params, Fault) return dumps(params, @@ -230,7 +232,7 @@ def xml_dumps(params, methodname=None, methodresponse=False, encoding='UTF-8'): ) -def json_encode_binary(val): +def json_encode_binary(val, version): ''' JSON cannot encode binary values. We encode binary values in Python str objects and text in Python unicode objects. In order to allow a binary @@ -253,10 +255,10 @@ def json_encode_binary(val): if isinstance(val, dict): new_dict = {} for k, v in val.items(): - new_dict[k] = json_encode_binary(v) + new_dict[k] = json_encode_binary(v, version) return new_dict elif isinstance(val, (list, tuple)): - new_list = [json_encode_binary(v) for v in val] + new_list = [json_encode_binary(v, version) for v in val] return new_list elif isinstance(val, str): return {'__base64__': base64.b64encode(val)} @@ -894,7 +896,8 @@ class xmlclient(RPCClient): env_rpc_uri_key = 'xmlrpc_uri' def _call_command(self, command, params): - params = xml_wrap(params) + version = params[1].get('version', VERSION_WITHOUT_CAPABILITIES) + params = xml_wrap(params, version) result = command(*params) return xml_unwrap(result) @@ -918,11 +921,12 @@ class JSONServerProxy(object): def __request(self, name, args): payload = {'method': unicode(name), 'params': args, 'id': 0} + version = args[1].get('version', VERSION_WITHOUT_CAPABILITIES) response = self.__transport.request( self.__host, self.__handler, - json.dumps(json_encode_binary(payload)), + json.dumps(json_encode_binary(payload, version)), verbose=self.__verbose, ) diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index c05740ded..821eed226 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -31,7 +31,8 @@ import urlparse import time import json -from ipalib import plugable, capabilities, errors +from ipalib import plugable, errors +from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES from ipalib.backend import Executioner from ipalib.errors import (PublicError, InternalError, CommandError, JSONError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, @@ -378,7 +379,8 @@ class WSGIExecutioner(Executioner): name, type(e).__name__) - return self.marshal(result, error, _id) + version = options.get('version', VERSION_WITHOUT_CAPABILITIES) + return self.marshal(result, error, _id, version) def simple_unmarshal(self, environ): name = environ['PATH_INFO'].strip('/') @@ -415,7 +417,8 @@ class WSGIExecutioner(Executioner): def unmarshal(self, data): raise NotImplementedError('%s.unmarshal()' % self.fullname) - def marshal(self, result, error, _id=None): + def marshal(self, result, error, _id=None, + version=VERSION_WITHOUT_CAPABILITIES): raise NotImplementedError('%s.marshal()' % self.fullname) @@ -439,7 +442,8 @@ class jsonserver(WSGIExecutioner, HTTP_Status): response = super(jsonserver, self).__call__(environ, start_response) return response - def marshal(self, result, error, _id=None): + def marshal(self, result, error, _id=None, + version=VERSION_WITHOUT_CAPABILITIES): if error: assert isinstance(error, PublicError) error = dict( @@ -455,7 +459,7 @@ class jsonserver(WSGIExecutioner, HTTP_Status): principal=unicode(principal), version=unicode(VERSION), ) - response = json_encode_binary(response) + response = json_encode_binary(response, version) return json.dumps(response, sort_keys=True, indent=4) def unmarshal(self, data): @@ -712,10 +716,11 @@ class xmlserver(KerberosWSGIExecutioner): # Keep backwards compatibility with client containing # bug https://fedorahosted.org/freeipa/ticket/3294: # If `version` is not given in XML-RPC, assume an old version - options['version'] = capabilities.VERSION_WITHOUT_CAPABILITIES + options['version'] = VERSION_WITHOUT_CAPABILITIES return (name, args, options, None) - def marshal(self, result, error, _id=None): + def marshal(self, result, error, _id=None, + version=VERSION_WITHOUT_CAPABILITIES): if error: self.debug('response: %s: %s', error.__class__.__name__, str(error)) response = Fault(error.errno, error.strerror) @@ -723,7 +728,7 @@ class xmlserver(KerberosWSGIExecutioner): if isinstance(result, dict): self.debug('response: entries returned %d', result.get('count', 1)) response = (result,) - return xml_dumps(response, methodresponse=True) + return xml_dumps(response, version, methodresponse=True) class jsonserver_session(jsonserver, KerberosSession): diff --git a/ipatests/test_ipalib/test_rpc.py b/ipatests/test_ipalib/test_rpc.py index 0369d04c4..64e5f5e33 100644 --- a/ipatests/test_ipalib/test_rpc.py +++ b/ipatests/test_ipalib/test_rpc.py @@ -29,6 +29,7 @@ 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, api, request +from ipapython.version import API_VERSION std_compound = (binary_bytes, utf8_bytes, unicode_str) @@ -43,7 +44,7 @@ def dump_n_load(value): def round_trip(value): return rpc.xml_unwrap( - dump_n_load(rpc.xml_wrap(value)) + dump_n_load(rpc.xml_wrap(value, API_VERSION)) ) @@ -90,15 +91,15 @@ def test_xml_wrap(): Test the `ipalib.rpc.xml_wrap` function. """ f = rpc.xml_wrap - assert f([]) == tuple() - assert f({}) == dict() - b = f('hello') + assert f([], API_VERSION) == tuple() + assert f({}, API_VERSION) == dict() + b = f('hello', API_VERSION) assert isinstance(b, Binary) assert b.data == 'hello' - u = f(u'hello') + u = f(u'hello', API_VERSION) assert type(u) is unicode assert u == u'hello' - value = f([dict(one=False, two=u'hello'), None, 'hello']) + value = f([dict(one=False, two=u'hello'), None, 'hello'], API_VERSION) def test_xml_unwrap(): @@ -127,14 +128,14 @@ def test_xml_dumps(): params = (binary_bytes, utf8_bytes, unicode_str, None) # Test serializing an RPC request: - data = f(params, 'the_method') + data = f(params, API_VERSION, 'the_method') (p, m) = loads(data) assert_equal(m, u'the_method') assert type(p) is tuple assert rpc.xml_unwrap(p) == params # Test serializing an RPC response: - data = f((params,), methodresponse=True) + data = f((params,), API_VERSION, methodresponse=True) (tup, m) = loads(data) assert m is None assert len(tup) == 1 @@ -143,7 +144,7 @@ def test_xml_dumps(): # Test serializing an RPC response containing a Fault: fault = Fault(69, unicode_str) - data = f(fault, methodresponse=True) + data = f(fault, API_VERSION, methodresponse=True) e = raises(Fault, loads, data) assert e.faultCode == 69 assert_equal(e.faultString, unicode_str) @@ -155,7 +156,7 @@ def test_xml_loads(): """ f = rpc.xml_loads params = (binary_bytes, utf8_bytes, unicode_str, None) - wrapped = rpc.xml_wrap(params) + wrapped = rpc.xml_wrap(params, API_VERSION) # Test un-serializing an RPC request: data = dumps(wrapped, 'the_method', allow_none=True) @@ -210,19 +211,19 @@ class test_xmlclient(PluginTester): conn = DummyClass( ( 'user_add', - rpc.xml_wrap(params), + rpc.xml_wrap(params, API_VERSION), {}, - rpc.xml_wrap(result), + rpc.xml_wrap(result, API_VERSION), ), ( 'user_add', - rpc.xml_wrap(params), + rpc.xml_wrap(params, API_VERSION), {}, Fault(3007, u"'four' is required"), # RequirementError ), ( 'user_add', - rpc.xml_wrap(params), + rpc.xml_wrap(params, API_VERSION), {}, Fault(700, u'no such error'), # There is no error 700 ), |