diff --git a/pym/format/__init__.py b/pym/format/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/format/ldif.py b/pym/format/ldif.py new file mode 100644 index 0000000..f27cd3d --- /dev/null +++ b/pym/format/ldif.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# Copyright 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 +from calculate.lib.cl_template import TemplateFormat +from calculate.lib.cl_ldap import LDIFAdd, LDIFError + +from calculate.lib.cl_lang import setLocalTranslate + +_ = lambda x: x +setLocalTranslate('cl_ldap3', sys.modules[__name__]) + + +class ldif(TemplateFormat): + """ + LDIF формат (применяется для изменения базы данных LDAP) + """ + text = "" + + def __init__(self, text): + self.text = text + + def textToXML(self): + return self.text + + def processingFile(self, textConfigFile, rootPath=None): + """Обработка конфигурационного файла""" + ldap_connect = self.objVar.Get('ldap.cl_ldap_connect') + #print self.text + try: + LDIFAdd(self.text, ldap_connect.conLdap).parse() + except LDIFError as e: + self.setError("LDIF: %s" % str(e)) + #except ValueError as e: + # self.setError("LDIF: %s" % str(e)) + return "" diff --git a/pym/ldap/ldap.py b/pym/ldap/ldap.py index bb925c8..0e20b72 100644 --- a/pym/ldap/ldap.py +++ b/pym/ldap/ldap.py @@ -15,7 +15,13 @@ # limitations under the License. import sys +import os +from os import path from calculate.core.server.func import MethodsInterface +from calculate.server.server import Server +from calculate.lib.utils.files import listDirectory +from calculate.lib.cl_ldap import LDAPConnectError, ldap +import shutil _ = lambda x: x from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate) @@ -23,10 +29,12 @@ from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate) setLocalTranslate('cl_ldap3', sys.modules[__name__]) __ = getLazyLocalTranslate(_) + class LdapError(Exception): pass -class Ldap(MethodsInterface): + +class Ldap(Server): """Основной объект для выполнения действий связанных с настройкой LDAP сервиса @@ -36,5 +44,77 @@ class Ldap(MethodsInterface): Setup = "ldap_setup" All = (Setup,) + class Service(object): + LDAP = "slapd" + def init(self): pass + + def preconfigureTemplates(self): + """ + Выполнить шаблоны для предварительной конфигурации + """ + self.clVars.Set('cl_ldap_preconfigure_set', 'on', force=True) + try: + return self.applyTemplates("/", False, False, "/", False, True) + finally: + self.clVars.Set('cl_ldap_preconfigure_set', 'off', force=True) + + def set_ldap_connection(self, binddn, bindpw): + self.clVars.Set('ldap.cl_ldap_bind_dn', binddn, force=True) + self.clVars.Set('ldap.cl_ldap_bind_pw', bindpw, force=True) + return True + + def clear_directory(self, rmpath): + """ + Удалить каталог + """ + for fname in listDirectory(rmpath, fullPath=True): + if path.isdir(fname): + shutil.rmtree(fname) + else: + os.unlink(fname) + + def remove_ldap_db(self, database_path): + """ + Удалить базу LDAP + """ + try: + self.clear_directory(database_path) + except OSError: + raise LdapError(_("Failed to erase LDAP database")) + return True + + def remove_ldap_branch(self, branch): + """ + Удалить указанную ветку LDAP + :param branch: удаляемая ветка + :return: + """ + ldap_connect = self.clVars.Get('ldap.cl_ldap_connect') + try: + try: + dn_list = ldap_connect.conLdap.search_s(branch, + ldap.SCOPE_SUBTREE, + '(objectclass=*)', + ['']) + except ldap.NO_SUCH_OBJECT as e: + self.printWARNING(_("Unix LDAP branch not found")) + return True + except ldap.LDAPError as e: + raise LdapError("searchDN: " + e[0]['desc']) + for dn, f in sorted(dn_list, key=lambda x: len(x[0]), reverse=True): + try: + ldap_connect.conLdap.delete_s(dn) + except ldap.LDAPError, e: + raise LdapError("deleteDN: " + e[0]['desc']) + except LDAPConnectError as e: + raise LdapError(str(e)) + return True + + def save_variables(self): + for varname in ('ldap.ld_base_root',): + header, o, writename = varname.partition(".") + self.clVars.Write(writename, self.clVars.Get(varname), + header=header) + return True diff --git a/pym/ldap/tools.py b/pym/ldap/tools.py new file mode 100644 index 0000000..ef7f95e --- /dev/null +++ b/pym/ldap/tools.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright 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 hashlib +import base64 +from calculate.lib.utils.common import genpassword + +_ = lambda x: x +from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate) + +setLocalTranslate('cl_ldap3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + +ALL_BYTE_VALUES = "".join(chr(x) for x in xrange(256)) + + +class SlapPasswd(object): + """ + Получить хэш из пароля + """ + + def __init__(self, hashtype): + self._hash = hashtype + + def _default(self, secret): + pass + + def _generate_salt(self, salt_len=4, salt_chars=ALL_BYTE_VALUES): + return genpassword(salt_len, salt_chars) + + def _sha1_base64(self, secret): + return base64.b64encode(hashlib.sha1(secret).digest()) + + def _salted_sha1_base64(self, secret): + salt = self._generate_salt() + return base64.b64encode("%s%s" % ( + hashlib.sha1("%s%s" % (secret, salt)).digest(), salt)) + + def get_hash(self, secret): + return "%s%s" % ( + self._hash, + { + '{SHA}': self._sha1_base64, + '{SSHA}': self._salted_sha1_base64 + }.get(self._hash, self._default)(secret)) + diff --git a/pym/ldap/utils/cl_ldap_setup.py b/pym/ldap/utils/cl_ldap_setup.py index 7d7fade..551de36 100644 --- a/pym/ldap/utils/cl_ldap_setup.py +++ b/pym/ldap/utils/cl_ldap_setup.py @@ -19,13 +19,38 @@ from calculate.core.server.func import Action, Tasks from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate from calculate.lib.cl_template import TemplatesError from calculate.lib.utils.files import FilesError -from ..ldap import LdapError +from calculate.lib.datavars import VariableError +from calculate.server.variables.action import Actions +from ..ldap import LdapError, Ldap +from calculate.server.server import ServerError _ = lambda x: x setLocalTranslate('cl_ldap3', sys.modules[__name__]) __ = getLazyLocalTranslate(_) +class MetaTasks(object): + def __init__(self, obj): + self.obj = obj + + def ldif_task(self, ldap_params, action): + return [ + {'name': 'set_ldif', + 'method': '%s.set_ldap_connection(%s)' % (self.obj, ldap_params) + }, + {'name': 'ldif_action', + 'method': '%s.set_server_action("%s")' % (self.obj, Actions.Ldif), + }, + {'name': 'ldif', + 'method': '%s.applyTemplates(install.cl_source,' + 'False,True,None,False,True)' % self.obj, + }, + {'name': 'ldif_action', + 'method': '%s.set_server_action("%s")' % (self.obj, action), + }, + ] + + class ClLdapSetupAction(Action): """ Действие обновление конфигурационных файлов @@ -33,15 +58,92 @@ class ClLdapSetupAction(Action): # ошибки, которые отображаются без подробностей native_error = (FilesError, TemplatesError, + VariableError, + ServerError, LdapError) - successMessage = __("SUCCESS") - failedMessage = __("FAILED") - interruptMessage = __("INTERRUPT") + successMessage = __("LDAP server configured!") + failedMessage = __("Failed to configure LDAP server!") + interruptMessage = __("LDAP configuration manually interrupted") + + meta_tasks = MetaTasks("Ldap") + + # список задач для удаления сервера + uninstall_tasks = [ + {'name': 'clear_creds', + 'method': 'Server.clear_service_data("all")', + 'condition': lambda Get: Get('server.sr_ldap_set') == 'on' + }, + {'name': 'unset_ldap', + 'method': 'Server.service_uninstall("ldap")', + 'condition': lambda Get: Get('server.sr_ldap_set') == 'on' + }, + ] + + stop_tasks = [ + {'name': 'noautorun', + 'method': 'Server.autorun_disable("%s")' % Ldap.Service.LDAP, + }, + {'name': 'stop_slapd', + 'message': __("Stopping LDAP service"), + 'method': 'Server.stop_service("%s")' % Ldap.Service.LDAP, + }, + ] + + # список задач для конфигурирования сервера + configure_task = [ + + ] # список задач для действия tasks = [ - {'name': 'test', - 'message': 'test' - } + {'name': 'uninstall', + 'tasks': uninstall_tasks, + }, + {'name': 'stop', + 'tasks': stop_tasks, + }, + {'name': 'preconfigure', + 'message': __("Pre-configure LDAP"), + 'method': 'Ldap.preconfigureTemplates()', + }, + {'name': 'remove_old_db', + 'method': 'Ldap.remove_ldap_db(ld_database_path)' + }, + {'name': 'start_slapd', + 'message': __("Starting LDAP service"), + 'method': 'Server.start_service("%s")' % Ldap.Service.LDAP, + }, + {'name': 'apply_ldif', + 'tasks': meta_tasks.ldif_task("ld_temp_dn,ld_temp_pw", Actions.Setup) + }, + {'name': 'templates', + 'message': __("Configure LDAP"), + 'method': 'Server.applyTemplates(install.cl_source,' + 'False,True,None,True,True)', + }, + {'name': 'restart_slapd', + 'message': __("Restarting LDAP service"), + 'method': 'Server.restart_service("%s")' % Ldap.Service.LDAP, + }, + {'name': 'save_creds', + 'method': 'Server.save_service_data("admin",ld_admin_dn,ld_admin_pw)' + }, + {'name': 'autorun', + 'method': 'Server.autorun_enable("%s")' % Ldap.Service.LDAP, + }, + {'name': 'set_ldap', + 'method': 'Server.service_install("ldap")' + }, + {'name': 'save_data', + 'method': 'Ldap.save_variables()' + } ] + + @classmethod + def register_stop(cls, stop_tasks): + cls.stop_tasks.extend(stop_tasks) + + @classmethod + def register_uninstall(cls, uninstall_tasks): + cls.uninstall_tasks.extend(uninstall_tasks) diff --git a/pym/ldap/variables/action.py b/pym/ldap/variables/action.py index bc844e4..9dc5308 100644 --- a/pym/ldap/variables/action.py +++ b/pym/ldap/variables/action.py @@ -20,18 +20,3 @@ from calculate.lib.datavars import ActionVariable from calculate.lib.cl_lang import setLocalTranslate setLocalTranslate('cl_ldap3', sys.modules[__name__]) - - -class Actions(object): - Setup = "ldap_setup" - - -class VariableAcLdapSetup(ActionVariable): - """ - Action variable which has value "on" for prepare squash - """ - - def action(self, cl_action): - if cl_action == Actions.Setup: - return "on" - return "off" diff --git a/pym/ldap/variables/helpers.py b/pym/ldap/variables/helpers.py new file mode 100644 index 0000000..374d094 --- /dev/null +++ b/pym/ldap/variables/helpers.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- + +# Copyright 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 +from calculate.lib.datavars import (ReadonlyVariable, Variable, + VariableInterface, VariableError) +from calculate.lib.utils.common import genpassword +from calculate.ldap.tools import SlapPasswd +from calculate.lib.utils.tools import repeater +from calculate.lib.cl_ldap import LDAPConnect, LDAPConnectError, ldap +from calculate.lib.configparser import (ConfigParserLocked, + Error as ConfigParserError) + +_ = lambda x: x +from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate) + +setLocalTranslate('cl_ldap3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ServerEnvHelper(VariableInterface): + """ + Получение значений из env файла или fallback + """ + fallback_variable = "" + fallback_value = "" + service = "" + parameter = "" + + def get(self): + cp_path = self.Get('server.cl_server_env_path') + cp = ConfigParserLocked(cp_path) + try: + with cp.lock_read() as cp: + value = cp.get(self.service, self.parameter, + fallback=None) + if value is None: + if self.fallback_variable: + return self.Get(self.fallback_variable) + else: + return self.fallback_value + else: + return value.encode('utf-8') + except ConfigParserError: + raise VariableError(_("Failed to remove server parameters")) + + +class LdapMaxHelper(VariableInterface): + """ + Получение максимального значение из среди атрибутов + """ + type = "int" + base_dn = "" + search_filter = "" + attr = "" + + def get_max(self): + ldap_connect = self.Get('ldap.cl_ldap_connect') + if ldap_connect: + base_dn = self._value_formatter.format(self.base_dn, self.Get) + try: + return max(int(x) for x in ldap_connect.ldap_simple_search( + base_dn, self.search_filter, self.attr) if x.isdigit()) + # max(empty_list) + except ValueError: + return None + else: + return None + + def get(self): + return str(self.get_max() or "") + + +class LdapSearchHelper(VariableInterface): + """ + Проверка свободен ли указанный идентификатор + """ + base_dn = "" + search_filter = "uid={value}" + + def value_get(self, value): + def get_wrapper(val): + if val == "value": + return value + else: + return self.Get(val) + + return get_wrapper + + def check_name(self, value): + ldap_connect = self.Get('ldap.cl_ldap_connect') + if ldap_connect: + base_dn = self._value_formatter.format(self.base_dn, + self.value_get(value)) + search_filter = self._value_formatter.format(self.search_filter, + self.value_get(value)) + print "DEBUG Search:", base_dn, search_filter + return any(ldap_connect.ldap_search( + base_dn, ldap.SCOPE_ONELEVEL, search_filter)) + else: + return None + + +class HashHelper(VariableInterface): + """ + Хэш пароля для LDAP + """ + source = "" + hash_var = "ldap.ld_encrypt" + + def get(self): + value = self.Get(self.source) + if value: + return SlapPasswd(self.Get(self.hash_var)).get_hash(value) + else: + return "crypt{xxx}" + + +class RandomPasswordHelper(VariableInterface): + """ + Генератор пароля + """ + password_len = 9 + + def get(self): + return genpassword(self.password_len).strip() diff --git a/pym/ldap/variables/ldap.py b/pym/ldap/variables/ldap.py index e1cdec8..0f67ea7 100644 --- a/pym/ldap/variables/ldap.py +++ b/pym/ldap/variables/ldap.py @@ -15,6 +15,208 @@ # limitations under the License. import sys -from calculate.lib.cl_lang import setLocalTranslate +from calculate.lib.datavars import (ReadonlyVariable, Variable, + VariableInterface, VariableError) +from calculate.lib.utils.tools import repeater +from calculate.lib.cl_ldap import LDAPConnect, LDAPConnectError +from helpers import ServerEnvHelper, HashHelper, RandomPasswordHelper + +_ = lambda x: x +from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate) setLocalTranslate('cl_ldap3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class VariableLdBaseRoot(Variable): + """ + Имя для базового суффикса + """ + value = "calculate" + opt = ("-b", "--basedn") + metavalue = "BASEDN" + + def init(self): + self.label = _("Root base DN") + self.help = "set root base DN" + + def set(self, value): + if value.startswith("dc="): + return value[3:] + return value + + +class VariableLdBaseDn(ReadonlyVariable): + """ + Базовый суффикс LDAP + """ + value_format = "dc={ld_base_root}" + + def init(self): + self.label = _("Base DN") + + +class VariableLdBindLogin(Variable): + """ + Пользователь для чтения + """ + value = "proxyuser" + + +class VariableLdBindDn(ReadonlyVariable): + """ + Bind суффикс LDAP + """ + value_format = "cn={ld_bind_login},{ld_base_dn}" + + +class VariableLdBindPw(ReadonlyVariable): + """ + Пароль для подключения + """ + value = "calculate" + + +class VariableLdBindHash(HashHelper, ReadonlyVariable): + """ + Хэш рут пароля + """ + source = "ld_bind_pw" + + +class VariableLdTempDn(ReadonlyVariable): + """ + Временный DN для подключения + """ + value_format = "cn=ldaproot,{ld_base_dn}" + + +class VariableLdTempPw(RandomPasswordHelper, ReadonlyVariable): + """ + Временный пароль для подключения к LDAP + """ + password_len = 9 + + def get(self): + return "test" + + +class VariableLdTempHash(HashHelper, ReadonlyVariable): + """ + Временный пароль для подключения к LDAP + """ + source = "ld_temp_pw" + + +class VariableClLdapPreconfigureSet(ReadonlyVariable): + """ + Предварительное наложение шаблонов + """ + type = "bool" + value = "off" + + +class VariableLdAdminDn(ReadonlyVariable): + """ + DN root пользователя + """ + value_format = "cn={ld_admin_login},{ld_base_dn}" + + +class VariableLdAdminLogin(ReadonlyVariable): + """ + Имя пользователя root + """ + value = "ldapadmin" + + +class VariableLdEncrypt(ReadonlyVariable): + """ + Алгоритм шифрования пароля + """ + value = "{SSHA}" + + +class VariableLdAdminHash(HashHelper, ReadonlyVariable): + """ + Хэш рут пароля + """ + source = "ld_admin_pw" + + +class VariableLdAdminPw(ServerEnvHelper, RandomPasswordHelper, Variable): + """ + Пароль root + """ + password_len = 9 + service = "admin" + parameter = "pass" + + @property + def fallback_value(self): + return "test" + # return RandomPasswordHelper.get(self) + + +class VariableLdServices(ReadonlyVariable): + """ + Имя всех сервисов + """ + value = "Services" + + +class VariableLdServicesDn(ReadonlyVariable): + """ + DN для всех сервисов + """ + value_format = "ou={ld_services},{ld_base_dn}" + + +class VariableLdDatabasePath(Variable): + """ + Путь до базы LDAP + """ + value = "/var/lib/openldap-data" + + +class VariableClLdapHost(Variable): + """ + Узел LDAP + """ + value = "localhost" + + +class VariableClLdapBindDn(Variable): + """ + Переменная используется для соединения с LDAP + """ + value = "" + + +class VariableClLdapBindPw(Variable): + """ + Переменная используется для соединения с LDAP + """ + value = "" + + +class VariableClLdapConnect(ReadonlyVariable): + """ + Объект соединение с LDAP + """ + type = "object" + + def get(self): + bind_dn = self.Get('cl_ldap_bind_dn') + bind_pw = self.Get('cl_ldap_bind_pw') + ldap_host = self.Get('cl_ldap_host') + if bind_dn and bind_pw: + error = "" + for x in repeater(0.2, 0.4, 0.8): + try: + return LDAPConnect(bind_dn, bind_pw, host=ldap_host) + except LDAPConnectError as e: + error = str(e) + raise VariableError(_("Failed to connect to LDAP server") + + _(": ") + error) + return "" diff --git a/pym/ldap/wsdl_ldap.py b/pym/ldap/wsdl_ldap.py index ff11336..bbe48b6 100644 --- a/pym/ldap/wsdl_ldap.py +++ b/pym/ldap/wsdl_ldap.py @@ -20,7 +20,8 @@ from calculate.lib.datavars import VariableError, DataVarsError from calculate.core.server.func import WsdlBase from .ldap import Ldap, LdapError -from .variables.action import Actions as LdapActions +from calculate.server.server import Server +from calculate.server.variables.action import Actions as ServerActions from utils.cl_ldap_setup import ClLdapSetupAction _ = lambda x: x @@ -39,11 +40,11 @@ class Wsdl(WsdlBase): # идентификатор метода 'method_name': Ldap.Method.Setup, # категория метода - 'category': __('LDAP'), + 'category': __('Setup Server'), # заголовок метода - 'title': __("Setup"), + 'title': __("LDAP"), # иконка для графической консоли - 'image': 'setup', + 'image': 'server-database', # метод присутствует в графической консоли 'gui': True, # консольная команда @@ -51,21 +52,27 @@ class Wsdl(WsdlBase): # права для запуска метода 'rights': ['ldap'], # объект содержащий модули для действия - 'logic': {'Ldap': Ldap}, + 'logic': {'Ldap': Ldap, + 'Server': Server}, # описание действия 'action': ClLdapSetupAction, # объект переменных 'datavars': "ldap", 'native_error': (VariableError, DataVarsError, LdapError), # значения по умолчанию для переменных этого метода - 'setvars': {'cl_action!': LdapActions.Setup}, + 'setvars': {'cl_action!': ServerActions.Setup, + 'server.cl_server_name!': "ldap", + 'cl_autoupdate_set': 'on' + # 'cl_dispatch_conf_default': "usenew" + }, # описание груп (список лямбда функций) 'groups': [ lambda group: group( - _("Repository"), - brief=(), - hide=(), - normal=(), + _("LDAP server"), + brief=('ld_base_dn', + 'server.sr_ldap_set'), + hide=('ld_base_root',), + normal=('ld_base_root',), expert=()), ], 'brief': {'next': __("Perform"), diff --git a/setup.py b/setup.py index bc296d5..3f31949 100755 --- a/setup.py +++ b/setup.py @@ -32,6 +32,8 @@ packages = [ if '__init__.py' in files ] +print packages + setup( name=__app__, version=__version__, @@ -40,5 +42,6 @@ setup( author_email="support@calculate.ru", url="http://calculate-linux.org", license="http://www.apache.org/licenses/LICENSE-2.0", - package_dir = {'calculate.ldap': "pym/ldap"}, - packages = packages) + package_dir = {'calculate.ldap': "pym/ldap", + 'calculate.lib.format': 'pym/format'}, + packages = packages + ["calculate.lib.format"])