Содержание


Работаем с AD на PHP

Чтение данных. Часть 1. Подключение к AD, запрос и обработка данных

Comments

Серия контента:

Этот контент является частью # из серии # статей: Работаем с AD на PHP

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Работаем с AD на PHP

Следите за выходом новых статей этой серии.

Для выполнения основных операций по работе с AD, таких как добавление или удаление пользователя, изменение данных или членства в группах, и в особенности для массовых операций (например, сформировать список всех пользователей по отделам) вовсе не обязательно изучать Visual Basic или PowerShell - для этого достаточно знания PHP (а также наличия пользователя с необходимыми правами).

Часто используемые сокращения:

  • AD - Active Directory (служба каталогов);
  • LDAP - облегченный протокол доступа к каталогу (lightweight directory access protocol);
  • DN - отличительное имя (distinguished name).

В первых частях цикла, опубликованных в июне месяце ([1], [2], [3]) рассказывалось о том, как прочитать данные сервера AD, обращаясь к нему как к обычному LDAP-серверу с помощью стандартной программы ldapsearch и скрипта, написанного на языке Bourne Shell. Надо сказать, Bourne Shell не слишком приспособлен для такой работы: даже для достаточно простой операции формирования текстового файла из двух колонок приходится идти на весьма нетривиальные ходы. Поэтому совершенно естественно попробовать переписать его на языке высокого уровня, например, на PHP.

Конфигурационный файл

Для работы скрипта используется практически тот же самый конфигурационный файл. Его содержание приведено в Листинге 1.

Листинг 1. Конфигурационный файл скрипта phpldapread.php
#LDAP сервер для подключения
ldap_server=10.54.200.1
#Base DN для подключения
ldap_basedn="dc=shelton,dc=int"
#Bind DN для подключения
ldap_binddn=ldapread@SHELTON.INT
#Пароль для пользователя, от имени которого будет выполняться подключение
ldap_password="cXdlcnR5YXNkZgo 1"
#Фильтр отбора записей. Он означает: отобрать объекты типа Пользователь,
# у которых не установлено свойство "Заблокировать учетную запись"
ldap_common_filter="(&(!(userAccountControl:1.2.840.113556.1.4.803:=2))
(sAMAccountType=805306368))"
#Игнорировать перечисленных пользователей - это системные объекты
ignore_list="SQLAgentCmdExec,SMSService,SMSServer_001, wsus"
#Каталог, куда будет сохранен файл
etcdir=/tmp
#Имя файла со списком
sarglist=sargusers

Зависимости, вспомогательные функции

Для работы скрипта потребуются дополнительные компоненты pear-Config и pear-Console_Getopt, а также расширение языка php-ldap. Pear-Config потребуется для чтения конфигурационного файла, pear-Console_Getopt - для разбора параметров командной строки. Надо сказать, рассматривается не весь скрипт: такие вопросы, как чтение конфигурационного файла, отображение справки или разбор командной строки являются вопросами уже достаточно хорошо описанными, поэтому соответствующие функции будут опущены, полную версию скрипта можно скачать с [4]. Рассмотрено будет только то, что непосредственно относится к чтению данных из AD, как сервера LDAP, и некоторые нестандартные вспомогательные функции.

Функция обратного преобразования пароля приведена в Листинге 2. Вся роль так называемой "защиты" - предотвратить случайную утечку (ту, что называется eyedropper) и не более того.

Листинг 2. Функция обратного преобразования пароля.
/*
 * Обратное преобразование пароля
 * @param       string  $converted             преобразованный пароль
 * @return       string  $passwd                 пароль в текстовой форме
 */
function demux_passwd($converted)
 {
   $_conved = explode(" ", $converted);
   $_passwd = "";
   if ( $_conved[1] != 0 )
     for (;$_conved[1] != 0; $_conved[1]--)
        { $_conved[0] = $_conved[0] . "="; }
   $_passwd = base64_decode($_conved[0]);
   return rtrim($_passwd);
 }

Ничего особо интересного, конечно же, здесь нет: как уже было сказано в предыдущих частях, в конфигурационном файле хранится пароль, преобразованный в base64, заполнители отброшены и заменены числом. Эта функция выполняет обратное преобразование.

