|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# Copyright 2008-2016 Mir Calculate. 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 sys
|
|
|
import ldap
|
|
|
from utils.common import _error
|
|
|
from collections import defaultdict
|
|
|
from ldif import LDIFParser, LDIFWriter
|
|
|
import cStringIO
|
|
|
|
|
|
from calculate.lib.cl_lang import setLocalTranslate
|
|
|
_ = lambda x: x
|
|
|
setLocalTranslate('cl_lib3', sys.modules[__name__])
|
|
|
|
|
|
|
|
|
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 сервером"""
|
|
|
con_ldap = ldap.initialize('ldap://%s' % host)
|
|
|
con_ldap.simple_bind_s(dnUser, password)
|
|
|
return con_ldap
|
|
|
|
|
|
def ldapSearch(self, base_dn, search_scope, search_filter, retrieve_attrs):
|
|
|
try:
|
|
|
ldap_result_id = self.conLdap.search(base_dn, search_scope,
|
|
|
search_filter,
|
|
|
retrieve_attrs)
|
|
|
result_set = []
|
|
|
while 1:
|
|
|
result_type, result_data = self.conLdap.result(
|
|
|
ldap_result_id, 0)
|
|
|
if isinstance(result_data, list) and not result_data:
|
|
|
break
|
|
|
else:
|
|
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
|
|
result_set.append(result_data)
|
|
|
except ldap.NO_SUCH_OBJECT:
|
|
|
return []
|
|
|
except Exception:
|
|
|
return False
|
|
|
return result_set
|
|
|
|
|
|
|
|
|
class LDAPConnectError(Exception):
|
|
|
pass
|
|
|
|
|
|
class LDAPBadSearchFilter(LDAPConnectError):
|
|
|
pass
|
|
|
|
|
|
class LDAPConnect(ldapFun):
|
|
|
"""
|
|
|
Объект работающий с исключениями
|
|
|
"""
|
|
|
|
|
|
def setError(self, message):
|
|
|
raise LDAPConnectError(message)
|
|
|
|
|
|
def ldap_search(self, base_dn, search_scope=ldap.SCOPE_BASE,
|
|
|
search_filter='(objectClass=*)',
|
|
|
retrieve_attrs=None):
|
|
|
try:
|
|
|
ldap_result_id = self.conLdap.search(
|
|
|
base_dn, search_scope, search_filter, retrieve_attrs)
|
|
|
while 1:
|
|
|
result_type, result_data = self.conLdap.result(
|
|
|
ldap_result_id, 0)
|
|
|
if isinstance(result_data, list) and not result_data:
|
|
|
break
|
|
|
else:
|
|
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
|
|
yield result_data
|
|
|
except ldap.NO_SUCH_OBJECT:
|
|
|
pass
|
|
|
except ldap.LDAPError, e:
|
|
|
error = e[0]['desc']
|
|
|
if "Bad search filter" in error:
|
|
|
raise LDAPBadSearchFilter(error)
|
|
|
raise LDAPConnectError(error)
|
|
|
except Exception as e:
|
|
|
raise LDAPConnectError(str(e))
|
|
|
|
|
|
def ldap_dump(self, fobj, base_dn, search_scope=ldap.SCOPE_SUBTREE,
|
|
|
search_filter='(objectClass=*)', retrieve_attrs=None):
|
|
|
"""
|
|
|
вернуть dump ldif
|
|
|
:param fobj:
|
|
|
:param base_dn:
|
|
|
:param search_scope:
|
|
|
:param search_filter:
|
|
|
:param retrieve_attrs:
|
|
|
:return:
|
|
|
"""
|
|
|
try:
|
|
|
dn_list = self.conLdap.search_s(
|
|
|
base_dn, search_scope, search_filter, retrieve_attrs)
|
|
|
writer = LDIFWriter(fobj)
|
|
|
for dn, f in dn_list:
|
|
|
writer.unparse(dn, f)
|
|
|
except ldap.NO_SUCH_OBJECT:
|
|
|
pass
|
|
|
except ldap.LDAPError, e:
|
|
|
error = e[0]['desc']
|
|
|
if "Bad search filter" in error:
|
|
|
raise LDAPBadSearchFilter(error)
|
|
|
raise LDAPConnectError(error)
|
|
|
except Exception as e:
|
|
|
raise LDAPConnectError(str(e))
|
|
|
|
|
|
def ldap_simple_search(self, base_dn, search_filter, attr, first=True):
|
|
|
"""
|
|
|
Простой поиск аттрибутов
|
|
|
:param base_dn:
|
|
|
:param search_filter:
|
|
|
:param attr:
|
|
|
:param first: вернуть первый из списка
|
|
|
:return:
|
|
|
"""
|
|
|
for entry in self.ldap_search(base_dn, ldap.SCOPE_ONELEVEL,
|
|
|
search_filter, [attr]):
|
|
|
attrs = entry[0][1]
|
|
|
yield attrs[attr][0]
|
|
|
|
|
|
def ldap_modify_attrs(self, base_dn, attrs):
|
|
|
"""Модифицирует аттрибуты DN"""
|
|
|
attrs = list(attrs)
|
|
|
if attrs:
|
|
|
try:
|
|
|
self.conLdap.modify_s(base_dn, attrs)
|
|
|
except ldap.LDAPError, e:
|
|
|
raise LDAPConnectError(e[0]['desc'])
|
|
|
|
|
|
def ldap_modify_dn(self, base_dn, new_dn):
|
|
|
"""
|
|
|
Изменить ветку
|
|
|
:param base_dn: предыдущее название
|
|
|
:param new_dn: новое название
|
|
|
:return:
|
|
|
"""
|
|
|
try:
|
|
|
self.conLdap.modrdn_s(base_dn, new_dn)
|
|
|
except ldap.LDAPError, e:
|
|
|
raise LDAPConnectError(e[0]['desc'])
|
|
|
|
|
|
def ldap_remove_dn(self, base_dn):
|
|
|
"""
|
|
|
Удалить указанный dn
|
|
|
:param base_dn: удаляемый dn
|
|
|
:return:
|
|
|
"""
|
|
|
try:
|
|
|
self.conLdap.delete_s(base_dn)
|
|
|
except ldap.LDAPError, e:
|
|
|
raise LDAPConnectError(e[0]['desc'])
|
|
|
|
|
|
|
|
|
|
|
|
# @adminConnectLdap
|
|
|
# def modifyElemDN(self, relDN, newFirstDn):
|
|
|
# """Изменяет основной элемент DN (uid, cn и др.)"""
|
|
|
# DN = self.addDN(relDN,self.baseDN)
|
|
|
# try:
|
|
|
# self.conLdap.modrdn_s(DN, newFirstDn)
|
|
|
# except ldap.LDAPError, e:
|
|
|
# self.printERROR(e[0]['desc'])
|
|
|
# return False
|
|
|
# return True
|
|
|
|
|
|
# @adminConnectLdap
|
|
|
# def delDN(self, relDN):
|
|
|
# """Удаляет одиночный DN"""
|
|
|
# DN = self.addDN(relDN,self.baseDN)
|
|
|
# try:
|
|
|
# self.conLdap.delete_s(DN)
|
|
|
# except ldap.LDAPError, e:
|
|
|
# self.printERROR(e[0]['desc'])
|
|
|
# return False
|
|
|
# return True
|
|
|
|
|
|
|
|
|
class ldapUser(_error):
|
|
|
"""Получение данных для пользователя из LDAP"""
|
|
|
# Данные из /etc/ldap.conf
|
|
|
_dictData = {}
|
|
|
# Объект LDAP
|
|
|
ldapObj = False
|
|
|
# Подключение к LDAP
|
|
|
conLdap = False
|
|
|
|
|
|
def addDN(self, *arg):
|
|
|
"""
|
|
|
Append text DN elements
|
|
|
"""
|
|
|
return ",".join(x for x in arg if x)
|
|
|
|
|
|
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')]
|
|
|
names_data = [x[0] for x in data]
|
|
|
# Данные из кеша, если он есть
|
|
|
if (cache and self._dictData and
|
|
|
set(names_data) <= set(self._dictData.keys())):
|
|
|
return self._dictData
|
|
|
file_name = "/etc/ldap.conf"
|
|
|
get_str_list = lambda x: reduce(lambda x, y: [x, y.upper()], ([x] * 2))
|
|
|
workdata = map(lambda x: (x[0], get_str_list(x[1]), len(x[1])), data)
|
|
|
dict_data = defaultdict(list)
|
|
|
delimeter = (" ", "\t")
|
|
|
try:
|
|
|
for line in open(file_name):
|
|
|
for name, keys, lenKey in workdata:
|
|
|
if (name not in dict_data.keys() and
|
|
|
any(line.startswith(x) for x in keys) and
|
|
|
len(line) > lenKey):
|
|
|
spl = line[lenKey]
|
|
|
if spl in delimeter:
|
|
|
param_value = line.rpartition(spl)[2]
|
|
|
if name in ("usersDN", "groupsDN"):
|
|
|
dict_data[name].append(
|
|
|
param_value.partition('?')[0].strip())
|
|
|
else:
|
|
|
dict_data[name].append(param_value.strip())
|
|
|
except Exception:
|
|
|
# self.setError(_("Can not open %s")%fileName)
|
|
|
return False
|
|
|
if set(dict_data.keys()) == set(names_data):
|
|
|
# Кеширование данных
|
|
|
if cache:
|
|
|
self._dictData.clear()
|
|
|
self._dictData.update(dict_data)
|
|
|
return dict_data
|
|
|
else:
|
|
|
return {}
|
|
|
|
|
|
def getBindConnectData(self):
|
|
|
"""Получение данных для соединения с LDAP bind пользователем"""
|
|
|
configdata = self.getDataInLdapConf()
|
|
|
if configdata:
|
|
|
bind_dn = configdata["bindDn"][0]
|
|
|
bind_pw = configdata["bindPw"][0]
|
|
|
host = configdata["host"][0]
|
|
|
return bind_dn, bind_pw, host
|
|
|
return False
|
|
|
|
|
|
def getUsersDN(self):
|
|
|
"""Получение DN пользователей"""
|
|
|
configdata = self.getDataInLdapConf(bindData=False)
|
|
|
if configdata:
|
|
|
return self._dictData["usersDN"][0]
|
|
|
return False
|
|
|
|
|
|
def getHost(self):
|
|
|
"""Получение LDAP хоста"""
|
|
|
configdata = self.getDataInLdapConf(bindData=False)
|
|
|
if configdata:
|
|
|
return configdata["host"][0]
|
|
|
return False
|
|
|
|
|
|
def getGroupsDN(self):
|
|
|
"""Получение списка DN групп"""
|
|
|
configdata = self.getDataInLdapConf(bindData=False)
|
|
|
if configdata:
|
|
|
return self._dictData["groupsDN"]
|
|
|
return False
|
|
|
|
|
|
def connectLdap(self):
|
|
|
"""
|
|
|
Connect to LDAP
|
|
|
"""
|
|
|
connectData = self.getBindConnectData()
|
|
|
if not connectData:
|
|
|
return {}
|
|
|
bindDn, bindPw, host = connectData
|
|
|
self.getUsersDN()
|
|
|
# Соединяемся с LDAP
|
|
|
return self.ldapConnect(bindDn, bindPw, host)
|
|
|
|
|
|
def getUserLdapInfo(self, user_name, shadowAttr=False):
|
|
|
"""Выдаем информацию о пользователе из LDAP"""
|
|
|
if not self.connectLdap():
|
|
|
return False
|
|
|
users_dn = self.getUsersDN()
|
|
|
groups_dn = self.getGroupsDN()
|
|
|
search_user = self.ldapObj.ldapSearch(users_dn, ldap.SCOPE_ONELEVEL,
|
|
|
"uid=%s" % user_name, None)
|
|
|
if not search_user:
|
|
|
return False
|
|
|
convert_dict = {'uid': ('user', 'uidNumber'),
|
|
|
'gid': ('user', 'gidNumber'),
|
|
|
'fullName': ('user', 'cn'),
|
|
|
'mail': ('user', 'mail'),
|
|
|
'jid': ('user', 'registeredAddress'),
|
|
|
'home': ('user', 'homeDirectory'),
|
|
|
'group': ('group', 'cn')}
|
|
|
if shadowAttr:
|
|
|
convert_dict.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')})
|
|
|
list_user_attr = [k for k, v in convert_dict.items() if v[0] == "user"]
|
|
|
list_group_attr = [k for k, v in convert_dict.items()
|
|
|
if v[0] == "group"]
|
|
|
uid = ""
|
|
|
gid = ""
|
|
|
dict_out = {}
|
|
|
for dict_attr in list_user_attr:
|
|
|
ldap_attr = convert_dict[dict_attr][1]
|
|
|
if ldap_attr in search_user[0][0][1]:
|
|
|
dict_out[dict_attr] = search_user[0][0][1][ldap_attr][0]
|
|
|
else:
|
|
|
dict_out[dict_attr] = ""
|
|
|
if dict_attr == 'uid':
|
|
|
uid = dict_out[dict_attr]
|
|
|
if dict_attr == 'gid':
|
|
|
gid = dict_out[dict_attr]
|
|
|
if gid:
|
|
|
for dict_attr in list_group_attr:
|
|
|
search_group = []
|
|
|
ldap_attr = convert_dict[dict_attr][1]
|
|
|
if dict_attr == "group":
|
|
|
for groupDN in groups_dn:
|
|
|
search_group = self.ldapObj.ldapSearch(
|
|
|
groupDN, ldap.SCOPE_ONELEVEL, "gidNumber=%s" % gid,
|
|
|
None)
|
|
|
if search_group:
|
|
|
break
|
|
|
if search_group:
|
|
|
data = search_group[0][0][1]
|
|
|
if ldap_attr in data:
|
|
|
dict_out[dict_attr] = data[ldap_attr][0]
|
|
|
else:
|
|
|
dict_out[dict_attr] = ""
|
|
|
else:
|
|
|
dict_out[dict_attr] = ""
|
|
|
elif dict_attr == "groups":
|
|
|
user_groups_data = []
|
|
|
for groupDN in groups_dn:
|
|
|
search_group = self.ldapObj.ldapSearch(
|
|
|
groupDN, ldap.SCOPE_ONELEVEL,
|
|
|
"%s=%s" % (ldap_attr, user_name),
|
|
|
["cn", "gidNumber"])
|
|
|
if search_group:
|
|
|
user_groups_data.extend(
|
|
|
[(x[0][1]["cn"][0], x[0][1]["gidNumber"][0])
|
|
|
for x in search_group])
|
|
|
dict_out[dict_attr] = user_groups_data
|
|
|
if uid and gid:
|
|
|
return dict_out
|
|
|
else:
|
|
|
return {}
|
|
|
|
|
|
def ldapConnect(self, bind_dn, bind_pw, host):
|
|
|
"""Подключение к LDAP"""
|
|
|
if not self.ldapObj:
|
|
|
ldap_obj = ldapFun(bind_dn, bind_pw, host)
|
|
|
if ldap_obj.getError():
|
|
|
ldap_obj.clearErrors()
|
|
|
return False
|
|
|
# Устанавливаем у объекта соединение и объект LDAP функций
|
|
|
self.ldapObj = ldap_obj
|
|
|
self.conLdap = ldap_obj.conLdap
|
|
|
return True
|
|
|
|
|
|
|
|
|
class LDIFError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class LDIFAdd(LDIFParser):
|
|
|
"""
|
|
|
Добавление LDIF в базу
|
|
|
"""
|
|
|
|
|
|
def __init__(self, ldif_data, ldap_connect):
|
|
|
self.ldap_connect = ldap_connect
|
|
|
LDIFParser.__init__(self, cStringIO.StringIO(ldif_data))
|
|
|
|
|
|
def handle(self, dn, entry, controls=None):
|
|
|
# (self, dn, entry, *args):
|
|
|
try:
|
|
|
self.ldap_connect.add_s(dn, entry.items())
|
|
|
except ldap.LDAPError as e:
|
|
|
raise LDIFError(e[0]['desc'])
|
|
|
except Exception as e:
|
|
|
print str(e)
|
|
|
raise LDIFError(_("Error in LDIF file"))
|