Statistics
| Branch: | Tag: | Revision:

root / lib / serializer.py @ 4884f187

History | View | Annotate | Download (8.6 kB)

1 8d14b30d Iustin Pop
#
2 8d14b30d Iustin Pop
#
3 8d14b30d Iustin Pop
4 32542155 Jose A. Lopes
# Copyright (C) 2007, 2008, 2014 Google Inc.
5 8d14b30d Iustin Pop
#
6 8d14b30d Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 8d14b30d Iustin Pop
# it under the terms of the GNU General Public License as published by
8 8d14b30d Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 8d14b30d Iustin Pop
# (at your option) any later version.
10 8d14b30d Iustin Pop
#
11 8d14b30d Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 8d14b30d Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 8d14b30d Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 8d14b30d Iustin Pop
# General Public License for more details.
15 8d14b30d Iustin Pop
#
16 8d14b30d Iustin Pop
# You should have received a copy of the GNU General Public License
17 8d14b30d Iustin Pop
# along with this program; if not, write to the Free Software
18 8d14b30d Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 8d14b30d Iustin Pop
# 02110-1301, USA.
20 8d14b30d Iustin Pop
21 8d14b30d Iustin Pop
"""Serializer abstraction module
22 8d14b30d Iustin Pop

23 8d14b30d Iustin Pop
This module introduces a simple abstraction over the serialization
24 8d14b30d Iustin Pop
backend (currently json).
25 8d14b30d Iustin Pop

26 8d14b30d Iustin Pop
"""
27 b459a848 Andrea Spadaccini
# pylint: disable=C0103
28 fe267188 Iustin Pop
29 fe267188 Iustin Pop
# C0103: Invalid name, since pylint doesn't see that Dump points to a
30 fe267188 Iustin Pop
# function and not a constant
31 8d14b30d Iustin Pop
32 8d14b30d Iustin Pop
import re
33 f4a2f532 Guido Trotter
34 b76fd1bc Michael Hanselmann
# Python 2.6 and above contain a JSON module based on simplejson. Unfortunately
35 b76fd1bc Michael Hanselmann
# the standard library version is significantly slower than the external
36 b76fd1bc Michael Hanselmann
# module. While it should be better from at least Python 3.2 on (see Python
37 b76fd1bc Michael Hanselmann
# issue 7451), for now Ganeti needs to work well with older Python versions
38 b76fd1bc Michael Hanselmann
# too.
39 b76fd1bc Michael Hanselmann
import simplejson
40 b76fd1bc Michael Hanselmann
41 f4a2f532 Guido Trotter
from ganeti import errors
42 615aaaba Michael Hanselmann
from ganeti import utils
43 071448fb Michael Hanselmann
44 d357f531 Michael Hanselmann
45 d0c8c01d Iustin Pop
_RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE)
46 8d14b30d Iustin Pop
47 8d14b30d Iustin Pop
48 a182a3ed Michael Hanselmann
def DumpJson(data):
49 8d14b30d Iustin Pop
  """Serialize a given object.
50 8d14b30d Iustin Pop

51 c41eea6e Iustin Pop
  @param data: the data to serialize
52 c41eea6e Iustin Pop
  @return: the string representation of data
53 071448fb Michael Hanselmann

54 8d14b30d Iustin Pop
  """
55 a182a3ed Michael Hanselmann
  encoded = simplejson.dumps(data)
56 071448fb Michael Hanselmann
57 a182a3ed Michael Hanselmann
  txt = _RE_EOLSP.sub("", encoded)
58 d0c8c01d Iustin Pop
  if not txt.endswith("\n"):
59 d0c8c01d Iustin Pop
    txt += "\n"
60 d357f531 Michael Hanselmann
61 8d14b30d Iustin Pop
  return txt
62 8d14b30d Iustin Pop
63 8d14b30d Iustin Pop
64 228538cf Michael Hanselmann
def LoadJson(txt):
65 8d14b30d Iustin Pop
  """Unserialize data from a string.
66 8d14b30d Iustin Pop

67 c41eea6e Iustin Pop
  @param txt: the json-encoded form
68 c41eea6e Iustin Pop
  @return: the original data
69 32542155 Jose A. Lopes
  @raise JSONDecodeError: if L{txt} is not a valid JSON document
70 c41eea6e Iustin Pop

71 8d14b30d Iustin Pop
  """
