remove simplejson dependency
[plcapi.git] / PLC / Auth.py
index e4a5a11..3be444b 100644 (file)
@@ -4,13 +4,15 @@
 # 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$
-#
 
 import crypt
 
 import crypt
-import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    import sha
 import hmac
 import time
 import hmac
 import time
+import os
 
 from PLC.Faults import *
 from PLC.Parameter import Parameter, Mixed
 
 from PLC.Faults import *
 from PLC.Parameter import Parameter, Mixed
@@ -19,6 +21,7 @@ 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
 
 class Auth(Parameter):
 from PLC.Boot import notify_owners
 
 class Auth(Parameter):
@@ -34,23 +37,17 @@ class Auth(Parameter):
         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):
+        global auth_methods
+
         # Method.type_check() should have checked that all of the
         # mandatory fields were present.
         assert 'AuthMethod' in auth
 
         # 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()
+        if auth['AuthMethod'] in auth_methods:
+            expected = auth_methods[auth['AuthMethod']]()
         else:
         else:
-            raise PLCInvalidArgument("must be 'session', 'password', 'gpg', 'hmac', or 'anonymous'", "AuthMethod")
+            sm = "'" + "', '".join(auth_methods.keys()) + "'"
+            raise PLCInvalidArgument("must be " + sm, "AuthMethod")
 
         # Re-check using the specified authentication method
         method.type_check("auth", auth, expected, (auth,) + args)
 
         # Re-check using the specified authentication method
         method.type_check("auth", auth, expected, (auth,) + args)
@@ -72,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
@@ -125,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()
@@ -203,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)
@@ -250,10 +227,11 @@ class BootAuth(Auth):
 
             # We encode in UTF-8 before calculating the HMAC, which is
             # an 8-bit algorithm.
 
             # We encode in UTF-8 before calculating the HMAC, which is
             # an 8-bit algorithm.
-            digest = hmac.new(key, msg.encode('utf-8'), sha).hexdigest()
+            # 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']:
 
             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
 
@@ -274,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
 
@@ -298,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]
 
@@ -309,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)
@@ -324,9 +302,32 @@ 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)
+except OSError, e:
+    extensions = []
+for extension in extensions:
+    if extension.startswith("."):
+        continue
+    if not extension.endswith(".py"):
+        continue
+    execfile("%s/%s" % (path, extension))
+del extensions