remove PLC.Debug.log, use PLC.Logger.logger instead
[plcapi.git] / PLC / Auth.py
index 80db3e7..3be444b 100644 (file)
@@ -4,9 +4,6 @@
 # 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$
-# $URL$
-#
 
 import crypt
 try:
 
 import crypt
 try:
@@ -24,16 +21,9 @@ from PLC.Nodes import Node, Nodes
 from PLC.Interfaces import Interface, Interfaces
 from PLC.Sessions import Session, Sessions
 from PLC.Peers import Peer, Peers
 from PLC.Interfaces import Interface, Interfaces
 from PLC.Sessions import Session, Sessions
 from PLC.Peers import Peer, Peers
+from PLC.Keys import Keys
 from PLC.Boot import notify_owners
 
 from PLC.Boot import notify_owners
 
-auth_methods = {'session': SessionAuth,
-                'password': PasswordAuth,
-                'capability': PasswordAuth,
-                'gpg': GPGAuth,
-                'hmac': BootAuth,
-                'hmac_dummybox': BootAuth,
-                'anonymous': AnonymousAuth}
-
 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
@@ -79,28 +69,29 @@ class GPGAuth(Auth):
             peers = Peers(method.api, [auth['name']])
             if peers:
                 if 'peer' not in method.roles:
             peers = Peers(method.api, [auth['name']])
             if peers:
                 if 'peer' not in method.roles:
-                    raise PLCAuthenticationFailure, "Not allowed to call method"
+                    raise PLCAuthenticationFailure, "GPGAuth: Not allowed to call method, missing 'peer' role"
 
                 method.caller = peer = peers[0]
 
                 method.caller = peer = peers[0]
-                keys = [peer['key']]
+                gpg_keys = [ peer['key'] ]
             else:
                 persons = Persons(method.api, {'email': auth['name'], 'enabled': True, 'peer_id': None})
                 if not persons:
             else:
                 persons = Persons(method.api, {'email': auth['name'], 'enabled': True, 'peer_id': None})
                 if not persons:
-                    raise PLCAuthenticationFailure, "No such user '%s'" % auth['name']
+                    raise PLCAuthenticationFailure, "GPGAuth: No such user '%s'" % auth['name']
 
 
+                method.caller = person = persons[0]
                 if not set(person['roles']).intersection(method.roles):
                 if not set(person['roles']).intersection(method.roles):
-                    raise PLCAuthenticationFailure, "Not allowed to call method"
+                    raise PLCAuthenticationFailure, "GPGAuth: Not allowed to call method, missing role"
 
 
-                method.caller = person = persons[0]
                 keys = Keys(method.api, {'key_id': person['key_ids'], 'key_type': "gpg", 'peer_id': None})
                 keys = Keys(method.api, {'key_id': person['key_ids'], 'key_type': "gpg", 'peer_id': None})
+                gpg_keys = [ key['key'] for key in keys ]
 
 
-            if not keys:
-                raise PLCAuthenticationFailure, "No GPG key on record for peer or user '%s'"
+            if not gpg_keys:
+                raise PLCAuthenticationFailure, "GPGAuth: No GPG key on record for peer or user '%s'"%auth['name']
 
 
-            for key in keys:
+            for gpg_key in gpg_keys:
                 try:
                     from PLC.GPG import gpg_verify
                 try:
                     from PLC.GPG import gpg_verify
-                    gpg_verify(args, key, auth['signature'], method.name)
+                    gpg_verify(args, gpg_key, auth['signature'], method.name)
                     return
                 except PLCAuthenticationFailure, fault:
                     pass
                     return
                 except PLCAuthenticationFailure, fault:
                     pass
@@ -132,34 +123,39 @@ class SessionAuth(Auth):
         # Get session record
         sessions = Sessions(method.api, [auth['session']], expires = None)
         if not sessions:
         # Get session record
         sessions = Sessions(method.api, [auth['session']], expires = None)
         if not sessions:
-            raise PLCAuthenticationFailure, "No such session"
+            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:
         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, "No such node"