Функция перекодировки из UTF-8 в KOI8-R приведена в Листинге 3. Необходимость в этой функции возникает вследствие того, что консоль во FreeBSD не использует UTF-8.

Листинг 3. Функция перекодировки строки из UTF-8 в KOI8-R
/*
 * Перекодировать строку из UTF-8 в KOI8-R
 * @param       string  $source               строка в кодировке UTF-8
 * @return       string  $dest                   строка в кодировке KOI8-R
 */
function _from_utf8($source)
 {
   $converted = iconv("UTF-8", "KOI8-R", $source);
   return($converted);
 }

Кроме того, используется совершенно неинтересная функция safe_logger, в задачу которой входит вывод сообщений в лог или на консоль с завершением или без завершения работы скрипта. Все эти функции сохранены в файле utils.php.

Подключение к AD

Для подключения к AD используется функция ldap_server_connect, приведенная в Листинге 4. Функция выполняет все операции по подключению и возвращает идентификатор соединения для работы с сервером. Функция сохранена в отдельный файл ldapquery.php

Листинг 4. Функция подключения к серверу AD
require_once $PATH_LIB."/utils.php";
/*
 * Подключение к серверу LDAP
 * @param     array          $_config      массив параметров конфигурации
 * @return    resource       $ldapconn     идентификатор соединения с LDAP-сервером
 */
function ldap_server_connect($_config)
 {
   // Получить пароль в текстовом виде
   $_ldap_pwd = demux_passwd($_config["root"]["ldap_password"]);
   // Установить соединение с сервером
   if (!$ldapconn = ldap_connect($_config["root"]["ldap_server"]))
     safe_logger(sprintf("Невозможно подключиться к LDAP-серверу %s",

                         $_config["root"]["ldap_server"]), "DIE");
   // Для подключения к AD Windows 2003 и выше нужно установить эти опции
   ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
   ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
   // Авторизоваться на сервере
   ldap_bind($ldapconn, $_config["root"]["ldap_binddn"], $_ldap_pwd);
   return $ldapconn;
 }

На что хотелось бы обратить внимание здесь.

Во-первых, опции LDAP_OPT_PROTOCOL_VERSION ("версия протокола") и LDAP_OPT_REFERRALS ("отключить реферальные ссылки") должны быть обязательно установлены соответственно в 3 и 0 - без них можно увидеть странное: авторизация на сервере пройдет, но любой поиск будет возвращать ровно нуль записей.

Во-вторых, Bind DN должен быть задан в точности как в конфигурационном файле и никаким другим образом. Неверным будет в том числе и указание полного DN.

Запрос данных из AD

Для запроса данных из AD разработана отдельная функция ldap_data_query. Сделано это в основном потому, что данные, содержащие не-ASCII символы (а таких в нормальном AD большинство), хранятся в кодировке UTF-8. Поскольку консоль FreeBSD ограниченно поддерживает UTF-8, пришлось пойти на некоторые преобразования.

Отбор данных из AD проводится функцией ldap_search, которая принимает в числе других параметров одномерный массив с атрибутами, которые необходимо получить. Но для того, чтобы указать, следует ли перекодировать значение этого атрибута, функция получает двумерный массив, в котором каждый элемент сам есть массив, состоящий из элементов с индексами name и recode.

Вид массива атрибутов, который получает на входе функция, показан в Листинге 5 (частично).

Листинг 5. Массив параметров, передаваемый функции запроса данных.
array(2) {
  [0]=>
  array(2) {
    ["name"]=>
    string(2) "cn"
    ["recode"]=>
    string(4) "true"
  }
...
}

Собственно функция запроса данных приведена в Листинге 6.

Листинг 6. Функция запроса данных из AD.
require_once $PATH_LIB."/utils.php";
require_once $PATH_LIB."/ldapconnect.php";
/*
 * Запросить данные с сервера LDAP
 * @param   array       $_config        Массив с конфигурационными данными
 * @param   resource  $ldapconn      Идентификатор подключения к серверу LDAP
 * @param   array       $attribute      Массив атрибутов для запроса из LDAP
 * @return  array         $ldapdata     Данные с сервера LDAP
 */
