#!/usr/bin/python
#
# arch-tag: authentication and authorization module
# Time-stamp: <2005-03-09 20:06:00 mike>
#
import binascii
import sha
import time
import os

__version__ = '1.5'

class dbfile(object):
    """Class to model the user and load database files. This class provides
    a dictionary interface to the file contents and insures that the files
    are re-read if they are updated.

    See SecurityManager documentation for a description of the file formats.
    
    """
    def __init__(self, filename, multival=0):
        self.filename = filename
        self.multi = multival
        self.db = {}
        self.t = 0
        self._read(force=1)

    def _read(self, force=0):
        try:
            if os.path.getmtime(self.filename) >= self.t or force:
                fd = open(self.filename, 'r')
                self.db = {}
                self.t = time.time()
                if self.multi:
                    for line in fd.readlines():
                        key,val = line.strip().split(':')
                        self.db[key] = val.split(',')
                else:
                    for line in fd.readlines():
                        key,val = line.strip().split(':')
                        self.db[key] = val
                fd.close()
        except (OSError, IOError):
            pass

    def __getitem__(self, name):
        self._read()
        return self.db[name]
    
class SecurityManager(object):
    """
    Class to manage user authentication and access to the Loads. An instance
    is initialized with two arguments:

      userfile - name of the username/password file
      loadfile - name of the username/load file.

    File Formats
    ------------

    userfile
    ^^^^^^^^
      Ascii file. Each line of the file contains two colon (':') separated
      fields. The first field is the username and the second is the password.
      **Note that the password is stored in plaintext therefore this file
      should only be readable by the userid associated with the PMACS
      server process**

    loadfile
    ^^^^^^^^
      Ascii file. Each line of the file contains two colon separated fields.
      The first field is the load ID formated as follows:

         TYPE/PORT/VOLTAGE   (e.g. internal/10/v48 or external/5/v400)

      The second field is a comma separted list of usernames.
      
    """
    def __init__(self, userfile, loadfile):
        self.pwdb = dbfile(userfile)
        self.loaddb = dbfile(loadfile, multival=1)

    def authenticate(self, ticket):
        """Authenticate a SOAP request using the Web Services Security
        UsernameToken method.  Returns the tuple (username, 'ok') if
        successful and (username, reason) if the request cannot be
        authenticated.

        Input:
          ticket  -  dictionary containing the UsernameToken
        """
        try:
            user = ticket['Username']
            digest = binascii.a2b_base64(ticket['Password'])
            nonce = binascii.a2b_base64(ticket['Nonce'])
            pword = self.pwdb[user]
            check = sha.new(nonce + ticket['Created'] + pword).digest()
            if check == digest:
                return (user, 'ok')
            else:
                return (user, 'invalid password')
        except KeyError:
            return (user, 'invalid token')

    def authorize(self, user, load_id):
        """Check whether the (authenticated) user is authorized to change
        the state of the named load.  Return 1 if authorized, 0 if not.
        """
        try:
            if user in self.loaddb[load_id]:
                return 1
        except KeyError:
            pass
        if user == 'admin':
            return 1
        else:
            return 0
    
