Implement GetRoles()
authorTony Mack <tmack@paris.CS.Princeton.EDU>
Fri, 14 Sep 2012 17:32:55 +0000 (13:32 -0400)
committerTony Mack <tmack@paris.CS.Princeton.EDU>
Fri, 14 Sep 2012 17:32:55 +0000 (13:32 -0400)
PLC/Auth.py
PLC/Method.py
PLC/Methods/GetRoles.py
PLC/NovaObject.py [new file with mode: 0644]
PLC/Roles.py
PLC/Shell.py

index 3be444b..6723572 100644 (file)
@@ -1,28 +1,7 @@
 #
-# PLCAPI authentication parameters
-#
-# Mark Huang <mlhuang@cs.princeton.edu>
-# Copyright (C) 2006 The Trustees of Princeton University
-#
-
-import crypt
-try:
-    from hashlib import sha1 as sha
-except ImportError:
-    import sha
-import hmac
-import time
 import os
 
-from PLC.Faults import *
-from PLC.Parameter import Parameter, Mixed
-from PLC.Persons import Persons
-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.Keys import Keys
-from PLC.Boot import notify_owners
+from PLC.Parameter import Parameter
 
 class Auth(Parameter):
     """
@@ -36,226 +15,6 @@ class Auth(Parameter):
             auth = {'AuthMethod': Parameter(str, "Authentication method to use", optional = False)}
         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
-
-        if auth['AuthMethod'] in auth_methods:
-            expected = auth_methods[auth['AuthMethod']]()
-        else:
-            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)
-
-class GPGAuth(Auth):
-    """
-    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, "GPGAuth: Not allowed to call method, missing 'peer' role"
-
-                method.caller = peer = peers[0]
-                gpg_keys = [ peer['key'] ]
-            else:
-                persons = Persons(method.api, {'email': auth['name'], 'enabled': True, 'peer_id': None})
-                if not persons:
-                    raise PLCAuthenticationFailure, "GPGAuth: No such user '%s'" % auth['name']
-
-                method.caller = person = persons[0]
-                if not set(person['roles']).intersection(method.roles):
-                    raise PLCAuthenticationFailure, "GPGAuth: Not allowed to call method, missing role"
-
-                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 gpg_keys:
-                raise PLCAuthenticationFailure, "GPGAuth: No GPG key on record for peer or user '%s'"%auth['name']
-
-            for gpg_key in gpg_keys:
-                try:
-                    from PLC.GPG import gpg_verify
-                    gpg_verify(args, gpg_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)
-            })
-
-    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
-
 class PasswordAuth(Auth):
     """
     PlanetLab version 3.x password authentication structure.
@@ -264,61 +23,10 @@ class PasswordAuth(Auth):
     def __init__(self):
         Auth.__init__(self, {
             '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),
+            'Username': Parameter(str, "Username, typically an e-mail address", optional = False),
             'AuthString': Parameter(str, "Authentication string, typically a password", optional = False),
+            'Tenant': Parameter(str, "User Tenant", 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('Username')
-
-        # Get record (must be enabled)
-        persons = Persons(method.api, {'email': auth['Username'].lower(), 'enabled': True, 'peer_id': None})
-        if len(persons) != 1:
-            raise PLCAuthenticationFailure, "PasswordAuth: No such account"
-
-        person = persons[0]
-
-        if auth['Username'] == method.api.config.PLC_API_MAINTENANCE_USER:
-            # "Capability" authentication, whatever the hell that was
-            # supposed to mean. It really means, login as the special
-            # "maintenance user" using password authentication. Can
-            # 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, "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:
-                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)
-            password = person['password']
-
-            # Protect against blank passwords in the DB
-            if password is None or password[:12] == "" or \
-               crypt.crypt(plaintext, password[:12]) != password:
-                raise PLCAuthenticationFailure, "PasswordAuth: Password verification failed"
-
-        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']))
-            raise PLCAuthenticationFailure, "PasswordAuth: missing role, %s -- %s"%(method_message,person_message)
-
-        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)
index 3d7383c..827df54 100644 (file)
@@ -64,8 +64,8 @@ w
     def __init__(self, api,caller=None):
         self.name = self.__class__.__name__
         self.api = api
-        self.admin_shell = NovaShell()
-        self.client_shell = None
+        self.api.admin_shell = NovaShell()
+        self.api.client_shell = None
 
         if caller: 
             # let a method call another one by propagating its caller
@@ -321,13 +321,13 @@ w
     def authenticate(self, auth):
 
         # establish nova connection
-        self.client_shell = NovaShell(user=auth['Username'],
+        self.api.client_shell = NovaShell(user=auth['Username'],
                                   password=auth['AuthString'],
                                   tenant=auth['Tenant'])
-        self.client_shell.authenticate()
-        self.caller = self.client_shell.keystone.users.find(name=auth['Username'])
-        self.caller_tenant = self.client_shell.keystone.tenants.find(name=auth['Tenant'])
-        caller_roles = self.client_shell.keystone.roles.roles_for_user(self.caller, self.caller_tenant)
+        self.api.client_shell.authenticate()
+        self.caller = self.api.client_shell.keystone.users.find(name=auth['Username'])
+        self.caller_tenant = self.api.client_shell.keystone.tenants.find(name=auth['Tenant'])
+        caller_roles = self.api.client_shell.keystone.roles.roles_for_user(self.caller, self.caller_tenant)
         role_names = [role.name for role in caller_roles] 
         if not set(role_names).intersection(self.roles):
             method_message="method %s has roles [%s]"%(self.name,','.join(self.roles))
index fb905e5..78432d9 100644 (file)
@@ -1,7 +1,8 @@
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
-from PLC.Roles import Role, Roles
+from PLC.Roles import Role 
+from PLC.NovaObject import NovaObject
 from PLC.Auth import Auth
 
 class GetRoles(Method):
@@ -9,7 +10,7 @@ class GetRoles(Method):
     Get an array of structs containing details about all roles.
     """
 
