This commit was manufactured by cvs2svn to create branch
[plcapi.git] / PLC / Auth.py
index c186028..a062b3a 100644 (file)
@@ -4,7 +4,7 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: Auth.py,v 1.7 2006/11/08 22:53:30 mlhuang Exp $
+# $Id: Auth.py,v 1.15 2007/02/01 22:28:48 mlhuang Exp $
 #
 
 import crypt
 #
 
 import crypt
@@ -17,29 +17,102 @@ from PLC.Parameter import Parameter, Mixed
 from PLC.Persons import Persons
 from PLC.Nodes import Node, Nodes
 from PLC.Sessions import Session, Sessions
 from PLC.Persons import Persons
 from PLC.Nodes import Node, Nodes
 from PLC.Sessions import Session, Sessions
+from PLC.Peers import Peer, Peers
+from PLC.Boot import notify_owners
 
 class Auth(Parameter):
     """
     Base class for all API authentication methods, as well as a class
 
 class Auth(Parameter):
     """
     Base class for all API authentication methods, as well as a class
-    that can be used to represent Mixed(SessionAuth(), PasswordAuth()),
-    i.e. the two principal API authentication methods.
+    that can be used to represent all supported API authentication
+    methods.
     """
 
     """
 
-    def __init__(self, auth = {}):
+    def __init__(self, auth = None):
+        if auth is None:
+            auth = {'AuthMethod': Parameter(str, "Authentication method to use", optional = False)}
         Parameter.__init__(self, auth, "API authentication structure")
 
     def check(self, method, auth, *args):
         Parameter.__init__(self, auth, "API authentication structure")
 
     def check(self, method, auth, *args):
-        method.type_check("auth", auth,
-                          Mixed(SessionAuth(), PasswordAuth()),
-                          (auth,) + args)
+        # Method.type_check() should have checked that all of the
+        # mandatory fields were present.
+        assert 'AuthMethod' in auth
+
+        if auth['AuthMethod'] == "session":
+            expected = SessionAuth()
+        elif auth['AuthMethod'] == "password" or \
+             auth['AuthMethod'] == "capability":
+            expected = PasswordAuth()
+        elif auth['AuthMethod'] == "gpg":
+            expected = GPGAuth()
+        elif auth['AuthMethod'] == "hmac":
+            expected = BootAuth()
+        elif auth['AuthMethod'] == "anonymous":
+            expected = AnonymousAuth()
+        else:
+            raise PLCInvalidArgument("must be 'session', 'password', 'gpg', 'hmac', or 'anonymous'", "AuthMethod")
 
 
-class SessionAuth(Auth):
+        # Re-check using the specified authentication method
+        method.type_check("auth", auth, expected, (auth,) + args)
+
+class GPGAuth(Auth):
     """
     Proposed PlanetLab federation authentication structure.
     """
 
     def __init__(self):
         Auth.__init__(self, {
     """
     Proposed PlanetLab federation authentication structure.
     """
 
     def __init__(self):
         Auth.__init__(self, {
+            'AuthMethod': Parameter(str, "Authentication method to use, always 'gpg'", optional = False),
+            'name': Parameter(str, "Peer or user name", optional = False),
+            'signature': Parameter(str, "Message signature", optional = False)
+            })
+
+    def check(self, method, auth, *args):
+        try:
+            peers = Peers(method.api, [auth['name']])
+            if peers:
+                if 'peer' not in method.roles:
+                    raise PLCAuthenticationFailure, "Not allowed to call method"
+
+                method.caller = peer = peers[0]
+                keys = [peer['key']]
+            else:
+                persons = Persons(method.api, {'email': auth['name'], 'enabled': True, 'peer_id': None})
+                if not persons:
+                    raise PLCAuthenticationFailure, "No such user '%s'" % auth['name']
+
+                if not set(person['roles']).intersection(method.roles):
+                    raise PLCAuthenticationFailure, "Not allowed to call method"
+
+                method.caller = person = persons[0]
+                keys = Keys(method.api, {'key_id': person['key_ids'], 'key_type': "gpg", 'peer_id': None})
+
+            if not keys:
+                raise PLCAuthenticationFailure, "No GPG key on record for peer or user '%s'"
+
+            for key in keys:
+                try:
+                    from PLC.GPG import gpg_verify
+                    gpg_verify(args, key, auth['signature'], method.name)
+                    return
+                except PLCAuthenticationFailure, fault:
+                    pass
+
+            raise fault
+
+        except PLCAuthenticationFailure, fault:
+            # XXX Send e-mail
+            raise fault
+
+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)
             })
 
             'session': Parameter(str, "Session key", optional = False)
             })
 
@@ -56,7 +129,7 @@ class SessionAuth(Auth):
 
         try:
             if session['node_id'] is not None:
 
         try:
             if session['node_id'] is not None:
-                nodes = Nodes(method.api, [session['node_id']])
+                nodes = Nodes(method.api, {'node_id': session['node_id'], 'peer_id': None})
                 if not nodes:
                     raise PLCAuthenticationFailure, "No such node"
                 node = nodes[0]
                 if not nodes:
                     raise PLCAuthenticationFailure, "No such node"
                 node = nodes[0]
@@ -67,7 +140,7 @@ class SessionAuth(Auth):
                 method.caller = node
 
             elif session['person_id'] is not None and session['expires'] > time.time():
                 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})
+                persons = Persons(method.api, {'person_id': session['person_id'], 'enabled': True, 'peer_id': None})
                 if not persons:
                     raise PLCAuthenticationFailure, "No such account"
                 person = persons[0]
                 if not persons:
                     raise PLCAuthenticationFailure, "No such account"
                 person = persons[0]
@@ -128,8 +201,11 @@ class BootAuth(Auth):
         # mandatory fields were present.
         assert auth.has_key('node_id')
 
         # mandatory fields were present.
         assert auth.has_key('node_id')
 
+        if 'node' not in method.roles:
+            raise PLCAuthenticationFailure, "Not allowed to call method"
+
         try:
         try:
-            nodes = Nodes(method.api, [auth['node_id']])
+            nodes = Nodes(method.api, {'node_id': auth['node_id'], 'peer_id': None})
             if not nodes:
                 raise PLCAuthenticationFailure, "No such node"
             node = nodes[0]
             if not nodes:
                 raise PLCAuthenticationFailure, "No such node"
             node = nodes[0]
@@ -161,7 +237,7 @@ class BootAuth(Auth):
                     raise PLCAuthenticationFailure, "Cannot determine IP address of requestor"
 
                 if nodenetwork['ip'] != method.source[0]:
                     raise PLCAuthenticationFailure, "Cannot determine IP address of requestor"
 
                 if nodenetwork['ip'] != method.source[0]:
-                    raise PLCAuthenticationFailure, "Requestor IP %s does not mach node IP %s" % \
+                    raise PLCAuthenticationFailure, "Requestor IP %s does not match node IP %s" % \
                           (method.source[0], nodenetwork['ip'])
             else:
                 raise PLCAuthenticationFailure, "No node key or boot nonce"
                           (method.source[0], nodenetwork['ip'])
             else:
                 raise PLCAuthenticationFailure, "No node key or boot nonce"
@@ -181,7 +257,8 @@ class BootAuth(Auth):
             method.caller = node
 
         except PLCAuthenticationFailure, fault:
             method.caller = node
 
         except PLCAuthenticationFailure, fault:
-            # XXX Send e-mail
+            if nodes:
+                notify_owners(method, node, 'authfail', include_pis = True, include_techs = True, fault = fault)
             raise fault
 
 class AnonymousAuth(Auth):
             raise fault
 
 class AnonymousAuth(Auth):
@@ -195,8 +272,10 @@ class AnonymousAuth(Auth):
             })
 
     def check(self, method, auth, *args):
             })
 
     def check(self, method, auth, *args):
-        # Sure, dude, whatever
-        return True
+        if 'anonymous' not in method.roles:
+            raise PLCAuthenticationFailure, "Not allowed to call method anonymously"
+
+        method.caller = None
 
 class PasswordAuth(Auth):
     """
 
 class PasswordAuth(Auth):
     """
@@ -205,7 +284,7 @@ class PasswordAuth(Auth):
 
     def __init__(self):
         Auth.__init__(self, {
 
     def __init__(self):
         Auth.__init__(self, {
-            'AuthMethod': Parameter(str, "Authentication method to use, typically 'password'", optional = False),
+            'AuthMethod': Parameter(str, "Authentication method to use, always 'password' or 'capability'", optional = False),
             'Username': Parameter(str, "PlanetLab username, typically an e-mail address", optional = False),
             'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
             })
             'Username': Parameter(str, "PlanetLab username, typically an e-mail address", optional = False),
             'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
             })
@@ -216,7 +295,7 @@ class PasswordAuth(Auth):
         assert auth.has_key('Username')
 
         # Get record (must be enabled)
         assert auth.has_key('Username')
 
         # Get record (must be enabled)
-        persons = Persons(method.api, {'email': auth['Username'], 'enabled': True})
+        persons = Persons(method.api, {'email': auth['Username'].lower(), 'enabled': True, 'peer_id': None})
         if len(persons) != 1:
             raise PLCAuthenticationFailure, "No such account"
 
         if len(persons) != 1:
             raise PLCAuthenticationFailure, "No such account"
 
@@ -247,6 +326,6 @@ class PasswordAuth(Auth):
                 raise PLCAuthenticationFailure, "Password verification failed"
 
         if not set(person['roles']).intersection(method.roles):
                 raise PLCAuthenticationFailure, "Password verification failed"
 
         if not set(person['roles']).intersection(method.roles):
-            raise PLCAuthenticationFailure, "Not allowed to call method"
+           raise PLCAuthenticationFailure, "Not allowed to call method"
 
         method.caller = person
 
         method.caller = person