72 cdeda3b6 Michael Hanselmann
  return simplejson.loads(txt)
73 228538cf Michael Hanselmann
74 228538cf Michael Hanselmann
75 72ca1dcb Balazs Lecz
def DumpSignedJson(data, key, salt=None, key_selector=None):
76 f4a2f532 Guido Trotter
  """Serialize a given object and authenticate it.
77 f4a2f532 Guido Trotter

78 f4a2f532 Guido Trotter
  @param data: the data to serialize
79 f4a2f532 Guido Trotter
  @param key: shared hmac key
80 72ca1dcb Balazs Lecz
  @param key_selector: name/id that identifies the key (in case there are
81 72ca1dcb Balazs Lecz
    multiple keys in use, e.g. in a multi-cluster environment)
82 f4a2f532 Guido Trotter
  @return: the string representation of data signed by the hmac key
83 f4a2f532 Guido Trotter

84 f4a2f532 Guido Trotter
  """
85 a182a3ed Michael Hanselmann
  txt = DumpJson(data)
86 f4a2f532 Guido Trotter
  if salt is None:
87 d0c8c01d Iustin Pop
    salt = ""
88 f4a2f532 Guido Trotter
  signed_dict = {
89 d0c8c01d Iustin Pop
    "msg": txt,
90 d0c8c01d Iustin Pop
    "salt": salt,
91 3718bf6d Michael Hanselmann
    }
92 3718bf6d Michael Hanselmann
93 72ca1dcb Balazs Lecz
  if key_selector:
94 72ca1dcb Balazs Lecz
    signed_dict["key_selector"] = key_selector
95 72ca1dcb Balazs Lecz
  else:
96 3718bf6d Michael Hanselmann
    key_selector = ""
97 3718bf6d Michael Hanselmann
98 3718bf6d Michael Hanselmann
  signed_dict["hmac"] = utils.Sha1Hmac(key, txt, salt=salt + key_selector)
99 72ca1dcb Balazs Lecz
100 a182a3ed Michael Hanselmann
  return DumpJson(signed_dict)
101 f4a2f532 Guido Trotter
102 f4a2f532 Guido Trotter
103 4e9dac14 Guido Trotter
def LoadSignedJson(txt, key):
104 f4a2f532 Guido Trotter
  """Verify that a given message was signed with the given key, and load it.
105 f4a2f532 Guido Trotter

106 f4a2f532 Guido Trotter
  @param txt: json-encoded hmac-signed message
107 72ca1dcb Balazs Lecz
  @param key: the shared hmac key or a callable taking one argument (the key
108 72ca1dcb Balazs Lecz
    selector), which returns the hmac key belonging to the key selector.
109 72ca1dcb Balazs Lecz
    Typical usage is to pass a reference to the get method of a dict.
110 f4a2f532 Guido Trotter
  @rtype: tuple of original data, string
111 4e9dac14 Guido Trotter
  @return: original data, salt
112 f4a2f532 Guido Trotter
  @raises errors.SignatureError: if the message signature doesn't verify
113 f4a2f532 Guido Trotter

114 f4a2f532 Guido Trotter
  """
115 f4a2f532 Guido Trotter
  signed_dict = LoadJson(txt)
116 f4a2f532 Guido Trotter
  if not isinstance(signed_dict, dict):
117 d0c8c01d Iustin Pop
    raise errors.SignatureError("Invalid external message")
118 f4a2f532 Guido Trotter
  try:
119 d0c8c01d Iustin Pop
    msg = signed_dict["msg"]
120 d0c8c01d Iustin Pop
    salt = signed_dict["salt"]
121 d0c8c01d Iustin Pop
    hmac_sign = signed_dict["hmac"]
122 f4a2f532 Guido Trotter
  except KeyError:
123 d0c8c01d Iustin Pop
    raise errors.SignatureError("Invalid external message")
124 f4a2f532 Guido Trotter
125 72ca1dcb Balazs Lecz
  if callable(key):
126 b459a848 Andrea Spadaccini
    # pylint: disable=E1103
127 72ca1dcb Balazs Lecz
    key_selector = signed_dict.get("key_selector", None)
128 72ca1dcb Balazs Lecz
    hmac_key = key(key_selector)
