#!/usr/bin/env python

import ldap
import re

from dm.common.utility.loggingManager import LoggingManager
from dm.common.exceptions.configurationError import ConfigurationError
from dm.common.exceptions.internalError import InternalError
from dm.common.exceptions.objectNotFound import ObjectNotFound
from dm.common.exceptions.authenticationError import AuthenticationError
from dm.common.exceptions.communicationError import CommunicationError
from dm.common.exceptions.dmException import DmException

class LdapClient:

    LDAP_ERROR_REGEX_LIST = [
        (re.compile('.*No such object.*'),ObjectNotFound),
        (re.compile('.*'),DmException)
    ]

    def __init__(self, serverUrl, adminDn, adminPasswordFile):
        self.serverUrl = serverUrl
        self.adminDn = adminDn
        self.adminPassword = open(adminPasswordFile, 'r').readline().strip()
        if not self.adminPassword:
            raise ConfigurationError('LDAP password could not be found in %s file' % adminPasswordFile) 
        self.ldapClient = None

    # Decorator for all LDAP transactions
    @classmethod
    def executeLdapCall(cls, func):
        def ldapCall(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
                return result
            except DmException, ex:
                raise
            except Exception, ex:
                error = str(ex)
                for (pattern,dmExClass) in LdapClient.LDAP_ERROR_REGEX_LIST:
                    if pattern.match(error):
                        raise dmExClass(exception=ex)
                raise DmException(exception=ex)
        return ldapCall

    @classmethod
    def getLogger(cls):
        logger = LoggingManager.getInstance().getLogger(cls.__name__)
        return logger

    def getLdapClient(self):
        if self.ldapClient is not None:
            try:
                self.ldapClient.simple_bind_s(self.adminDn, self.adminPassword)
            except Exception, ex:
                self.getLogger().error('Invalidating LDAP client due to error: %s' % ex)
                self.unbind(self.ldapClient)
                self.ldapClient = None

        if not self.ldapClient:
            self.ldapClient = self.bind(self.serverUrl, self.adminDn, self.adminPassword)
        return self.ldapClient

    @classmethod 
    def bind(cls, serverUrl, adminDn, adminPassword):
        try:
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
            ldapClient = ldap.initialize(serverUrl)
            ldapClient.set_option(ldap.OPT_REFERRALS,0)
            ldapClient.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
            ldapClient.simple_bind_s(adminDn, adminPassword)
            cls.getLogger().debug('Successful binding with LDAP DN: %s' % adminDn)
            return ldapClient
        except ldap.INVALID_CREDENTIALS, ex:
            ldapClient.unbind()
            raise AuthenticationError('Invalid LDAP credentials for admin user %s' % adminDn)
        except ldap.SERVER_DOWN, ex:
            raise CommunicationError('Cannot reach LDAP server %s' % serverUrl)
        except Exception, ex:
            raise InternalError('Unrecognized exception while binding to LDAP server %s: %s' % (serverUrl, ex))

    @classmethod 
    def unbind(cls, ldapClient):
        try:
            ldapClient.unbind()
        except Exception, ex:
            cls.getLogger().error('Could not unbind LDAP client: %s' % ex)

#######################################################################
# Testing.

if __name__ == '__main__':
    pass