rpc: Make compression function module-global
[ganeti-local] / lib / serializer.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Serializer abstraction module
22
23 This module introduces a simple abstraction over the serialization
24 backend (currently json).
25
26 """
27 # pylint: disable=C0103
28
29 # C0103: Invalid name, since pylint doesn't see that Dump points to a
30 # function and not a constant
31
32 import re
33
34 # Python 2.6 and above contain a JSON module based on simplejson. Unfortunately
35 # the standard library version is significantly slower than the external
36 # module. While it should be better from at least Python 3.2 on (see Python
37 # issue 7451), for now Ganeti needs to work well with older Python versions
38 # too.
39 import simplejson
40
41 from ganeti import errors
42 from ganeti import utils
43
44
45 _JSON_INDENT = 2
46
47 _RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE)
48
49
50 def _GetJsonDumpers(_encoder_class=simplejson.JSONEncoder):
51   """Returns two JSON functions to serialize data.
52
53   @rtype: (callable, callable)
54   @return: The function to generate a compact form of JSON and another one to
55            generate a more readable, indented form of JSON (if supported)
56
57   """
58   plain_encoder = _encoder_class(sort_keys=True)
59
60   # Check whether the simplejson module supports indentation
61   try:
62     indent_encoder = _encoder_class(indent=_JSON_INDENT, sort_keys=True)
63   except TypeError:
64     # Indentation not supported
65     indent_encoder = plain_encoder
66
67   return (plain_encoder.encode, indent_encoder.encode)
68
69
70 (_DumpJson, _DumpJsonIndent) = _GetJsonDumpers()
71
72
73 def DumpJson(data, indent=True):
74   """Serialize a given object.
75
76   @param data: the data to serialize
77   @param indent: whether to indent output (depends on simplejson version)
78
79   @return: the string representation of data
80
81   """
82   if indent:
83     fn = _DumpJsonIndent
84   else:
85     fn = _DumpJson
86
87   txt = _RE_EOLSP.sub("", fn(data))
88   if not txt.endswith("\n"):
89     txt += "\n"
90
91   return txt
92
93
94 def LoadJson(txt):
95   """Unserialize data from a string.
96
97   @param txt: the json-encoded form
98
99   @return: the original data
100
101   """
102   return simplejson.loads(txt)
103
104
105 def DumpSignedJson(data, key, salt=None, key_selector=None):
106   """Serialize a given object and authenticate it.
107
108   @param data: the data to serialize
109   @param key: shared hmac key
110   @param key_selector: name/id that identifies the key (in case there are
111     multiple keys in use, e.g. in a multi-cluster environment)
112   @return: the string representation of data signed by the hmac key
113
114   """
115   txt = DumpJson(data, indent=False)
116   if salt is None:
117     salt = ""
118   signed_dict = {
119     "msg": txt,
120     "salt": salt,
121     }
122
123   if key_selector:
124     signed_dict["key_selector"] = key_selector
125   else:
126     key_selector = ""
127
128   signed_dict["hmac"] = utils.Sha1Hmac(key, txt, salt=salt + key_selector)
129
130   return DumpJson(signed_dict, indent=False)
131
132
133 def LoadSignedJson(txt, key):
134   """Verify that a given message was signed with the given key, and load it.
135
136   @param txt: json-encoded hmac-signed message
137   @param key: the shared hmac key or a callable taking one argument (the key
138     selector), which returns the hmac key belonging to the key selector.
139     Typical usage is to pass a reference to the get method of a dict.
140   @rtype: tuple of original data, string
141   @return: original data, salt
142   @raises errors.SignatureError: if the message signature doesn't verify
143
144   """
145   signed_dict = LoadJson(txt)
146   if not isinstance(signed_dict, dict):
147     raise errors.SignatureError("Invalid external message")
148   try:
149     msg = signed_dict["msg"]
150     salt = signed_dict["salt"]
151     hmac_sign = signed_dict["hmac"]
152   except KeyError:
153     raise errors.SignatureError("Invalid external message")
154
155   if callable(key):
156     # pylint: disable=E1103
157     key_selector = signed_dict.get("key_selector", None)
158     hmac_key = key(key_selector)
159     if not hmac_key:
160       raise errors.SignatureError("No key with key selector '%s' found" %
161                                   key_selector)
162   else:
163     key_selector = ""
164     hmac_key = key
165
166   if not utils.VerifySha1Hmac(hmac_key, msg, hmac_sign,
167                               salt=salt + key_selector):
168     raise errors.SignatureError("Invalid Signature")
169
170   return LoadJson(msg), salt
171
172
173 Dump = DumpJson
174 Load = LoadJson
175 DumpSigned = DumpSignedJson
176 LoadSigned = LoadSignedJson