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 |