129 72ca1dcb Balazs Lecz
    if not hmac_key:
130 72ca1dcb Balazs Lecz
      raise errors.SignatureError("No key with key selector '%s' found" %
131 72ca1dcb Balazs Lecz
                                  key_selector)
132 72ca1dcb Balazs Lecz
  else:
133 72ca1dcb Balazs Lecz
    key_selector = ""
134 72ca1dcb Balazs Lecz
    hmac_key = key
135 72ca1dcb Balazs Lecz
136 3718bf6d Michael Hanselmann
  if not utils.VerifySha1Hmac(hmac_key, msg, hmac_sign,
137 3718bf6d Michael Hanselmann
                              salt=salt + key_selector):
138 d0c8c01d Iustin Pop
    raise errors.SignatureError("Invalid Signature")
139 f4a2f532 Guido Trotter
140 4e9dac14 Guido Trotter
  return LoadJson(msg), salt
141 f4a2f532 Guido Trotter
142 f4a2f532 Guido Trotter
143 5d630c22 Michael Hanselmann
def LoadAndVerifyJson(raw, verify_fn):
144 5d630c22 Michael Hanselmann
  """Parses and verifies JSON data.
145 5d630c22 Michael Hanselmann

146 5d630c22 Michael Hanselmann
  @type raw: string
147 5d630c22 Michael Hanselmann
  @param raw: Input data in JSON format
148 5d630c22 Michael Hanselmann
  @type verify_fn: callable
149 5d630c22 Michael Hanselmann
  @param verify_fn: Verification function, usually from L{ht}
150 5d630c22 Michael Hanselmann
  @return: De-serialized data
151 5d630c22 Michael Hanselmann

152 5d630c22 Michael Hanselmann
  """
153 5d630c22 Michael Hanselmann
  try:
154 5d630c22 Michael Hanselmann
    data = LoadJson(raw)
155 5d630c22 Michael Hanselmann
  except Exception, err:
156 5d630c22 Michael Hanselmann
    raise errors.ParseError("Can't parse input data: %s" % err)
157 5d630c22 Michael Hanselmann
158 5d630c22 Michael Hanselmann
  if not verify_fn(data):
159 5d630c22 Michael Hanselmann
    raise errors.ParseError("Data does not match expected format: %s" %
160 5d630c22 Michael Hanselmann
                            verify_fn)
161 5d630c22 Michael Hanselmann
162 5d630c22 Michael Hanselmann
  return data
163 5d630c22 Michael Hanselmann
164 5d630c22 Michael Hanselmann
165 228538cf Michael Hanselmann
Dump = DumpJson
166 228538cf Michael Hanselmann
Load = LoadJson
167 f4a2f532 Guido Trotter
DumpSigned = DumpSignedJson
168 f4a2f532 Guido Trotter
LoadSigned = LoadSignedJson
169 4884f187 Santi Raffa
170 4884f187 Santi Raffa
171 4884f187 Santi Raffa
class Private(object):
172 4884f187 Santi Raffa
  """Wrap a value so it is hard to leak it accidentally.
173 4884f187 Santi Raffa

174 4884f187 Santi Raffa
  >>> x = Private("foo")
175 4884f187 Santi Raffa
  >>> print "Value: %s" % x
176 4884f187 Santi Raffa
  Value: <redacted>
177 4884f187 Santi Raffa
  >>> print "Value: {0}".format(x)
178 4884f187 Santi Raffa
  Value: <redacted>
179 4884f187 Santi Raffa
  >>> x.upper() == "FOO"
180 4884f187 Santi Raffa
  True
181 4884f187 Santi Raffa

182 4884f187 Santi Raffa
  """
183 4884f187 Santi Raffa
  def __init__(self, item, descr="redacted"):
184 4884f187 Santi Raffa
    if isinstance(item, Private):
185 4884f187 Santi Raffa
      raise ValueError("Attempted to nest Private values.")
186 4884f187 Santi Raffa
    self._item = item
187 4884f187 Santi Raffa
    self._descr = descr
188 4884f187 Santi Raffa
189 4884f187 Santi Raffa
  def Get(self):
190 4884f187 Santi Raffa
    "Return the wrapped value."
191 4884f187 Santi Raffa
    return self._item
192 4884f187 Santi Raffa
193 4884f187 Santi Raffa
  def __str__(self):