-    roles = ['admin', 'pi', 'user', 'tech', 'node']
+    roles = ['admin', 'pi', 'user', 'tech']
 
     accepts = [
         Auth()
@@ -18,4 +19,6 @@ class GetRoles(Method):
     returns = [Role.fields]
 
     def call(self, auth):
-        return Roles(self.api)
+        roles = self.api.client_shell.keystone.roles.findall()
+        result = [NovaObject.object_to_dict(role, Role.fields) for role in roles] 
+        return result
diff --git a/PLC/NovaObject.py b/PLC/NovaObject.py
new file mode 100644 (file)
index 0000000..8a20927
--- /dev/null
@@ -0,0 +1,12 @@
+class NovaObject:
+
+    # Set this to a dict of the valid fields of this object and their
+    # types. Not all fields (e.g., joined fields) may be updated via
+    # sync().
+    @staticmethod 
+    def object_to_dict(object, fields):
+        d = {}
+        for field in fields:
+            if hasattr(object, field):
+                d[field] = getattr(object, field)
+        return d    
index fcc05f4..869eb96 100644 (file)
@@ -1,78 +1,16 @@
-#
-# Functions for interacting with the roles table in the database
-#
-# Mark Huang <mlhuang@cs.princeton.edu>
-# Copyright (C) 2006 The Trustees of Princeton University
-#
 
-from types import StringTypes
-from PLC.Faults import *
 from PLC.Parameter import Parameter
-from PLC.Filter import Filter
-from PLC.Table import Row, Table
 
-class Role(Row):
+class Role:
     """
     Representation of a row in the roles table. To use,
     instantiate with a dict of values.
     """
 
-    table_name = 'roles'
-    primary_key = 'role_id'
-    join_tables = ['person_role', 'tag_type_role' ]
     fields = {
-        'role_id': Parameter(int, "Role identifier"),
+        'role_id': Parameter(str, "Role identifier"),
         'name': Parameter(str, "Role", max = 100),
         }
 
-    def validate_role_id(self, role_id):
-        # Make sure role does not already exist
-        conflicts = Roles(self.api, [role_id])
-        if conflicts:
-            raise PLCInvalidArgument, "Role ID already in use"
-
-        return role_id
-
-    def validate_name(self, name):
-        # Make sure name is not blank
-        if not len(name):
-            raise PLCInvalidArgument, "Role must be specified"
-
-        # Make sure role does not already exist
-        conflicts = Roles(self.api, [name])
-        if conflicts:
-            raise PLCInvalidArgument, "Role name already in use"
-
-        return name
-
-class Roles(Table):
-    """
-    Representation of the roles table in the database.
-    """
-
-    def __init__(self, api, role_filter = None):
-        Table.__init__(self, api, Role)
-
-        sql = "SELECT %s FROM roles WHERE True" % \
-              ", ".join(Role.fields)
-
-        if role_filter is not None:
-            if isinstance(role_filter, (list, tuple, set)):
-                # Separate the list into integers and strings
-                ints = filter(lambda x: isinstance(x, (int, long)), role_filter)
-                strs = filter(lambda x: isinstance(x, StringTypes), role_filter)
-                role_filter = Filter(Role.fields, {'role_id': ints, 'name': strs})
-                sql += " AND (%s) %s" % role_filter.sql(api, "OR")
-            elif isinstance(role_filter, dict):
-                role_filter = Filter(Role.fields, role_filter)
-                sql += " AND (%s) %s" % role_filter.sql(api, "AND")
-            elif isinstance(role_filter, (int, long)):
-                role_filter = Filter(Role.fields, {'role_id': role_filter})
-                sql += " AND (%s) %s" % role_filter.sql(api, "AND")
-            elif isinstance(role_filter, StringTypes):
-                role_filter = Filter(Role.fields, {'name': role_filter})
-                sql += " AND (%s) %s" % role_filter.sql(api, "AND")
-            else:
-                raise PLCInvalidArgument, "Wrong role filter %r"%role_filter
-
-        self.selectall(sql)
+class Roles(list):
+    pass
index fb401d3..d6c3a54 100644 (file)
@@ -144,6 +144,10 @@ class Shell:
             if role is not None:
                 self.auth['Role'] = role
 
+        self.load_methods()
+    def load_methods(self):
+
         for method in PLC.API.PLCAPI.all_methods:
             api_function = self.api.callable(method)