- fix canonicalize() call
[nodemanager.git] / plcapi.py
1 import xmlrpclib
2 import hmac, sha
3
4 class PLCAPI:
5     """
6     Wrapper around xmlrpclib.ServerProxy to automagically add an Auth
7     struct as the first argument to every XML-RPC call. Initialize
8     auth with either:
9
10     (node_id, key) => BootAuth
11     or
12     session => SessionAuth
13
14     To authenticate using the Boot Manager authentication method, or
15     the new session-based method.
16     """
17
18     def __init__(self, uri, auth, **kwds):
19         if isinstance(auth, (tuple, list)):
20             (self.node_id, self.key) = auth
21             self.session = None
22         else:
23             self.node_id = self.key = None
24             self.session = auth
25
26         self.server = xmlrpclib.ServerProxy(uri, allow_none = 1, **kwds)
27
28     def add_auth(self, function):
29         """
30         Returns a wrapper which adds an Auth struct as the first
31         argument when the function is called.
32         """
33
34         def canonicalize(args):
35             """
36             BootAuth canonicalization method. Parameter values are
37             collected, sorted, converted to strings, then hashed with
38             the node key.
39             """
40
41             values = []
42
43             for arg in args:
44                 if isinstance(arg, list) or isinstance(arg, tuple):
45                     # The old implementation did not recursively handle
46                     # lists of lists. But neither did the old API itself.
47                     values += canonicalize(arg)
48                 elif isinstance(arg, dict):
49                     # Yes, the comments in the old implementation are
50                     # misleading. Keys of dicts are not included in the
51                     # hash.
52                     values += canonicalize(arg.values())
53                 else:
54                     # We use unicode() instead of str().
55                     values.append(unicode(arg))
56
57             return values
58
59         def wrapper(*params):
60             """
61             Adds an Auth struct as the first argument when the
62             function is called.
63             """
64
65             if self.session is not None:
66                 # Use session authentication
67                 auth = {'session': self.session}
68             else:
69                 # Yes, this is the "canonicalization" method used.
70                 args = canonicalize(params)
71                 args.sort()
72                 msg = "[" + "".join(args) + "]"
73
74                 # We encode in UTF-8 before calculating the HMAC, which is
75                 # an 8-bit algorithm.
76                 digest = hmac.new(self.key, msg.encode('utf-8'), sha).hexdigest()
77
78                 auth = {'AuthMethod': "hmac",
79                         'node_id': self.node_id,
80                         'value': digest}
81
82             # Automagically add auth struct to every call
83             params = (auth,) + params
84
85             return function(*params)
86
87         return wrapper
88
89     def __getattr__(self, methodname):
90         function = getattr(self.server, methodname)
91         return self.add_auth(function)