194 4884f187 Santi Raffa
    return "<{._descr}>".format(self)
195 4884f187 Santi Raffa
196 4884f187 Santi Raffa
  def __repr__(self):
197 4884f187 Santi Raffa
    return "Private(?, descr='{._descr}')".format(self)
198 4884f187 Santi Raffa
199 4884f187 Santi Raffa
  # pylint: disable=W0212
200 4884f187 Santi Raffa
  # If it doesn't access _item directly, the call will go through __getattr__
201 4884f187 Santi Raffa
  # because this class defines __slots__ and "item" is not in it.
202 4884f187 Santi Raffa
  # OTOH, if we do add it there, we'd risk shadowing an "item" attribute.
203 4884f187 Santi Raffa
  def __eq__(self, other):
204 4884f187 Santi Raffa
    if isinstance(other, Private):
205 4884f187 Santi Raffa
      return self._item == other._item
206 4884f187 Santi Raffa
    else:
207 4884f187 Santi Raffa
      return self._item == other
208 4884f187 Santi Raffa
209 4884f187 Santi Raffa
  def __hash__(self):
210 4884f187 Santi Raffa
    return hash(self._item)
211 4884f187 Santi Raffa
212 4884f187 Santi Raffa
  def __format__(self, *_1, **_2):
213 4884f187 Santi Raffa
    return self.__str__()
214 4884f187 Santi Raffa
215 4884f187 Santi Raffa
  def __getattr__(self, attr):
216 4884f187 Santi Raffa
    return Private(getattr(self._item, attr),
217 4884f187 Santi Raffa
                   descr="%s.%s" % (self._descr, attr))
218 4884f187 Santi Raffa
219 4884f187 Santi Raffa
  def __call__(self, *args, **kwargs):
220 4884f187 Santi Raffa
    return Private(self._item(*args, **kwargs),
221 4884f187 Santi Raffa
                   descr="%s()" % self._descr)
222 4884f187 Santi Raffa
223 4884f187 Santi Raffa
  # pylint: disable=R0201
224 4884f187 Santi Raffa
  # While this could get away with being a function, it needs to be a method.
225 4884f187 Santi Raffa
  # Required by the copy.deepcopy function used by FillDict.
226 4884f187 Santi Raffa
  def __getnewargs__(self):
227 4884f187 Santi Raffa
    return tuple()
228 4884f187 Santi Raffa
229 4884f187 Santi Raffa
  def __nonzero__(self):
230 4884f187 Santi Raffa
    return bool(self._item)
231 4884f187 Santi Raffa
232 4884f187 Santi Raffa
  # Get in the way of Pickle by implementing __slots__ but not __getstate__
233 4884f187 Santi Raffa
  # ...and get a performance boost, too.
234 4884f187 Santi Raffa
  __slots__ = ["_item", "_descr"]
235 4884f187 Santi Raffa
236 4884f187 Santi Raffa
237 4884f187 Santi Raffa
class PrivateDict(dict):
238 4884f187 Santi Raffa
  """A dictionary that turns its values to private fields.
239 4884f187 Santi Raffa

240 4884f187 Santi Raffa
  >>> PrivateDict()
241 4884f187 Santi Raffa
  {}
242 4884f187 Santi Raffa
  >>> supersekkrit = PrivateDict({"password": "foobar"})
243 4884f187 Santi Raffa
  >>> print supersekkrit["password"]
244 4884f187 Santi Raffa
  <password>
245 4884f187 Santi Raffa
  >>> supersekkrit["password"].Get()
246 4884f187 Santi Raffa
  'foobar'
247 4884f187 Santi Raffa
  >>> supersekkrit.GetPrivate("password")
248 4884f187 Santi Raffa
  'foobar'
249 4884f187 Santi Raffa
  >>> supersekkrit["user"] = "eggspam"
250 4884f187 Santi Raffa
  >>> supersekkrit.Unprivate()
251 4884f187 Santi Raffa
  {'password': 'foobar', 'user': 'eggspam'}
252 4884f187 Santi Raffa

253 4884f187 Santi Raffa
  """
254 4884f187 Santi Raffa
  def __init__(self, data=None):
255 4884f187 Santi Raffa
    dict.__init__(self)
256 4884f187 Santi Raffa
    self.update(data)
