summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2014-03-28 09:51:10 +0100
committerPetr Viktorin <pviktori@redhat.com>2014-04-18 14:59:20 +0200
commit8b6dc819d5e1a74935b270593ca0e6d3f5e5d9d7 (patch)
treecfb83a335b08e993bf719d90ecdbe64fcf617faa
parent4314d02fbf9ef1cb9543ecf76a8d22e79d250214 (diff)
downloadfreeipa-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.py24
-rw-r--r--ipaserver/rpcserver.py21
-rw-r--r--ipatests/test_ipalib/test_rpc.py29
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
),