#-*- coding: utf-8 -*-

# Copyright 2008-2012 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import ldap
from utils.common import _error

class ldapFun(_error):
    '''Объект для работы с LDAP сервером

        подключение к серверу и поиск данных
    '''
    def __init__(self, dnUser, password, host="localhost"):
        self.conLdap = False
        # Получаем соединение с LDAP
        try:
            self.conLdap = self.__ldapConnect(dnUser, password, host)
        except ldap.LDAPError, e:
            self.setError(e[0]['desc'])

    def __ldapConnect(self, dnUser, password, host):
        """Соединение с LDAP сервером"""
        conLdap = ldap.initialize('ldap://%s'%host)
        conLdap.simple_bind_s(dnUser, password)
        return conLdap

    def ldapSearch(self,baseDN, searchScope, searchFilter, retrieveAttributes):
        try:
            ldap_result_id = self.conLdap.search(baseDN, searchScope,
                                                searchFilter,
                                                retrieveAttributes)
            result_set = []
            while 1:
                result_type, result_data = self.conLdap.result(ldap_result_id,
                                                               0)
                if (result_data == []):
                    break
                else:
                    if result_type == ldap.RES_SEARCH_ENTRY:
                        result_set.append(result_data)
        except ldap.NO_SUCH_OBJECT:
            return []
        except:
            return False
        return result_set

