adding SessionAuth and BootAuth back
authorTony Mack <tmack@paris.CS.Princeton.EDU>
Fri, 12 Oct 2012 20:18:43 +0000 (16:18 -0400)
committerTony Mack <tmack@paris.CS.Princeton.EDU>
Fri, 12 Oct 2012 20:18:43 +0000 (16:18 -0400)
PLC/Auth.py

index 6723572..b647683 100644 (file)
@@ -2,6 +2,9 @@
 import os
 
 from PLC.Parameter import Parameter
+from PLC.Nodes import Node, Nodes
+from PLC.Sessions import Session, Sessions
+
 
 class Auth(Parameter):
     """
@@ -27,6 +30,161 @@ class PasswordAuth(Auth):
             'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
             'Tenant': Parameter(str, "User Tenant", optional = False),
             })
+
+class SessionAuth(Auth):
+    """
+    Secondary authentication method. After authenticating with a
+    primary authentication method, call GetSession() to generate a
+    session key that may be used for subsequent calls.
+    """
+
+    def __init__(self):
+        Auth.__init__(self, {
+            'AuthMethod': Parameter(str, "Authentication method to use, always 'session'", optional = False),
+            'session': Parameter(str, "Session key", optional = False)
+            })
+
+    def check(self, method, auth, *args):
+        # Method.type_check() should have checked that all of the
+        # mandatory fields were present.
+        assert auth.has_key('session')
+
+        # Get session record
+        sessions = Sessions(method.api, [auth['session']], expires = None)
+        if not sessions:
+            raise PLCAuthenticationFailure, "SessionAuth: No such session"
+        session = sessions[0]
+
+        try:
+            if session['node_id'] is not None:
+                nodes = Nodes(method.api, {'node_id': session['node_id'], 'peer_id': None})
+                if not nodes:
+                    raise PLCAuthenticationFailure, "SessionAuth: No such node"
+                node = nodes[0]
+
+                if 'node' not in method.roles:
+                    # using PermissionDenied rather than AuthenticationFailure here because
+                    # if that fails we don't want to delete the session..
+                    raise PLCPermissionDenied, "SessionAuth: Not allowed to call method %s, missing 'node' role"%method.name
+
+                method.caller = node
+
+            elif session['person_id'] is not None and session['expires'] > time.time():
+                persons = Persons(method.api, {'person_id': session['person_id'], 'enabled': True, 'peer_id': None})
+                if not persons:
+                    raise PLCAuthenticationFailure, "SessionAuth: No such enabled account"
+                person = persons[0]
+
+                if not set(person['roles']).intersection(method.roles):
+                    method_message="method %s has roles [%s]"%(method.name,','.join(method.roles))
+                    person_message="caller %s has roles [%s]"%(person['email'],','.join(person['roles']))
+                    # not PLCAuthenticationFailure b/c that would end the session..
+                    raise PLCPermissionDenied, "SessionAuth: missing role, %s -- %s"%(method_message,person_message)
+
+                method.caller = person
+
+            else:
+                raise PLCAuthenticationFailure, "SessionAuth: Invalid session"
+
+        except PLCAuthenticationFailure, fault:
+            session.delete()
+            raise fault
+
+class BootAuth(Auth):
+    """
+    PlanetLab version 3.x node authentication structure. Used by the
+    Boot Manager to make authenticated calls to the API based on a
+    unique node key or boot nonce value.
+
+    The original parameter serialization code did not define the byte
+    encoding of strings, or the string encoding of all other types. We
+    define the byte encoding to be UTF-8, and the string encoding of
+    all other types to be however Python version 2.3 unicode() encodes
+    them.
+    """
+
+    def __init__(self):
+        Auth.__init__(self, {
+            'AuthMethod': Parameter(str, "Authentication method to use, always 'hmac'", optional = False),
+            'node_id': Parameter(int, "Node identifier", optional = False),
+            'value': Parameter(str, "HMAC of node key and method call", optional = False)
+            })
+
+    def canonicalize(self, args):
+        values = []
+
+        for arg in args:
+            if isinstance(arg, list) or isinstance(arg, tuple):
+                # The old implementation did not recursively handle
+                # lists of lists. But neither did the old API itself.
+                values += self.canonicalize(arg)
+            elif isinstance(arg, dict):
+                # Yes, the comments in the old implementation are
+                # misleading. Keys of dicts are not included in the
+                # hash.
+                values += self.canonicalize(arg.values())
+            else:
+                # We use unicode() instead of str().
+                values.append(unicode(arg))
+
+        return values
+
+    def check(self, method, auth, *args):
+        # Method.type_check() should have checked that all of the
+        # mandatory fields were present.
+        assert auth.has_key('node_id')
+
+        if 'node' not in method.roles:
+            raise PLCAuthenticationFailure, "BootAuth: Not allowed to call method, missing 'node' role"
+
+        try:
+            nodes = Nodes(method.api, {'node_id': auth['node_id'], 'peer_id': None})
+            if not nodes:
+                raise PLCAuthenticationFailure, "BootAuth: No such node"
+            node = nodes[0]
+
+            # Jan 2011 : removing support for old boot CDs
+            if node['key']:
+                key = node['key']
+            else:
+                raise PLCAuthenticationFailure, "BootAuth: No node key"
+
+            # Yes, this is the "canonicalization" method used.
+            args = self.canonicalize(args)
+            args.sort()
+            msg = "[" + "".join(args) + "]"
+
+            # We encode in UTF-8 before calculating the HMAC, which is
+            # an 8-bit algorithm.
+            # python 2.6 insists on receiving a 'str' as opposed to a 'unicode'
+            digest = hmac.new(str(key), msg.encode('utf-8'), sha).hexdigest()
+
+            if digest != auth['value']:
+                raise PLCAuthenticationFailure, "BootAuth: Call could not be authenticated"
+
+            method.caller = node
+
+        except PLCAuthenticationFailure, fault:
+            if nodes:
+                notify_owners(method, node, 'authfail', include_pis = True, include_techs = True, fault = fault)
+            raise fault
+
+class AnonymousAuth(Auth):
+    """
+    PlanetLab version 3.x anonymous authentication structure.
+    """
+
+    def __init__(self):
+        Auth.__init__(self, {
+            'AuthMethod': Parameter(str, "Authentication method to use, always 'anonymous'", False),
+            })
+
+    def check(self, method, auth, *args):
+        if 'anonymous' not in method.roles:
+            raise PLCAuthenticationFailure, "AnonymousAuth: method cannot be called anonymously"
+
+        method.caller = None
+
 path = os.path.dirname(__file__) + "/Auth.d"
 try:
     extensions = os.listdir(path)