fix Slice bugs
[plcapi.git] / PLC / Auth.py
1 #
2 import os
3
4 from PLC.Parameter import Parameter
5 from PLC.Nodes import Node, Nodes
6 from PLC.Sessions import Session, Sessions
7
8
9 class Auth(Parameter):
10     """
11     Base class for all API authentication methods, as well as a class
12     that can be used to represent all supported API authentication
13     methods.
14     """
15
16     def __init__(self, auth = None):
17         if auth is None:
18             auth = {'AuthMethod': Parameter(str, "Authentication method to use", optional = False)}
19         Parameter.__init__(self, auth, "API authentication structure")
20
21 class PasswordAuth(Auth):
22     """
23     PlanetLab version 3.x password authentication structure.
24     """
25
26     def __init__(self):
27         Auth.__init__(self, {
28             'AuthMethod': Parameter(str, "Authentication method to use, always 'password' or 'capability'", optional = False),
29             'Username': Parameter(str, "Username, typically an e-mail address", optional = False),
30             'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
31             'Tenant': Parameter(str, "User Tenant", optional = False),
32             })
33
34 class SessionAuth(Auth):
35     """
36     Secondary authentication method. After authenticating with a
37     primary authentication method, call GetSession() to generate a
38     session key that may be used for subsequent calls.
39     """
40
41     def __init__(self):
42         Auth.__init__(self, {
43             'AuthMethod': Parameter(str, "Authentication method to use, always 'session'", optional = False),
44             'session': Parameter(str, "Session key", optional = False)
45             })
46
47     def check(self, method, auth, *args):
48         # Method.type_check() should have checked that all of the
49         # mandatory fields were present.
50         assert auth.has_key('session')
51
52         # Get session record
53         sessions = Sessions(method.api, [auth['session']], expires = None)
54         if not sessions:
55             raise PLCAuthenticationFailure, "SessionAuth: No such session"
56         session = sessions[0]
57
58         try:
59             if session['node_id'] is not None:
60                 nodes = Nodes(method.api, {'node_id': session['node_id'], 'peer_id': None})
61                 if not nodes:
62                     raise PLCAuthenticationFailure, "SessionAuth: No such node"
63                 node = nodes[0]
64
65                 if 'node' not in method.roles:
66                     # using PermissionDenied rather than AuthenticationFailure here because
67                     # if that fails we don't want to delete the session..
68                     raise PLCPermissionDenied, "SessionAuth: Not allowed to call method %s, missing 'node' role"%method.name
69
70                 method.caller = node
71
72             elif session['person_id'] is not None and session['expires'] > time.time():
73                 persons = Persons(method.api, {'person_id': session['person_id'], 'enabled': True, 'peer_id': None})
74                 if not persons:
75                     raise PLCAuthenticationFailure, "SessionAuth: No such enabled account"
76                 person = persons[0]
77
78                 if not set(person['roles']).intersection(method.roles):
79                     method_message="method %s has roles [%s]"%(method.name,','.join(method.roles))
80                     person_message="caller %s has roles [%s]"%(person['email'],','.join(person['roles']))
81                     # not PLCAuthenticationFailure b/c that would end the session..
82                     raise PLCPermissionDenied, "SessionAuth: missing role, %s -- %s"%(method_message,person_message)
83
84                 method.caller = person
85
86             else:
87                 raise PLCAuthenticationFailure, "SessionAuth: Invalid session"
88
89         except PLCAuthenticationFailure, fault:
90             session.delete()
91             raise fault
92
93 class BootAuth(Auth):
94     """
95     PlanetLab version 3.x node authentication structure. Used by the
96     Boot Manager to make authenticated calls to the API based on a
97     unique node key or boot nonce value.
98
99     The original parameter serialization code did not define the byte
100     encoding of strings, or the string encoding of all other types. We
101     define the byte encoding to be UTF-8, and the string encoding of
102     all other types to be however Python version 2.3 unicode() encodes
103     them.
104     """
105
106     def __init__(self):
107         Auth.__init__(self, {
108             'AuthMethod': Parameter(str, "Authentication method to use, always 'hmac'", optional = False),
109             'node_id': Parameter(int, "Node identifier", optional = False),
110             'value': Parameter(str, "HMAC of node key and method call", optional = False)
111             })
112
113     def canonicalize(self, args):
114         values = []
115
116         for arg in args:
117             if isinstance(arg, list) or isinstance(arg, tuple):
118                 # The old implementation did not recursively handle
119                 # lists of lists. But neither did the old API itself.
120                 values += self.canonicalize(arg)
121             elif isinstance(arg, dict):
122                 # Yes, the comments in the old implementation are
123                 # misleading. Keys of dicts are not included in the
124                 # hash.
125                 values += self.canonicalize(arg.values())
126             else:
127                 # We use unicode() instead of str().
128                 values.append(unicode(arg))
129
130         return values
131
132     def check(self, method, auth, *args):
133         # Method.type_check() should have checked that all of the
134         # mandatory fields were present.
135         assert auth.has_key('node_id')
136
137         if 'node' not in method.roles:
138             raise PLCAuthenticationFailure, "BootAuth: Not allowed to call method, missing 'node' role"
139
140         try:
141             nodes = Nodes(method.api, {'node_id': auth['node_id'], 'peer_id': None})
142             if not nodes:
143                 raise PLCAuthenticationFailure, "BootAuth: No such node"
144             node = nodes[0]
145
146             # Jan 2011 : removing support for old boot CDs
147             if node['key']:
148                 key = node['key']
149             else:
150                 raise PLCAuthenticationFailure, "BootAuth: No node key"
151
152             # Yes, this is the "canonicalization" method used.
153             args = self.canonicalize(args)
154             args.sort()
155             msg = "[" + "".join(args) + "]"
156
157             # We encode in UTF-8 before calculating the HMAC, which is
158             # an 8-bit algorithm.
159             # python 2.6 insists on receiving a 'str' as opposed to a 'unicode'
160             digest = hmac.new(str(key), msg.encode('utf-8'), sha).hexdigest()
161
162             if digest != auth['value']:
163                 raise PLCAuthenticationFailure, "BootAuth: Call could not be authenticated"
164
165             method.caller = node
166
167         except PLCAuthenticationFailure, fault:
168             if nodes:
169                 notify_owners(method, node, 'authfail', include_pis = True, include_techs = True, fault = fault)
170             raise fault
171
172 class AnonymousAuth(Auth):
173     """
174     PlanetLab version 3.x anonymous authentication structure.
175     """
176
177     def __init__(self):
178         Auth.__init__(self, {
179             'AuthMethod': Parameter(str, "Authentication method to use, always 'anonymous'", False),
180             })
181
182     def check(self, method, auth, *args):
183         if 'anonymous' not in method.roles:
184             raise PLCAuthenticationFailure, "AnonymousAuth: method cannot be called anonymously"
185
186         method.caller = None
187
188 path = os.path.dirname(__file__) + "/Auth.d"
189 try:
190     extensions = os.listdir(path)
191 except OSError, e:
192     extensions = []
193 for extension in extensions:
194     if extension.startswith("."):
195         continue
196     if not extension.endswith(".py"):
197         continue
198     execfile("%s/%s" % (path, extension))
199 del extensions