257 4884f187 Santi Raffa
258 4884f187 Santi Raffa
  def __setitem__(self, item, value):
259 4884f187 Santi Raffa
    if not isinstance(value, Private):
260 4884f187 Santi Raffa
      if not isinstance(item, dict):
261 4884f187 Santi Raffa
        value = Private(value, descr=item)
262 4884f187 Santi Raffa
      else:
263 4884f187 Santi Raffa
        value = PrivateDict(value)
264 4884f187 Santi Raffa
    dict.__setitem__(self, item, value)
265 4884f187 Santi Raffa
266 4884f187 Santi Raffa
  # The actual conversion to Private containers is done by __setitem__
267 4884f187 Santi Raffa
268 4884f187 Santi Raffa
  # copied straight from cpython/Lib/UserDict.py
269 4884f187 Santi Raffa
  # Copyright (c) 2001-2014 Python Software Foundation; All Rights Reserved
270 4884f187 Santi Raffa
  def update(self, other=None, **kwargs):
271 4884f187 Santi Raffa
    # Make progressively weaker assumptions about "other"
272 4884f187 Santi Raffa
    if other is None:
273 4884f187 Santi Raffa
      pass
274 4884f187 Santi Raffa
    elif hasattr(other, 'iteritems'):  # iteritems saves memory and lookups
275 4884f187 Santi Raffa
      for k, v in other.iteritems():
276 4884f187 Santi Raffa
        self[k] = v
277 4884f187 Santi Raffa
    elif hasattr(other, 'keys'):
278 4884f187 Santi Raffa
      for k in other.keys():
279 4884f187 Santi Raffa
        self[k] = other[k]
280 4884f187 Santi Raffa
    else:
281 4884f187 Santi Raffa
      for k, v in other:
282 4884f187 Santi Raffa
        self[k] = v
283 4884f187 Santi Raffa
    if kwargs:
284 4884f187 Santi Raffa
      self.update(kwargs)
285 4884f187 Santi Raffa
286 4884f187 Santi Raffa
  def GetPrivate(self, *args):
287 4884f187 Santi Raffa
    """Like dict.get, but extracting the value in the process.
288 4884f187 Santi Raffa

289 4884f187 Santi Raffa
    Arguments are semantically equivalent to ``dict.get``
290 4884f187 Santi Raffa

291 4884f187 Santi Raffa
    >>> PrivateDict({"foo": "bar"}).GetPrivate("foo")
292 4884f187 Santi Raffa
    'bar'
293 4884f187 Santi Raffa
    >>> PrivateDict({"foo": "bar"}).GetPrivate("baz", "spam")
294 4884f187 Santi Raffa
    'spam'
295 4884f187 Santi Raffa

296 4884f187 Santi Raffa
    """
297 4884f187 Santi Raffa
    if len(args) == 1:
298 4884f187 Santi Raffa
      key, = args
299 4884f187 Santi Raffa
      return self[key].Get()
300 4884f187 Santi Raffa
    elif len(args) == 2:
301 4884f187 Santi Raffa
      key, default = args
302 4884f187 Santi Raffa
      if key not in self:
303 4884f187 Santi Raffa
        return default
304 4884f187 Santi Raffa
      else:
305 4884f187 Santi Raffa
        return self[key].Get()
306 4884f187 Santi Raffa
    else:
307 4884f187 Santi Raffa
      raise TypeError("GetPrivate() takes 2 arguments (%d given)" % len(args))
308 4884f187 Santi Raffa
309 4884f187 Santi Raffa
  def Unprivate(self):
310 4884f187 Santi Raffa
    """Turn this dict of Private() values to a dict of values.
311 4884f187 Santi Raffa

312 4884f187 Santi Raffa
    >>> PrivateDict({"foo": "bar"}).Unprivate()
313 4884f187 Santi Raffa
    {'foo': 'bar'}
314 4884f187 Santi Raffa

315 4884f187 Santi Raffa
    @rtype: dict
316 4884f187 Santi Raffa

317 4884f187 Santi Raffa
    """
318 4884f187 Santi Raffa
    returndict = {}
319 4884f187 Santi Raffa
    for key in self:
320 4884f187 Santi Raffa
      returndict[key] = self[key].Get()
321 4884f187 Santi Raffa
    return returndict