Обратная синхронизация с Active Directory

21 3

Ни для кого не секрет, что современная IT-инфраструктура становится все сложнее и больше. Все больше систем автоматизации, накопления, обработки информации появляется в организациях. Помимо этого, каждая из систем развивается и становится сложнее, что требует больших усилий при их администрировании. Минимальным на предприятии я считаю должны быть:

Думаю, каждый администратор DIRECTUM или Active Directory, который хоть раз вручную проводил синхронизацию штатного расписания в системе, поймет меня. Когда отдел кадров не сообщает об изменениях в штатном расписании, переводе сотрудника на другую должность или перемещении сотрудника в другой отдел, а потом разом приходится вносить очень много изменений в DIRECTUM, то это отнимает очень много времени. В организации с численностью 50 человек это уже может стать камнем преткновения. Лично я поддерживаю мнение о том, что лень – двигатель прогресса, поэтому все, что можно автоматизировать, надо автоматизировать!

Классическая схема передачи сведений о сотруднике между системами выглядит примерно так:

Распространенный режим интеграции систем на предприятии

Такая схема используется очень часто, так как именно в системе учета находятся самая актуальная информация, которая потом передается в Active Directory, откуда уже многие другие программы получают информацию.

В данной статье будет рассматриваться немного модифицированный вариант схемы, когда не вся информация может быть получена с кадровой системы учета:

Рассматриваемый режим интеграции систем на предприятии

Такой вариант обусловлен тем, что в DIRECTUM могут быть более полные данные о сотруднике, чем в системе учета или Active Directory. Конечно же, актуальная кадровая информация хранится в системе учета, но там может хранится на много меньше данных о сотруднике, чем в DIRECTUM. Active Directory может выступать как средство контроля доступа, но не содержать актуальной информации о пользователях.

Рассмотрим блоки схемы подробнее:

  • Кадровая система учета – в данном случае это система, работу в которой ведут отдел кадров, а также бухгалтерия. Из всей накопленной информации о сотруднике в кадровой системе, в DIRECTUM стоит переносить: должность, подразделение, состояние сотрудника (уволен, в отпуске и т.д.) и телефон.
  • Active Directory – в данном случае это система контроля доступа, а также каталог информации о пользователях, которая может быть получена сторонними программами. Изначально пользователь создается с минимальной информацией. В дальнейшем информация о нем обновляется благодаря синхронизации.
  • DIRECTUM – система куда стекается вся информация по сотруднику. Не все данные могут быть получены из кадровой системы учета. Например, фото сотрудника, телефон, e-mail могут быть указаны в DIRECTUM явно, как и некоторые другие реквизиты.

В стандартной поставке DIRECTUM есть возможность синхронизировать данные по пользователям из Active Directory в DIRECTUM, но нет возможности синхронизировать данные в обратную сторону. Я решил устранить данный пробел и реализовал обратную синхронизацию.

Расскажу об основных сложностях, с которыми пришлось столкнуться.

Получение данных из Active Directory

Самый распространённый вариант — это использование протокола LDAP, однако IS-Builder не умеет работать с LDAP-объектами напрямую. В данной ситуации может выручить ADODB.Connection.

DomenAD = 'test.local'                        // Домен или контроллер домена AD
DomenADPatch = 'OU=Moscow,DC=test,DC=local'   // Путь внутри домена до корневого элемента
Connection = CreateObject('ADODB.Connection') // Получим объект для работы
Connection.Provider = 'ADsDSOObject'          // Укажем провайдера для работы с LDAP
Connection.Open('ADs Provider')               // Откроем соединение

Command = CreateObject('ADODB.Command')     // Объявим объект для выполнения запроса. 
                                            // В качестве результата вернется объект RecordSet

Command.ActiveConnection = Connection       // Передадим настроенное соединение 

RecordSet = CreateObject('ADODB.Recordset') // Получим объект в котором будет 
                                            // храниться результат запроса

// Составим запрос в LDAP каталог, указав получаемые поля
Sql = "select
         DisplayName,
         sAMAccountName,
         ...
      from
        'LDAP://" & DomenAD & "/" & DomenADPatch & "'
      where
         objectCategory = 'Person' and objectClass = 'user'
         order by name"   

Command.CommandText = Sql                     // Передадим запрос для выполнения  

RecordSet = Command.Execute                   // Выполним запрос и запишем результат 
                                              // в объект RecordSet
 

Один из плюсов такого варианта — это получение данных подобно SQL-запросу, где достаточно указать получаемые поля из схемы. Однако, данный поставщик предоставляет каталог LDAP только на чтение, что накладывает свои ограничения.

Сопоставление пользователей

С первого взгляда ничего сложного тут нет, IUser.Name соответствует логину в Active Directory, но это не всегда так. Пользователь может изменить фамилию, имя и т.д., что может заставить администраторов Active Directory изменить ему имя входа в каталоге. Имя входа в DIRECTUM можно перегенерировать, однако, получить объект IUser с помощью ServiceFactory.GetUserByName по новому имени не получится. Для решения такой ситуации нужно проверить компоненту Пользователи на наличие данного логина и получить объект IUser сотрудника на основе данных поля Имя.

