Рубрика:
Администрирование /
Продукты и решения
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Вадим Андросов
Делегируем права на перемещение учетных записей пользователей в Active Directory Часть 3. Реализуем необходимые операции
Продолжим изучать функции поддержки работы надстройки для Windows 2003 Server, реализующей перевод пользователя из одного подразделения в другое силами двух администраторов. В частности, будут затронуты вопросы программной манипуляции объектами Active Directory, работы с событиями с помощью WMI, модификации списков контроля доступа (ACL).
Делегирование полномочий менеджеру
Реализуем механизм автоматического делегирования прав на манипуляцию объектами пользователей. Конечно, эта операция может быть выполнена и вручную. Однако было бы гораздо удобнее назначить руководителя организационной единицы. Необходимые права должны быть предоставлены менеджеру автоматически. Соответственно при потере пользователем роли менеджера подразделения эти права нужно также автоматически отнять.
Такие вещи очень важны для предприятий со сложной структурой, когда администратор должен быть избавлен от необходимости выполнять взаимосвязанные действия вручную. Вместо этого достаточно выполнить только инициирующую операцию (назначение менеджера по персоналу отдела). Это существенно снизит уровень ошибок администратора (например, лишив пользователя роли менеджера можно забыть отключить у него соответствующие права), как следствие, повысив общий уровень безопасности системы.
Методы, связанные с делегированием, будут также реализованы в рамках единого класса надстройки UserMove.Engine.
Начнем с метода, предоставляющего менеджеру права на манипуляции с объектами пользователей в рамках заданного подразделения. В качестве параметров он получает путь к организационной единице и менеджеру, которому требуется предоставить необходимые полномочия.
function delegateOU(ouPath, userPath)
dim trustee, sec, acl, ace, ou, user, i
Set ou = getObject(ouPath)
Set user = getObject(userPath)
Для начала нужно привязаться к объекту «Список контроля доступа» текущей организационной единицы, как это делалось в методах проверки, реализованных в предыдущей части статьи [2].
Set sec = ou.Get("ntSecurityDescriptor")
Set acl = sec.DiscretionaryAcl
trustee = info.domainShortName & "\" & user.samAccountName
for i = 0 to UBound(delegationClasses)
Затем по очереди создаются необходимые записи списка контроля доступа для каждого из классов массива delegationClasses. Они уже обсуждались при реализации проверки, поэтому подробно останавливаться на них не будем. Как будет видно из программы ниже, для этого нужно создать объект типа AccessControlEntry, проинициализировать необходимые поля и записать новый элемент в список контроля доступа с помощью метода списка AddAce.
Set ace = createAcceptAce(ADS_RIGHT_DS_CREATE_CHILD Or ADS_RIGHT_DS_DELETE_CHILD, delegationClasses(i), ADS_FLAG_OBJECT_TYPE_PRESENT, Trustee)
Эта запись разрешает создавать в организационной единице объекты заданного типа.
acl.AddAce ace
Set ace = createAcceptAce(FULL_CONTROL , delegationClasses(i), ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT, Trustee)
Эта запись разрешает изменение свойств объектов, принадлежащих классу с идентификатором delegation Classes(i)
acl.AddAce ace
next
Обновленный список привязывается к отделу. Важно не забыть вызвать метод организационной единицы SetInfo, чтобы изменения были физически сохранены.
sec.DiscretionaryAcl = acl
ou.Put "ntSecurityDescriptor", Array(sec)
ou.SetInfo
Кроме того, в первой части статьи [1] упоминалась специальная группа безопасности, содержащая всех менеджеров по персоналу. Вызов следующей подпрограммы будет добавлять текущего пользователя в такую группу. Его реализация будет рассмотрена позже.
makeManager user
end function
Рассмотрим функцию создания записи списка контроля доступа.
function createAcceptAce(accessMask, classGUID, flag, trustee)
Сначала создается экземпляр класса AccessControlEntry, посредством которого будет предоставляться заданное право.
set createAcceptAce = CreateObject("AccessControlEntry")
createAcceptAce.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
createAcceptAce.accessMask = accessMask
Затем в зависимости от параметра flag целевой объект записывается в поле ObjectType или InheritedObjectType.
select case flag
case ADS_FLAG_OBJECT_TYPE_PRESENT:
createAcceptAce.ObjectType = classGUID
createAcceptAce.AceFlags = ADS_ACEFLAG_INHERIT_ACE
case ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT:
createAcceptAce.InheritedObjectType = classGUID
createAcceptAce.AceFlags = ADS_ACEFLAG_INHERIT_ACE + ADS_ACEFLAG_INHERIT_ONLY_ACE
end select
createAcceptAce.Flags = flag
createAcceptAce.Trustee = trustee
end function
Потребуется и обратная функция для отмены делегирования. Сразу нужно оговориться, что фактически будет происходить удаление тех записей списка контроля доступа, которые добавлялись в функции делегирования выше. Если пользователь имеет те же права, например, участвуя в определенных группах, то их он не потеряет. То есть речь идет не об отмене делегирования управления организационной единицей как таковой, а, скорее, о функции, обратной delegateOU. Такое поведение было выбрано, чтобы минимизировать влияние надстройки на другие политики безопасности предприятия.
Листинг 1. Отмена делегирования управления организационной единицей
function undelegateOU(ouPath, userPath)
dim ou, user, i
set ou = getObject(ouPath)
set user = getObject(userPath)
for i = 0 to UBound(delegationClasses)
undelegate ou, user, FULL_CONTROL , delegationClasses(i), true
undelegate ou, user, ADS_RIGHT_DS_DELETE_CHILD Or ADS_RIGHT_DS_CREATE_CHILD, delegationClasses(i), false
next
makeNotManager user
end function
Отмена конкретного разрешения выполняется следующей подпрограммой. Все записи списка контроля доступа проверяются с помощью метода shouldBeDeleted и, если он вернул истину, удаляются.
Листинг 2. Удаление из ACL записей, позволяющих управлять организационной единицей
function undelegate(ou, user, oper, targetClass, isInherited)
Dim sec, acl, ace
Set sec = ou.Get("ntSecurityDescriptor")
Set acl = sec.DiscretionaryAcl
For Each ace In acl
if shouldBeDeleted(user, ace, oper, targetClass, isInherited) then
acl.RemoveAce ace
end if
Next
sec.DiscretionaryAcl = acl
ou.Put "ntSecurityDescriptor", Array(sec)
ou.SetInfo
end function
Следующая подпрограмма выясняет, нужно ли удалить определенную запись списка контроля доступа при удалении у пользователя прав менеджера по персоналу. Она проверяет, что запись относится к целевому пользователю и предоставляет права на управление организационной единицей.
Листинг 3. Требуется ли удалять запись ACL для отмены делегирования
function shouldBeDeleted(user, ace, oper, targetClass, isInherited)
shouldBeDeleted = false
if ace.trustee <> (info.domainShortName & "\" & user.samAccountName) then exit function
dim classGUID
if isInherited then
classGUID = ace.InheritedObjectType
else
classGUID = ace.ObjectType
end if
if (classGUID = targetClass) and isMask(ace.accessMask, oper) then
if ace.AceType = _
ADS_ACETYPE_ACCESS_ALLOWED_OBJECT then
shouldBeDeleted = true
exit function
end if
end if
end function
Наконец, рассмотрим подпрограммы поддержки актуальности группы безопасности менеджеров по персоналу. Было принято решение назвать ее Staff Managers. Жестко прописанное название специальной группы не является самым удачным решением. Я остановился на нем, чтобы не отвлекать внимания от сущности надстройки мелкими деталями реализации. Вся функциональность описывается с помощью двух методов: makeManager (добавляет пользователя в группу менеджеров при назначении его руководителем организационной единицы) и makeNotManager (соответственно удаляет пользователя из группы. Для привязки к группе я использовал провайдер WinNT, чтобы не зависеть от расположения объекта группы в иерархии.
Итак, первый метод добавления пользователя в группу. В нем сначала происходит привязка к объекту группы с помощью провайдера WinNT. Для этого используется путь, состоящий только из домена и имени группы, например WinNT://MARKLAR/Staff Managers. Сама группа может быть расположена в любом контейнере. Затем составляется описатель пользователя в таком же формате (переменная userWinNTPath). Если пользователь уже является членом группы, функция завершает работу. В противном случае пользователь добавляется в группу с помощью метода add.
Листинг 4. Назначение менеджера по персоналу для подразделения
function makeManager(newManager)
dim managerGroup, userWinNTPath
set managerGroup = getObject("WinNT://" & info.domainShortName & "/Staff Managers")
userWinNTPath = "WinNT://" & info.domainShortName & "/" & newManager.samAccountName
if managerGroup.isMember(userWinNTPath) then exit function
managerGroup.add userWinNTPath
managerGroup.setInfo
end function
Метод удаления пользователя из группы очень похож на предыдущий. Главное отличие – если проверка показывает, что пользователь содержится в группе менеджеров по персоналу, он из нее удаляется.
Листинг 5. Отмена полномочий менеджера по персоналу для пользователя
function makeNotManager
dim managerGroup, userWinNTPath
set managerGroup = getObject("WinNT://" & info.domainShortName & "/Staff Managers")
userWinNTPath = "WinNT://" & info.domainShortName & "/" & user.samAccountName
if managerGroup.isMember(userWinNTPath) then
managerGroup.remove userWinNTPath
managerGroup.setInfo
end if
end function
Таким образом, группа Staff Managers будет всегда содержать действительный список менеджеров по персоналу организации.
Автоматизация делегирования
Функции делегирования и его отмены реализованы. Теперь остается только обеспечить их автоматическое выполнение при смене руководителя подразделения (см. рисунок).
Назначение руководителя подразделения
Подробно реализация обработки событий на основе сценариев была рассмотрена в [3], поэтому ограничусь поверхностным описанием. В этом случае потребуется обрабатывать события изменения объекта организационной единицы (__InstanceModificationEvent).
Для фильтрации событий будет использоваться специальный язык WQL [4] (WMI Query Language). Он основан на SQL (точнее, является подмножеством структурированного языка запросов) и во многом повторяет его синтаксис. WQL содержит только оператор SELECT, модифицированный для работы с WMI (Windows Management Instrumentation). Особенности языка будут рассмотрены в статье по мере его использования.
Сначала рассмотрим сценарий установки обработчика событий. Для этого требуется создать и соответствующим образом проинициализировать объекты трех классов
- __EventFilter. С помощью объекта этого класса операционная система ставится в известность, какие именно события должны обрабатываться. Для этого используется запрос на языке WQL. Для нашего случая запрос будет иметь вид:
SELECT * FROM __InstanceModificationEvent "WITHIN 5 WHERE TargetInstance ISA 'ds_organizationalunit'
Это значит, что требуется обрабатывать события класса __InstanceModificationEvent для объектов типа ds_organizationalunit, наличие событий должно проверяться раз в 5 секунд.
Объект этого класса инкапсулирует реакцию на событие и позволяет выполнить произвольный сценарий. Текст выполняемой программы должен быть сохранен в поле ScriptText.
- __FilterToConsumerBinding. Класс, объект которого используется для связи объектов предыдущих двух типов. Инициализация его полей включает механизм слежения за событиями. Удаление объекта этого класса приводит к прекращению обработки соответствующего события.
Сначала рассмотрим функцию, устанавливающую слушателя на событие. Первый ее параметр – название операции. Используется при формировании имен вспомогательных классов, чтобы они были нагляднее. Второй вариант – имя события, обработчик которого создается. Третий – имя файла с кодом обработки события.
Const FOR_READING = 1
Function addListener(sOperation, sEvent, sClass, sScript)
Сначала создается объект для работы с файловой системой. Текст сценария, выполняемого в ответ на событие, для удобства будет храниться в отдельном файле и загружаться при установке обработчика.
Set fso = createObject("Scripting.FileSystemObject")
path = "\\.\root\directory\LDAP"
Set objSWbemServices = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!" & path)
Затем подключаемся к классам, экземпляры которых будем создавать впоследствии. Классы __EventFilter и __FilterToConsumerBinding являются стандартными и уже присутствуют в пространстве имен root\directory\LDAP.
Set eventFilterClass = objSWbemServices.Get("__EventFilter")
Set consumerClass = objSWbemServices.Get("ActiveScriptEventConsumer")
Set bindingClass = objSWbemServices.Get("__FilterToConsumerBinding")
ActiveScriptEventConsumer нужно отдельно скомпилировать в этом пространстве имен с помощью команды:
mofcomp -N:root\directory\LDAP %SYSTEMROOT%\system32\wbem\scrcons.mof
Затем с помощью метода SpawnInstance_ создаются экземпляры каждого класса и инициализируются необходимые поля. Вызов метода Put_ приводит к сохранению экземпляра в постоянном хранилище.
Инициализируем класс-фильтр. Основная настройка здесь – текст запроса на языке WQL, в котором указывается, события какого типа требуется обрабатывать. С помощью конструкции WITHIN 5 системе предписывается проверять очередь событий раз в 5 секунд. Операция перевода человека в другой отдел достаточно длительная, поэтому значение может быть и больше.
set userFilter = eventFilterClass.SpawnInstance_()
userFilter.Name = sOperation & "Filter"
userFilter.QueryLanguage = "WQL"
userFilter.Query = "SELECT * FROM " & sEvent & " " & "WITHIN 5 WHERE TargetInstance ISA '" & sClass & "'"
userFilter.EventNamespace = "root\directory\LDAP"
userFilter.Put_()
Код непосредственной обработки события загружаем из файла. В нем не должно быть символов перевода строки, поэтому предварительно код сценария преобразовывается в однострочный с помощью функции multyLine2SingleString (ее реализация будет указана ниже).
Set srcFile = fso.OpenTextFile(sScript, FOR_READING)
set userConsumer = consumerClass.SpawnInstance_()
userConsumer.Name = "Run" & sOperation & "Script"
userConsumer.ScriptText = multyLine2SingleString(srcFile.readAll)
userConsumer.ScriptingEngine = "VBScript"
userConsumer.Put_()
srcFile.close
set userBinder = bindingClass.SpawnInstance_()
userBinder.Filter = "__EventFilter.Name=" & chr(34) & sOperation & "Filter" & chr(34)
userBinder.Consumer = "ActiveScriptEventConsumer.Name=" & chr(34) & "Run" & sOperation & "Script" & chr(34)
После выполнения следующей строки начнется обработка событий изменения объектов организационных единиц.
userBinder.Put_()
end function
Function multyLine2SingleString(multyLine)
multyLine2SingleString = replace(multyLine, vbCrLf, " : ")
end Function
Функции установки слушателей событий нужно сохранить в отдельном файле с расширением vbs. В начале файла будут содержаться их вызовы.
addListener "OUModificate", __InstanceModificationEvent", "ds_organizationalunit", "onOUModification.vbs_"
addListener "CommandSeed", "__InstanceCreationEvent", "ads_usermovecommand", "onCommand.vbs_"
Функция вызывается дважды. Первый вызов устанавливает обработчик события смены руководителя подразделения (см. рисунок). Рассмотрим содержимое файла onOUModification.vbs_. Расширение файла может быть любым, поскольку используется он только для хранения текста сценария. Сам по себе он не запускается.
Итак, цель – добиться того, чтобы при смене руководителя подразделения ему автоматически делегировались полномочия манипулирования пользовательскими объектами. У предыдущего руководителя эти полномочия нужно соответственно отнять. Реализуем эту функциональность (далее приводится содержание файла onOUModification.vbs_, т.е. сценарий, выполняющийся при смене менеджера организационной единицы).
В сценариях обработки событий можно использовать экземпляр объекта текущего события, ссылка на которых хранится в переменной targetEvent. Создавать или инициализировать ее вручную не нужно. При обработке события изменения имеется доступ как к текущему (TargetInstance), так и к предыдущему (Previous Instance) состояниям объекта. Это очень удобно, так как таким образом можно получить информацию о новом и старом менеджерах. Собственно, это и делается в первых двух строках сценария.
newManager = targetEvent.TargetInstance.DS_managedBy
oldManager = targetEvent.PreviousInstance.DS_managedBy
Затем производится проверка, а изменился ли менеджер. Дело в том, что событие этого типа возникает при изменении любого свойства организационной единицы (переименовании, например), продолжать же данный сценарий нужно, только если изменился менеджер.
if newManager <> oldManager then
Сначала создается объект основного класса надстройки, разрабатываемый в ходе этой статьи, который инкапсулирует основные операции надстройки.
set engine = createObject("UserMove.Engine")
Затем сохраняем путь к организационной единице в переменной ouPath:
ouPath = targetEvent.TargetInstance.ADSIPath
if oldManager <> "" then
Если у этого подразделения был другой менеджер, аннулируем его права.
engine.undelegateOU ouPath, "LDAP://" & oldManager
end if
Затем осуществляется делегирование прав манипуляции пользовательскими объектами новому менеджеру.
if newManager <> "" then
Если новый менеджер назначен (возможен сброс этого поля, когда старое значение удаляется, а новое не назначается), ему предоставляются необходимые права.
engine.delegateOU ouPath, "LDAP://" & newManager
end if
end if
Теперь автоматическое делегирование прав работает. Достаточно назначить менеджера подразделения с помощью оснастки Active Directory Users and Computers (см. рисунок), чтобы ему были делегированы полномочия работы с объектами пользователей в рамках данного подразделения.
Далее проанализируем файл, который содержит обработчик событий, связанных с объектами команд onCommand.vbs_:
set cmd = getObject(targetEvent.TargetInstance.ADSIPath)
createObject("UserMove.Engine").dispatchCommand cmd
Он содержит две строчки. Потому что весь код реальной обработки инкапсулирован в методе dispatchCommand основного класса надстройки UserMove.Engine. Его реализации и будет посвящена следующая часть статьи.
Set objWIMService = _ GetObject("winmgmts:\\.\root\directory\LDAP")
Сначала удаляются объекты классы __FilterToConsumer Binding. Они не имеют имен, поэтому для подключения к ним используется специальный запрос на получение всех объектов, ссылающихся на фильтр удаляемого события (__EventFilter). Результат выборки обходится, и все найденные объекты удаляются из хранилища.
Set objList = objWIMService.ExecQuery("references of {__EventFilter.Name='OUModificateFilter'}")
For each objInst in objList
objInst.Delete_
Next
Операции выше достаточно для прекращения обработки событий. Ее можно применять для временного отключения надстройки. Однако для полного удаления всех следов нужно удалить и оставшиеся два объекта. У них уже есть отличительные имена, поэтому операция удаления заключается в привязке на основе имени и вызова метода obj.Delete_:
Set obj = GetObject("winmgmts:\\.\root\directory\LDAP:” & ”ActiveScriptEventConsumer='RunOUModificateScript'")
obj.Delete_
Set obj = GetObject("winmgmts:\\.\root\directory\LDAP:” & & ”__EventFilter='OUModificateFilter'")
obj.Delete_
Обработчик команд
В этом разделе будет описан еще ряд методов основного класса надстройки UserMove.Engine. Здесь основное внимание сосредоточим на обработке объектов-команд. Для этого будет использоваться единственный открытый метод dispatchCommand и несколько закрытых вспомогательных.
Каждая команда имеет поле своего типа. Тип команды – строка. Все они определены в файле класса надстройки посредством ввода следующих констант.
Команда начала перемещения:
Const START_MOVE_COMMAND = "StartMove"
Команда отказа принять переводимого сотрудника в отдел:
Const DENY_COMMAND = "Deny"
Команда подтверждения перевода сотрудника:
Const ACCEPT_COMMAND = "Accept"
Команда отмены операции перевода:
Const ROLLBACK_COMMAND = "RollBack"
function dispatchCommand(cmd)
Здесь используется обработка ошибок, поскольку преждевременное завершение функции может привести к «потерям» объектов пользователей. В кавычках это слово потому, что физически удален объект пользователя не будет, лишь перестанет быть видимым. Кроме того, сбой в этой подпрограмме без обработки ошибок может привести к переводу надстройки в противоречивое состояние. Поэтому в самом начале функции используется директива продолжения выполнения сценария, несмотря на возникающие ошибки. В конце анализируется переменная специальная err – если она не равна нулю, ошибки были. В этом случае обрабатываемая команда отменяется с помощью специального метода resetCommand.
on error resume next
Далее команда проверяется на корректность с помощью метода checkIntegrity. Некорректная команда отменяется.
if not checkIntegrity(cmd) then
resetCommand cmd
exit function
end if
Если команда успешно прошла проверку, вызывается ее обработчик. Для каждого типа команд существует свой метод обработки. Далее все они будут рассмотрены.
select case cmd.userMoveID
case START_MOVE_COMMAND:
dispatchStartMoveCommand cmd
case ACCEPT_COMMAND:
dispatchAcceptCommand cmd
case DENY_COMMAND:
dispatchDenyCommand cmd
case ROLLBACK_COMMAND:
dispatchRollbackCommand cmd
default:
resetCommand cmd
end select
if err<>0 then resetCommand cmd
end function
Сначала рассмотрим метод проверки целостности команды. Это важная часть надстройки, которая относится к функциональности сервера. Сами команды создаются на рабочих станциях клиентов (менеджеров по персоналу). Поэтому существует вероятность фальсификации этих объектов. Выполняться же должны только корректные команды. Функция checkIntegrity возвращает значение логического типа. Возвращает истину, если команда прошла проверку, и ложь в противном случае.
function checkIntegrity(cmd)
Функция может работать с не вполне корректными объектами, поэтому возможны ошибки, которые не должны прервать ее выполнение.
On Error resume next
dim owner, executor, stOwner
Изначально предполагаем худшее – команда не прошла проверку.
checkIntegrity = false
Далее получаем объект владельца команды. Поле userMoveExecutor должно указывать на этого же пользователя – менеджера по персоналу, создавшего команду. То есть команда может быть создана с использованием одной учетной записи, а в поле userMoveExecutor установлен указатель на другую. Установить поле легко, поменять владельца объекта – сложнее (для этого требуются права администратора).
stOwner = cmd.get("ntsecuritydescriptor").owner
set owner = getObject("WinNT://" & replace(stOwner,"\", "/"))
set executor = getObject(cmd.userMoveExecutor)
if err <> 0 then exit function
if owner.class = "Group" then
Если владелец объекта команды – группа, пользователь, создавший ее, должен быть членом этой группы.
checkIntegrity = owner.isMember("WinNT://" & info.domainShortName & "/" & executor.samAccountName)
elseif owner.class = "User" then
checkIntegrity = (executor.samAccountName = owner.name)
end if
end function
Метод проверяет целостность объекта. Имеет ли право пользователь пользоваться командой, проверяется при ее выполнении.
Метод «Сброс команды» удаляет ее объект. Единственное исключение – команда начала перемещения, которая содержит в себе объект пользователя. В этом случае перед удалением объект пользователя извлекается в текущую организационную единицу. Поскольку эта ситуация потенциально опасна, профиль пользователя отключается.
Листинг 6. Сброс некорректной команды
function resetCommand(cmd)
On error resume next
dim ou
set ou = getObject(cmd.parent)
if cmd.class = START_MOVE_COMMAND_CLASS then
dim user
for each user in cmd
user.accountDisabled = true
user.setInfo
ou.moveHere user.ADSPath, vbNullString
next
end if
ou.delete cmd.class, cmd.name
end function
Начало перемещения
Перейдем к рассмотрению непосредственных обработчиков команд. Сначала команда начала перемещения. Логика ее работы подробно описана в первой части статьи [2]. Единственный параметр метода – объект команды, которую нужно выполнить. Проверку на целостность к этому моменту команда уже прошла. Здесь используется ряд вспомогательных методов, реализация которых будет рассмотрена позже.
function dispatchStartMoveCommand(cmd)
dim chair, room, whomObject, parentOU
Сначала создается объект стула ожидания в целевом подразделении.
set chair = createChair(cmd)
for each whomObject in cmd
Затем объект пользователя, инкапсулированный в команду, перемещается в объект стула ожидания.
chair.moveHere whomObject.ADSPath, vbNullString
next
set parentOU = getObject(cmd.parent)
Далее подключаемся к комнате ожидания организационной единицы – источника перемещения. Она понадобится для добавления ссылки на перемещенный объект, чтобы облегчить его поиск в случае необходимости отката операции.
set room = getWaitingRoom(parentOU)
for each whomObject in chair
Функция createBackLink создает эту самую обратную ссылку:
createBackLink room, whomObject.ADSPath
В конце объект выполненной команды удаляется.
parentOU.delete START_MOVE_COMMAND_CLASS, "CN=cmd_" & START_MOVE_COMMAND & "_" & whomObject.samAccountName
next
end function
Рассмотрим использованные при обработке дополнительные методы. Первой использовалась функция создания стула ожидания.
function createChair(cmd)
dim chairNumber, room
Свойство userMoveTo команды содержит указатель на организационную единицу – пункт конечного назначения. Указатель на комнату ожидания этого подразделения снова получаем, используя функцию getWaitingRoom.
set room = getWaitingRoom(getObject(cmd.userMoveTo))
Объекты стульев пронумерованы, чтобы избежать совпадений имен. Они называются chair_1, chair_2 и так далее. Метод getNextNumber позволяет получить следующий свободный номер стула в заданной комнате.
chairNumber = getNextNumber(room, CHAIR_CLASS)
Теперь можно создать объект стула. Практически все основные свойства перемещения (инициатор, время и др.) копируются из объекта команды.
set createChair = room.create(CHAIR_CLASS, "CN=chair_" & chairNumber)
createChair.userMoveFrom = cmd.userMoveFrom
createChair.userMoveWho = cmd.userMoveWho
createChair.userMoveComment = cmd.userMoveComment
createChair.userMoveWhen = cmd.userMoveWhen
createChair.userMoveDisabled = cmd.userMoveDisabled
После выполнения следующей строки новый объект стула ожидания будет создан и готов к перемещению в него пользователя.
createChair.setInfo
end function
Далее рассмотрим функцию получения объекта комнаты ожидания, прикрепленного к заданной организационной единице. Если комнаты ожидания нет, метод создает ее. Единственный параметр – целевое подразделение.
function getWaitingRoom(ou)
dim el
Устанавливаем фильтр, чтобы в перебор с помощью цикла for each попали только объекты типа «Комната ожидания» (на самом деле в этой надстройке используется только одна комната ожидания для каждой организационной единицы).
ou.filter = Array(ROOM_CLASS)
for each el in ou
set getWaitingRoom = el
Если комната ожидания найдена, функция возвращает ссылку на нее и завершает работу.
exit Function
next
В противном случае объект комнаты ожидания сначала создается.
set getWaitingRoom = ou.create(ROOM_CLASS, "CN=waiting_room")
getWaitingRoom.setInfo
end function
Также при создании стула использовалась функция определения первого свободного номера объекта заданного типа в конкретном контейнере. Рассмотрим ее подробнее. Функция позволяет получить следующий номер для объектов любого класса (имя типа передается вторым параметром), которые именуются в виде <имя>_<номер>.
function getNextNumber(room, className)
room.Filter = Array(className)
dim i, n
getNextNumber = 0
Реализация достаточно простая. Сначала для контейнера устанавливается фильтр, чтобы в перебор посредством for each попадали только объекты нужного типа. Затем получается номер каждого объекта (функция getUnderlinedNumber). Функция определяет максимальный встреченный номер и возвращает его, увеличив на единицу.
for each i in room
n = getUnderlinedNumber(i.cn)
if n > getNextNumber then getNextNumber = n
next
getNextNumber = getNextNumber + 1
end function
Этот метод использует функцию извлечения из строки имени номера, основываясь на том, что он отделен подчеркиванием. Функция находит символ подчеркивания и пытается преобразовать символы справа от него в число. Это достаточно примитивное решение, поддерживающее только имена с одним подчеркиванием. Однако для целей надстройки этого вполне достаточно.
Листинг 7. Извлечение номера объекта из его имени
function getUnderlinedNumber(src)
dim pos
pos = instr(1, src, "_", vbTextCompare)
if pos > 0 then
getUnderlinedNumber = CInt(right(src, len(src) - pos))
end if
end function
Ну и в конце этой части статьи рассмотрим последнюю вспомогательную функцию создания ссылки на перенесенный объект. Напомню, что ссылка в данной надстройке – объект, содержащий единственное поле – путь к объекту Active Directory. Схема именования используется такая же, как и для стульев ожидания: link_<номер ссылки>. Метод получает два параметра: организационную единицу, откуда осуществляется перемещение, и собственно ссылку. Реализация довольно очевидна – с помощью getNextNumber определяется первый свободный номер, затем создается экземпляр класса ссылки, инициализируется и сохраняется.
Листинг 8. Создание обратной ссылки
function createBackLink(room, backLink)
dim linkNumber
linkNumber = getNextNumber(room, LINK_CLASS)
set createBackLink = room.create(LINK_CLASS, "CN=link_" & linkNumber)
createBackLink.userMoveLink = backLink
createBackLink.setInfo
end function
Заключение
В этой части статьи была начата реализация механизма обработки команд. В следующей части она будет завершена. Остается сделать обработчики остальных типов команд, а также разработать подпрограммы создания новых команд. Также в следующей части будет рассмотрено использование регулярных выражений для упрощения обработки строк.
- Андросов В. Делегирование прав на перемещение учетных записей пользователей в Active Directory. Часть 1. Постановка задачи. //Системный администратор, №3, 2009 г. – С. 16-21.
- Андросов В. Делегирование прав на перемещение учетных записей пользователей в Active Directory. Часть 2. Реализация основных функций. //Системный администратор, №4, 2009 г. – С. 24-30.
- Андросов В. Синхронизация ACL и структуры организации. Часть 3. //Системный администратор», №2, 2008 г. – С. 82-87.
- msdn.microsoft.com.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|