function ldap_data_query($_config, $ldapconn, $attribute)
 {
   $oneadd = array();
   $myrecode = array();
   $myattrs = array();
   // Для запроса данных мы создаем одномерный массив
   foreach ($attribute as $oneattr)
    $myattrs[] = $oneattr["name"];
   // Запросим данные, используя общий фильтр отбора из конфигурационного файла
   $result = ldap_search($ldapconn, $_config["root"]["ldap_basedn"],
                                $_config["root"]["ldap_common_filter"], $myattrs);
   // Прочитаем все отобранные записи
   $info = ldap_get_entries($ldapconn, $result);
   // Выведем их количество в лог
   safe_logger(sprintf("Read %d records from server %s",
                        $info["count"], $_config["root"]["ldap_server"]), "");
   // Создадим двумерный массив с выходными данными
// Каждый элемент массива есть массив, в элементах которого ключ - имя атрибута,
// а данные - значение атрибута; перекодированные, если надо
   for ($i = 0; $i < $info["count"]; $i++)
    {
      for ($j = 0, $k = count($attribute); $j < $k; $j++)
       {
         $myattr = $attribute[$j]["name"];
         if (isset($info[$i][$myattr]))
           {
             if ($attribute[$j]["recode"] == "true")
               $myrecode[$myattr] = _from_utf8($info[$i][$myattr][0]);
              else
                $myrecode[$myattr] = $info[$i][$myattr][0];
           }
          else
            $myrecode[$myattr] = "";
         $oneadd[$i] = $myrecode;
       }
    }
   return $oneadd;
 }

Из двумерного массива параметров формируется одномерный для функции ldap_search потом запрашиваются данные. Данные возвращаются в виде массива, каждый элемент которого имеет вид, приведенный в Листинге 7.

Листинг 7. Один элемент массива данных, возвращаемого функцией ldap_get_entries.
  [0]=>
  array(6) {
    ["cn"]=>
    array(2) {
      ["count"]=>
      int(1)
      [0]=>
      string(13) "Administrator"
    }
    [0]=>
    string(2) "cn"
    ["samaccountname"]=>
    array(2) {
      ["count"]=>
      int(1)
      [0]=>
      string(13) "Administrator"
    }
    [1]=>
    string(14) "samaccountname"
    ["count"]=>
    int(2)
    ["dn"]=>
    string(43) "CN=Administrator,CN=Users,DC=shelton,DC=net"
  }

Как видно, это даже не двумерный, а трехмерный массив. На первом уровне - запрошенные данные, на втором - атрибуты одного объекта, на третьем - строки многострочного атрибута, которым на всякий случай считаются все строковые атрибуты. Также в каждом элементе первого уровня присутствует элемент второго уровня dn, который содержит полный DN данного объекта - это нам очень пригодится в дальнейшем. Выходной же массив куда проще, вид одного элемента приведен на Листинге 8. Здесь нарочно использован объект с не-ASCII данными, чтобы показать тот факт, что данные были перекодированы.

Листинг 8. Элемент выходного массива.
  [8]=>
  array(2) {
    ["cn"]=>
    string(11) "Просто Юзер"
    ["samaccountname"]=>
    string(10) "prostouser"
  }

Для чего так подробно рассматриваются входные и выходные данные этой функции? Потому что фактически вся работа основного скрипта (который будет рассмотрен в следующей части статьи) будет сведена к подготовке ее вызова и последующей обработке сформированного ею массива.

Заключение

Как видно из данной статьи, PHP значительно упрощает работу с LDAP-сервером, позволяя отказаться от зубодробительных конструкций, связанных с хранением данных во временных файлах, заменяя их куда более удобным представлением массивов в памяти, позволяя "на ходу" перекодировать в другую кодовую страницу, и значительно облегчая отладку скрипта.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=856600
ArticleTitle=Работаем с AD на PHP: Чтение данных. Часть 1. Подключение к AD, запрос и обработка данных
publish-date=01292013