Пример определения:

// Получим компоненту Пользователи
URef = References.SYSREF_USERS.GetComponent

// Установим фильтр по реквизиту Логин
URef.Filter = '[UserLogin] = "' & RecordSet.Fields('sAMAccountName').Value & '"'

// Включим фильтр
URef.Filtered = true

// Откроем отфильтрованный справочник
URef.Open
if URef.RecordCount == 1
   try
      // Попробуем получить IUser
      IUser = ServiceFactory.GetUserByName(URef.SYSREQ_CODE)
   except
      IUser = null
   endexcept
endif
// Выключим фильтр
URef.Filtered = false
URef.Close

// Уничтожаем объект
URef = nil

Еще один момент который стоит учесть: у пользователя может быть несколько записей справочника Работники. Как тут поступать надо решать в каждом конкретном случае.

Обработка изображений

Основная сложность при обработке фотографий возникла из-за разных требований к фотографиям, в DIRECTUM и системах, которые получают фотографии из Active Directory. В самом Active Directory максимальный размер значения атрибута thumbnailPhoto пользователя, в котором хранится загружаемая фотография, составляет 100Кб.
Для изменения изображений можно использовать стандартный объект Windows Image Acquisition (WIA), который позволяет изменять изображения на лету, не устанавливая стороннего программного обеспечения.

// Получаем объект для управления фильтрами изображений
ImageProcess = CreateObject("WIA.ImageProcess")

// Получаем контейнер для работы с изображениями
ImageFile    = CreateObject('WIA.ImageFile') 
     
// Находим фильтр Scale, который позволяет управлять масштабом изображений 
Filter = ImageProcess.FilterInfos("Scale").FilterID

// Добавляем фильтр для возможности работы с ним
ImageProcess.Filters.Add(Filter)
               
// Указываем размеры изображения, которое нам надо получить. 
// Размеры указываются в пикселях
ImageProcess.Filters(1).Properties("MaximumWidth").Value  = 250
ImageProcess.Filters(1).Properties("MaximumHeight").Value = 250

// Применим фильтр к изображению
ImageFile = ImageProcess.Apply(ImageFile)

// Сохраним изображение в файл
ImageFile.SaveFile(ImagesFullPath)

Такой вариант должен работать на рабочих станциях с Windows 10, а также Windows Server 2016, в остальных случаях требуется установка дополнительных компонентов системы: Возможности рабочего стола. При необходимости можно провести преобразование изображения в другой формат. Для этого можно использоваться фильтр Convert.

Преобразование и изменение размера изображения не самая быстрая операция. Поэтому вариант обновления фотографий в Active Directory надо рассматривать индивидуально. При маленьком количестве фотографий или наличии достаточно хорошего канала связи между контроллерами домена, можно постоянно загружать фотографии в каталог, где они будут реплицироваться между контроллерами домена. Необходимо учесть, что при наличии медленного канала связи между контроллерами домена (или большого количества: фотографий, контроллеров домена) лучше будет выгружать фото из Active Directory и проводить сравнения, так как в момент занесения фото в Active Directory обновленный объект будет реплицироваться на все контроллеры, что может быть еще более длительной операцией.

Внесение изменений в Active Directory

Выше я уже упоминал, что IS-Builder не умеет работать с LDAP-объектами напрямую, поэтому встал вопрос поиска вариантов обновления информации в Active Directory. Решение оказалось очень простым, на помощь пришел VBScript.

В IS-Builder формируем скрипт, который в конце выполняем с помощью MSScriptControl.ScriptControl.

// Формируем скрипт, который будет выполняться
SCRIPT_TEXT = 'Set objUser = GetObject("LDAP://' & DomenAD & '/' & DistinguishedName & '")
               objUser.PutEx 1, "jpegPhoto", vbNullString 
               objUser.PutEx 1, "thumbnailPhoto", vbNullString   
               objUser.SetInfo'

// Объявляем объект, для выполнения скрипта                  
Script = CreateObject('MSScriptControl.ScriptControl')

// Указываем язык скрипта, который будет выполняться
Script.Language = 'VBScript'
Script.Reset 
Script.AddObject('AttList'; CreateList())

// Передаем скрипт для его выполнения
Script.AddCode(SCRIPT_TEXT)   

// Не забываем очищать объект
Script = nil

Разберем более подробно.

С помощью функции GetObject() мы получаем объект LDAP. Дальнейшее обновление информации в объекте производится с помощью метода Put().

При записи данных в Active Directory стоит учесть, что пустая строка, переданная в качестве параметра в методе Put(), вызовет ошибку во время выполнения скрипта. Для очистки значения в Active Directory стоит использовать метод PutEx(), так как он обладает более гибкими настройками. В качестве первого параметра он принимает способ изменения информации в каталоге. Это могут быть:

  1. – Удаление всех значений атрибута;
  2. – Обновление значения атрибута;
  3. – Добавление к существующим значениям (например, добавление группы);
  4. – Удаление указанных значений из атрибута.