class ldapUser(_error):
    """Получение данных для пользователя из LDAP"""
    # Данные из /etc/ldap.conf
    _dictData = {}
    # Объект LDAP
    ldapObj = False
    # Подключение к LDAP
    conLdap = False

    def addDN(self, *arg):
        """
        Append text DN elements
        """
        DNs = []
        for dn in arg:
            if dn:
                DNs.append(dn)
        return ','.join(DNs)

    def getDataInLdapConf(self, bindData=True, cache=True):
        """Получение данных из /etc/ldap.conf"""
        data = [("host",'host'),
                ("usersDN",'nss_base_passwd'),
                ("groupsDN",'nss_base_group')]
        if bindData:
            data += [("bindDn",'binddn'), ("bindPw",'bindpw')]
        namesData = map(lambda x: x[0], data)
        # Данные из кеша, если он есть
        if cache and self._dictData and\
            set(namesData)<=set(self._dictData.keys()):
            return self._dictData
        fileName = "/etc/ldap.conf"
        getStrList = lambda x: reduce(lambda x,y: [x,y.upper()],([x]*2))
        workData = map(lambda x: (x[0],getStrList(x[1]),len(x[1])), data)
        dictData = {}
        splList = (" ", "\t")
        try:
            for line in open(fileName):
                for name, keys, lenKey in workData:
                    if not name in dictData.keys() and\
                        filter(lambda x: line.startswith(x), keys) and\
                        len(line)>lenKey:
                        spl = line[lenKey]
                        if spl in splList:
                            if not name in dictData:
                                dictData[name] = []
                            if name in  ("usersDN", "groupsDN"):
                                dictData[name].append(line.rpartition(spl)[2].\
                                                      partition('?')[0].strip())
                            else:
                                dictData[name].append(line.rpartition(spl)[2].\
                                                      strip())
        except:
            #self.setError(_("Can not open %s")%fileName)
            return False
        if set(dictData.keys()) == set(namesData):
            # Кеширование данных
            if cache:
                self._dictData.clear()
                self._dictData.update(dictData)
            return dictData
        else:
            return {}

    def getBindConnectData(self):
        """Получение данных для соединения с LDAP bind пользователем"""
        configdata = self.getDataInLdapConf()
        if configdata:
            bindDn = configdata["bindDn"][0]
            bindPw = configdata["bindPw"][0]
            host = configdata["host"][0]
            return bindDn, bindPw, host
        return False

    def getUsersDN(self):
        """Получение DN пользователей"""
        configdata = self.getDataInLdapConf(bindData=False)
        if configdata:
            usersDN = self._dictData["usersDN"][0]
            return usersDN
        return False

    def getHost(self):
        """Получение LDAP хоста"""
        configdata = self.getDataInLdapConf(bindData=False)
        if configdata:
            host = configdata["host"][0]
            return host
        return False

    def getGroupsDN(self):
        """Получение списка DN групп"""
        configdata = self.getDataInLdapConf(bindData=False)
        if configdata:
            groupsDNs = self._dictData["groupsDN"]
            return groupsDNs
        return False

    def connectLdap(self):
        """
        Connect to LDAP
        """
        connectData = self.getBindConnectData()
        if not connectData:
            return {}
        bindDn, bindPw, host = connectData
        usersDN = self.getUsersDN()
        # Соединяемся с LDAP
        return self.ldapConnect(bindDn, bindPw, host)

    def getUserLdapInfo(self, userName, shadowAttr=False):
        """Выдаем информацию о пользователе из LDAP"""
        if not self.connectLdap():
            return False
        usersDN = self.getUsersDN()
        groupsDNs = self.getGroupsDN()
        searchUser = self.ldapObj.ldapSearch(usersDN, ldap.SCOPE_ONELEVEL,
                                             "uid=%s" %userName, None)
        if not searchUser:
            return False
        convertDict = {'uid':('user','uidNumber'),
                       'gid':('user','gidNumber'),
                       'fullName':('user','cn'),
                       'mail':('user','mail'),
                       'jid':('user','registeredAddress'),
                       'home':('user','homeDirectory'),
                       'group':('group','cn')}
        if shadowAttr:
            convertDict.update({'loginShell':('user','loginShell'),
                                'shadowLastChange':('user','shadowLastChange'),
                                'shadowMin':('user','shadowMin'),
                                'shadowMax':('user','shadowMax'),
                                'shadowWarning':('user','shadowWarning'),
                                'shadowExpire':('user','shadowExpire'),
                                'shadowFlag':('user','shadowFlag'),
                                'groups':('group','memberUid')})
        listUserAttr = map(lambda x: x[0],
                           filter(lambda x: x[1][0]=="user",
                                  convertDict.items()))
        listGroupAttr = map(lambda x: x[0],
                           filter(lambda x: x[1][0]=="group",
                                  convertDict.items()))
        uid = ""
        gid = ""
        dictOut = {}
        for dictAttr in listUserAttr:
            ldapAttr = convertDict[dictAttr][1]
            if ldapAttr in searchUser[0][0][1]:
                dictOut[dictAttr] = searchUser[0][0][1][ldapAttr][0]
            else:
                dictOut[dictAttr] = ""
            if dictAttr == 'uid':
                uid = dictOut[dictAttr]
            if dictAttr == 'gid':
                gid = dictOut[dictAttr]
        if gid:
            for dictAttr in listGroupAttr:
                searchGroup = []
                ldapAttr = convertDict[dictAttr][1]
                if dictAttr == "group":
                    for groupDN in groupsDNs:
                        searchGroup = self.ldapObj.ldapSearch(groupDN,
                                                    ldap.SCOPE_ONELEVEL,
                                                    "gidNumber=%s" %gid, None)
                        if searchGroup:
                            break
                    if searchGroup:
                        if ldapAttr in searchGroup[0][0][1]:
                            dictOut[dictAttr]=searchGroup[0][0][1][ldapAttr][0]
                        else:
                            dictOut[dictAttr] = ""
                    else:
                        dictOut[dictAttr] = ""
                elif dictAttr == "groups":
                    userGroupsData = []
                    for groupDN in groupsDNs:
                        searchGroup = self.ldapObj.ldapSearch(groupDN,
                                                   ldap.SCOPE_ONELEVEL,
                                                   "%s=%s" %(ldapAttr,userName),
                                                   ["cn","gidNumber"])
                        if searchGroup:
                            userGroupsData += map(lambda x: (x[0][1]["cn"][0],
                                                       x[0][1]["gidNumber"][0]),
                                              searchGroup)
                    dictOut[dictAttr] = userGroupsData
        if uid and gid:
            return dictOut
        else:
            return {}

    def ldapConnect(self, bindDn, bindPw, host):
        """Подключение к LDAP"""
        if not self.ldapObj:
            ldapObj = ldapFun(bindDn, bindPw, host)
            if ldapObj.getError():
                return False
            # Устанавливаем у объекта соединение и объект LDAP функций
            self.ldapObj = ldapObj
            self.conLdap = ldapObj.conLdap
        return True
