Add first implementation of generic storage unit framework
[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
28 import simplejson
29 import re
30 import hmac
31
32 from ganeti import errors
33
34 try:
35   from hashlib import sha1
36 except ImportError:
37   import sha as sha1
38
39 # Check whether the simplejson module supports indentation
40 _JSON_INDENT = 2
41 try:
42   simplejson.dumps(1, indent=_JSON_INDENT)
43 except TypeError:
44   _JSON_INDENT = None
45
46 _RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE)
47
48
49 def DumpJson(data, indent=True):
50   """Serialize a given object.
51
52   @param data: the data to serialize
53   @param indent: whether to indent output (depends on simplejson version)
54
55   @return: the string representation of data
56
57   """
58   if not indent or _JSON_INDENT is None:
59     txt = simplejson.dumps(data)
60   else:
61     txt = simplejson.dumps(data, indent=_JSON_INDENT)
62
63   txt = _RE_EOLSP.sub("", txt)
64   if not txt.endswith('\n'):
65     txt += '\n'
66   return txt
67
68
69 def LoadJson(txt):
70   """Unserialize data from a string.
71
72   @param txt: the json-encoded form
73
74   @return: the original data
75
76   """
77   return simplejson.loads(txt)
78
79
80 def DumpSignedJson(data, key, salt=None):
81   """Serialize a given object and authenticate it.
82
83   @param data: the data to serialize
84   @param key: shared hmac key
85   @return: the string representation of data signed by the hmac key
86
87   """
88   txt = DumpJson(data, indent=False)
89   if salt is None:
90     salt = ''
91   signed_dict = {
92     'msg': txt,
93     'salt': salt,
94     'hmac': hmac.new(key, salt + txt, sha1).hexdigest(),
95   }
96   return DumpJson(signed_dict)
97
98
99 def LoadSignedJson(txt, key, salt_verifier=None):
100   """Verify that a given message was signed with the given key, and load it.
101
102   @param txt: json-encoded hmac-signed message
103   @param key: shared hmac key
104   @param salt_verifier: function taking a salt as input and returning boolean
105   @rtype: tuple of original data, string
106   @return: (original data, salt)
107   @raises errors.SignatureError: if the message signature doesn't verify
108
109   """
110   signed_dict = LoadJson(txt)
111   if not isinstance(signed_dict, dict):
112     raise errors.SignatureError('Invalid external message')
113   try:
114     msg = signed_dict['msg']
115     salt = signed_dict['salt']
116     hmac_sign = signed_dict['hmac']
117   except KeyError:
118     raise errors.SignatureError('Invalid external message')
119
120   if salt and not salt_verifier:
121     raise errors.SignatureError('Salted message is not verified')
122   elif salt_verifier is not None:
123     if not salt_verifier(salt):
124       raise errors.SignatureError('Invalid salt')
125
126   if hmac.new(key, salt + msg, sha1).hexdigest() != hmac_sign:
127     raise errors.SignatureError('Invalid Signature')
128   return LoadJson(msg)
129
130
131 def SaltEqualTo(expected):
132   """Helper salt verifier function that checks for equality.
133
134   @type expected: string
135   @param expected: expected salt
136   @rtype: function
137   @return: salt verifier that returns True if the target salt is "x"
138
139   """
140   return lambda salt: salt == expected
141
142
143 def SaltIn(expected):
144   """Helper salt verifier function that checks for equality.
145
146   @type expected: collection
147   @param expected: collection of possible valid salts
148   @rtype: function
149   @return: salt verifier that returns True if the salt is in the collection
150
151   """
152   return lambda salt: salt in expected
153
154
155 def SaltInRange(min, max):
156   """Helper salt verifier function that checks for equality.
157
158   @type min: integer
159   @param min: minimum salt value
160   @type max: integer
161   @param max: maximum salt value
162   @rtype: function
163   @return: salt verifier that returns True if the salt is in the min,max range
164
165   """
166   def _CheckSaltInRange(salt):
167     try:
168       i_salt = int(salt)
169     except (TypeError, ValueError), err:
170       return False
171
172     return i_salt > min and i_salt < max
173
174   return _CheckSaltInRange
175
176
177 Dump = DumpJson
178 Load = LoadJson
179 DumpSigned = DumpSignedJson
180 LoadSigned = LoadSignedJson