Все изменения которые вносятся в объект с помощью методов Put() и PutEx() выполняются только на локальном компьютере. Для обновления заранее подготовленных данных непосредственно в Active Directory необходимо вызвать метод SetInfo(), который вносит изменения в саму службу.

Особенностью данного подхода является его асинхронность, поэтому при загрузке фотографий в Active Directory не стоит сразу их удалять, чтобы избежать ошибок и дать возможность объекту, корректно загрузить изображения в каталог.

Результат

Ниже приложен сценарий для примера, который проводит обновление данных в Active Directory выгружая информацию из DIRECTUM. Сценарий проводит обновление следующих реквизитов в Active Directory:

Список реквизитов может быть изменен (Полный список атрибутов объектов AD), в зависимости от потребностей организации.

Все операции по обновлению или изменению данных происходят после сопоставления пользователя Active Directory и пользователя DIRECTUM.

Пара слов о параметрах в сценарии на которые стоит обратить внимание:

  • TaskWork (bool) – в случае если имеет истинное значение, то информация будет обновляться в Active Directory, в ином случае в Log файл (если он доступен), будет записан сформированный VBScript для обновления объекта AD.
  • TestUserADObject (string) – пользователь, которого рекомендуется создать для тестов. Если данная переменная означена и значение переменной TaskWork равно истине, то данные о пользователях будут вноситься в Active Directory и именно в этот объект, не затрагивая фактических пользователей. Данный параметр удобно использовать для эмуляции внесения изменений и проверки прав доступа.

Для полноценной работы синхронизации, необходимо добавить учетной записи Active Directory, от имени которой будет запускаться сценарий, права на обновление необходимых атрибутов в домене.

Данный сценарий приведен для примера и не служит окончательным решением! Автор настоятельно рекомендует использовать его для ознакомительных целей или после доработок под потребности организации.

Скачать сценарий синхронизации данных с Active Directory

Роман Деменков

Для создания пользователя в АД из Директум используем следующую функцию:

sl = CreateStringList()
sl.Add(Format('
    Function Main()
      Set ou = GetObject("LDAP://%s")
      Set user = ou.Create("user", "cn=%s")
      user.sAMAccountName = "%s"
      user.userAccountControl = 544
      user.SetInfo

      user.SetPassword "' & password & '"
      user.userAccountControl = 512
      user.pwdLastSet = 0
      user.Put "userPrincipalName", "' & userPrincipalName & '@domain.ru"
      user.Put "homeDirectory", "' & homeDirectory & '"
      user.Put "homeDrive", "' & homeDrive & '"
      user.Put "sn", "' & sn & '"
      user.Put "displayName", "' & Person & '"
      user.Put "givenName", "' & givenName & '"
      user.Put "department", "' & department & '"
      user.Put "title", "' & title & '"
      user.Put "description", "' & description & '"
      user.Put "physicalDeliveryOfficeName", "' & physicalDeliveryOfficeName & '"
      user.Put "streetAddress", "' & streetAddress & '"
      user.Put "l", "' & l & '"
      user.Put "st", "' & st & '"
      user.Put "company", "' & company & '"
      user.SetInfo

      Main = user.distinguishedName
    End Function'; ArrayOf(ADPodr; Person; Login)))

  ScriptControl = CreateObject("MSScriptControl.ScriptControl")
  ScriptControl.Language = "VBScript"
  ScriptControl.Reset
  ScriptControl.AddCode(sl.Text)
  distinguishedName = ScriptControl.Run("Main")
  ADAddUserToGroup(distinguishedName)

Создаёт в конкретном подразделении department. У нас синхронизированы подразделения между АД и Директум.

ADAddUserToGroup добавляет пользователя в группы/группу:

...
sl.Add(Format('
    Function Main()
      Set grp = GetObject("LDAP://%s")
      grp.Add ("LDAP://%s")
      grp.SetInfo
    End Function'; ArrayOf(ADPodr; UserDN)))
ScriptControl = CreateObject("MSScriptControl.ScriptControl")
ScriptControl.Language = "VBScript"
ScriptControl.Reset
ScriptControl.AddCode(sl.Text)
ScriptControl.Run("Main")

ADPodr это distinguishedName группы в АД.

Роман Деменков: обновлено 05.05.2017 в 08:45
Михаил Манохин

А часто всплывает необходимость обратной синхронизации из Directum в AD? Интересно, нужен ли подобный функционал в коробке. 

Владимир Гарипов

На самом деле такой функционал — это частные случаи, чем правило. Я в начале статьи рассказывал о том, что в DIRECTUM чаще данные попадают из AD, чем наоборот. Напомню, все зависит от того как именно выстроена ваша архитектура предприятия.

Авторизуйтесь, чтобы написать комментарий