+                    raise PLCAuthenticationFailure, "SessionAuth: No such node"
                 node = nodes[0]
 
                 if 'node' not in method.roles:
                 node = nodes[0]
 
                 if 'node' not in method.roles:
-                    raise PLCAuthenticationFailure, "Not allowed to call method"
+                    # 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:
 
                 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, "No such account"
+                    raise PLCAuthenticationFailure, "SessionAuth: No such enabled account"
                 person = persons[0]
 
                 if not set(person['roles']).intersection(method.roles):
                 person = persons[0]
 
                 if not set(person['roles']).intersection(method.roles):
-                    raise PLCPermissionDenied, "Not allowed to call method"
+                    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 = persons[0]
+                method.caller = person
 
             else:
 
             else:
-                raise PLCAuthenticationFailure, "Invalid session"
+                raise PLCAuthenticationFailure, "SessionAuth: Invalid session"
 
         except PLCAuthenticationFailure, fault:
             session.delete()
 
         except PLCAuthenticationFailure, fault:
             session.delete()
@@ -210,45 +206,19 @@ class BootAuth(Auth):
         assert auth.has_key('node_id')
 
         if 'node' not in method.roles:
         assert auth.has_key('node_id')
 
         if 'node' not in method.roles:
-            raise PLCAuthenticationFailure, "Not allowed to call method"
+            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:
 
         try:
             nodes = Nodes(method.api, {'node_id': auth['node_id'], 'peer_id': None})
             if not nodes:
-                raise PLCAuthenticationFailure, "No such node"
+                raise PLCAuthenticationFailure, "BootAuth: No such node"
             node = nodes[0]
 
             node = nodes[0]
 
+            # Jan 2011 : removing support for old boot CDs
             if node['key']:
                 key = node['key']
             if node['key']:
                 key = node['key']
-            elif node['boot_nonce']:
-                # Allow very old nodes that do not have a node key in
-                # their configuration files to use their "boot nonce"
-                # instead. The boot nonce is a random value generated
-                # by the node itself and POSTed by the Boot CD when it
-                # requests the Boot Manager. This is obviously not
-                # very secure, so we only allow it to be used if the
-                # requestor IP is the same as the IP address we have
-                # on record for the node.
-                key = node['boot_nonce']
-
-                interface = None
-                if node['interface_ids']:
-                    interfaces = Interfaces(method.api, node['interface_ids'])
-                    for interface in interfaces:
-                        if interface['is_primary']:
-                            break
-
-                if not interface or not interface['is_primary']:
-                    raise PLCAuthenticationFailure, "No primary network interface on record"
-
-                if method.source is None:
-                    raise PLCAuthenticationFailure, "Cannot determine IP address of requestor"
-
-                if interface['ip'] != method.source[0]:
-                    raise PLCAuthenticationFailure, "Requestor IP %s does not match node IP %s" % \
-                          (method.source[0], interface['ip'])
             else:
             else:
-                raise PLCAuthenticationFailure, "No node key or boot nonce"
+                raise PLCAuthenticationFailure, "BootAuth: No node key"
 
             # Yes, this is the "canonicalization" method used.
             args = self.canonicalize(args)
 
             # Yes, this is the "canonicalization" method used.
             args = self.canonicalize(args)
@@ -261,7 +231,7 @@ class BootAuth(Auth):
             digest = hmac.new(str(key), msg.encode('utf-8'), sha).hexdigest()
 
             if digest != auth['value']:
             digest = hmac.new(str(key), msg.encode('utf-8'), sha).hexdigest()
 
             if digest != auth['value']:
-                raise PLCAuthenticationFailure, "Call could not be authenticated"
+                raise PLCAuthenticationFailure, "BootAuth: Call could not be authenticated"
 
             method.caller = node
 
 
             method.caller = node
 
@@ -282,7 +252,7 @@ class AnonymousAuth(Auth):
 
     def check(self, method, auth, *args):
         if 'anonymous' not in method.roles:
 
     def check(self, method, auth, *args):
         if 'anonymous' not in method.roles:
-            raise PLCAuthenticationFailure, "Not allowed to call method anonymously"
+            raise PLCAuthenticationFailure, "AnonymousAuth: method cannot be called anonymously"
 
         method.caller = None
 
 
         method.caller = None
 
@@ -306,7 +276,7 @@ class PasswordAuth(Auth):
         # Get record (must be enabled)
         persons = Persons(method.api, {'email': auth['Username'].lower(), 'enabled': True, 'peer_id': None})
         if len(persons) != 1:
         # Get record (must be enabled)
         persons = Persons(method.api, {'email': auth['Username'].lower(), 'enabled': True, 'peer_id': None})
         if len(persons) != 1:
-            raise PLCAuthenticationFailure, "No such account"
+            raise PLCAuthenticationFailure, "PasswordAuth: No such account"
 
         person = persons[0]
 
 
         person = persons[0]
 
@@ -317,13 +287,13 @@ class PasswordAuth(Auth):
             # only be used on particular machines (those in a list).
             sources = method.api.config.PLC_API_MAINTENANCE_SOURCES.split()
             if method.source is not None and method.source[0] not in sources:
             # only be used on particular machines (those in a list).
             sources = method.api.config.PLC_API_MAINTENANCE_SOURCES.split()
             if method.source is not None and method.source[0] not in sources:
-                raise PLCAuthenticationFailure, "Not allowed to login to maintenance account"
+                raise PLCAuthenticationFailure, "PasswordAuth: Not allowed to login to maintenance account"
 
             # Not sure why this is not stored in the DB
             password = method.api.config.PLC_API_MAINTENANCE_PASSWORD
 
             if auth['AuthString'] != password:
 
             # Not sure why this is not stored in the DB
             password = method.api.config.PLC_API_MAINTENANCE_PASSWORD
 
             if auth['AuthString'] != password:
-                raise PLCAuthenticationFailure, "Maintenance account password verification failed"
+                raise PLCAuthenticationFailure, "PasswordAuth: Maintenance account password verification failed"
         else:
             # Compare encrypted plaintext against encrypted password stored in the DB
             plaintext = auth['AuthString'].encode(method.api.encoding)
         else:
             # Compare encrypted plaintext against encrypted password stored in the DB
             plaintext = auth['AuthString'].encode(method.api.encoding)
@@ -332,13 +302,23 @@ class PasswordAuth(Auth):
             # Protect against blank passwords in the DB
             if password is None or password[:12] == "" or \
                crypt.crypt(plaintext, password[:12]) != password:
             # Protect against blank passwords in the DB
             if password is None or password[:12] == "" or \
                crypt.crypt(plaintext, password[:12]) != password:
-                raise PLCAuthenticationFailure, "Password verification failed"
+                raise PLCAuthenticationFailure, "PasswordAuth: Password verification failed"
 
         if not set(person['roles']).intersection(method.roles):
 
         if not set(person['roles']).intersection(method.roles):
-            raise PLCAuthenticationFailure, "Not allowed to call method"
+            method_message="method %s has roles [%s]"%(method.name,','.join(method.roles))
+            person_message="caller %s has roles [%s]"%(person['email'],','.join(person['roles']))
+            raise PLCAuthenticationFailure, "PasswordAuth: missing role, %s -- %s"%(method_message,person_message)
 
         method.caller = person
 
 
         method.caller = person
 
+auth_methods = {'session': SessionAuth,
+                'password': PasswordAuth,
+                'capability': PasswordAuth,
+                'gpg': GPGAuth,
+                'hmac': BootAuth,
+                'hmac_dummybox': BootAuth,
+                'anonymous': AnonymousAuth}
+
 path = os.path.dirname(__file__) + "/Auth.d"
 try:
     extensions = os.listdir(path)
 path = os.path.dirname(__file__) + "/Auth.d"
 try:
     extensions = os.listdir(path)
@@ -350,5 +330,4 @@ for extension in extensions:
     if not extension.endswith(".py"):
         continue
     execfile("%s/%s" % (path, extension))
     if not extension.endswith(".py"):
         continue
     execfile("%s/%s" % (path, extension))
-del extension
 del extensions
 del extensions