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