#!/usr/bin/env python

import os

from dm.common.exceptions.authorizationError import AuthorizationError
from dm.common.objects.dmObjectManager import DmObjectManager
from dm.common.utility.configurationManager import ConfigurationManager
from dm.common.utility.objectCache import ObjectCache
from dm.common.utility.cryptUtility import CryptUtility
from dm.common.utility.objectUtility import ObjectUtility
from dm.common.utility.singleton import Singleton

class AuthorizationPrincipalManager(DmObjectManager, Singleton):

    DEFAULT_CACHE_SIZE = 10000 # number of items
    DEFAULT_CACHE_OBJECT_LIFETIME = 3600 # seconds

    CONFIG_SECTION_NAME = 'AuthorizationPrincipalManager'
    ADMIN_ROLE_NAME_KEY = 'adminrolename'
    PRINCIPAL_RETRIEVER_KEY = 'principalretriever'
    PRINCIPAL_AUTHENTICATOR_KEY = 'principalauthenticator'

    # Singleton instance.
    __instance = None

    def __init__(self):
        if AuthorizationPrincipalManager.__instance:
            return
        AuthorizationPrincipalManager.__instance = self
        DmObjectManager.__init__(self)
        self.configurationManager = ConfigurationManager.getInstance()
        self.principalRetriever = None
        self.principalAuthenticatorList = []
        self.objectCache = ObjectCache(AuthorizationPrincipalManager.DEFAULT_CACHE_SIZE, AuthorizationPrincipalManager.DEFAULT_CACHE_OBJECT_LIFETIME)
        self.configure()

    @classmethod
    def cryptPassword(cls, cleartext):
        return CryptUtility.cryptPassword(cleartext)

    @classmethod
    def cryptPasswordWithPbkdf2(cls, cleartext):
        return CryptUtility.cryptPasswordWithPbkdf2(cleartext)

    def configure(self):
        configItems = self.configurationManager.getConfigItems(AuthorizationPrincipalManager.CONFIG_SECTION_NAME)
        self.logger.debug('Got config items: %s' % configItems)
        adminRoleName = self.configurationManager.getConfigOption(AuthorizationPrincipalManager.CONFIG_SECTION_NAME, AuthorizationPrincipalManager.ADMIN_ROLE_NAME_KEY)

        # Create principal retriever
        principalRetriever = self.configurationManager.getConfigOption(AuthorizationPrincipalManager.CONFIG_SECTION_NAME, AuthorizationPrincipalManager.PRINCIPAL_RETRIEVER_KEY)
        (moduleName,className,constructor) = self.configurationManager.getModuleClassConstructorTuple(principalRetriever, AuthorizationPrincipalManager)    
        self.logger.debug('Creating principal retriever class: %s' % className)
        self.principalRetriever = ObjectUtility.createObjectInstance(moduleName, className, constructor)
        if adminRoleName is not None:
            self.principalRetriever.setAdminRoleName(adminRoleName)
        self.logger.debug('Authorization principal retriever: %s' % (self.principalRetriever))

        # Create principal authenticators
        for (key,value) in configItems:
            if key.startswith(AuthorizationPrincipalManager.PRINCIPAL_AUTHENTICATOR_KEY):
                (moduleName,className,constructor) = self.configurationManager.getModuleClassConstructorTuple(value, AuthorizationPrincipalManager)    
                self.logger.debug('Creating principal authenticator class: %s' % className)
                principalAuthenticator = ObjectUtility.createObjectInstance(moduleName, className, constructor)
                self.addAuthorizationPrincipalAuthenticator(principalAuthenticator)
                self.logger.debug('Authorization principal authenticator: %s' % (principalAuthenticator))

    def addAuthorizationPrincipalAuthenticator(self, principalAuthenticator):
        self.principalAuthenticatorList.append(principalAuthenticator)

    def getAuthenticatedAuthorizationPrincipal(self, username, password):
        """ Get principal based on a username and password """
        # First try cache.
        #self.logger.debug('Trying username %s from the cache' % username)
        principal = None
        principalTuple = self.objectCache.get(username)
        if principalTuple is not None:
            (id, principal, updateTime, expirationTime) = principalTuple
        if principal is None:
            # Try principal retriever
            principal = self.principalRetriever.getAuthorizationPrincipal(username)

        if principal is None:
            self.logger.debug('No principal for username: %s' % username)
            return 
        
        # Try all authorization principal authenticators.
        for principalAuthenticator in self.principalAuthenticatorList:
            self.logger.debug('Attempting to authenticate %s by %s' % (username, principalAuthenticator.getName()))
            authenticatedPrincipal = principalAuthenticator.authenticatePrincipal(principal, password)
            if authenticatedPrincipal is not None:
                self.logger.debug('Adding authorization principal %s to the cache, authenticated by %s' % (principal.getName(),principalAuthenticator.getName()))
                self.objectCache.put(username, authenticatedPrincipal)
                return authenticatedPrincipal
        return None

    def removeAuthorizationPrincipal(self, username):
        """ Clear principal from the cache. """
        self.objectCache.remove(username)

#######################################################################
# Testing.
if __name__ == '__main__':
    am = AuthorizationPrincipalManager.getInstance()
    authPrincipal = am.getAuthorizationPrincipal('sveseli', 'sv')
    print 'Auth principal: ', authPrincipal