LDAP
![]() |
Тема этой заметки имеет некоторое отношение к компьютерной так называемой "безопасности". |
— Откуда вычеркнули? — остолбенел Коротков.
— Хи. Известно откуда, из списков. Карандашиком — чирк, и готово — хи-кхи. — Старичок сладострастно засмеялся.
— Поз...вольте... Откуда же вы меня знаете?
— Хи. Шутник вы, Василий Павлович.
— Я — Варфоломей, — сказал Коротков и потрогал рукой свой холодный и скользкий лоб, — Петрович.Улыбка на минуту покинула лицо страшного старичка.
М. Булгаков, «Дьяволиада».
LDAP — наиболее распространённый способ хранения информации о пользователях системы, использование которого может потребоваться в ходе реализации вашего проекта. Конечно, таблица users в реляционной БД гораздо удобнее, более того, без неё невозможно обойтись (ведь пользователи — это не только те, кто имеет право входа, но и объекты учёта, наряду с документами, организациями и т. п.), однако хранить в ней пароли крупный заказчик может просто не позволить. И его можно понять: систем, кроме вашей у него много, и пароль к каждой не упомнить.
Итак, при обработке авторизационной формы нам потребуется:
- соединиться с LDAP-сервером;
- выяснить, есть ли на LDAP-сервере учётная запись с заданным логином;
- если есть, то (заодно) импортировать её в БД приложения или освежить данные об этом пользователе;
- проверить, подходит ли заданный пароль: если да, то выдать корректный номер сессии.
Входу в систему в Eludia всегда соответствует действие execute для типа экрана logon. Поэтому все проверки следует производить в процедуре validate_execute_logon, установление номера сессии — в do_execute_logon.
Соединение
Используем модуль Net::LDAP, причём будем предполагать, что параметры подключения описаны при загрузке ядра в perl-секции httpd.conf в виде:
ldap => { host => 'domain_server_hostname', user => '...', password => '...', base => '...', },
Значения user и password должны соответствовать учётной записи с правами чтения учётной записи любого пользователя системы. Попробуем установить соединение:
my $mesg = $ldap -> bind ($preconf -> {ldap} -> {user}, password => $preconf -> {ldap} -> {password}); $mesg -> code && return "Невозможно подключиться к LDAP-серверу";
Проверка наличия записи
Теперь стоит исполнить запрос следующего вида (приведён вариант для AciveDirectory):
$mesg = $ldap -> search ( base => $preconf -> {ldap} -> {base}, filter => "(sAMAccountName=$_REQUEST{login})", ); $mesg -> code && return "Ошибка при поиске учётной записи";
Фильтр может быть установлен на другое поле (см. ниже) и/или содержать дополнительные условия.
Импорт данных о пользователе
Запрос может вернуть 0 или 1 запись, однако для обработки выборки удобно воспользоваться циклом (мы снова используем схему AciveDirectory):
use Encode; use Encode::Byte; foreach my $entry ($mesg -> entries) { $_USER -> {dn} = $entry -> dn (); my $label = $entry -> get_value ('displayName') || ' '; Encode::from_to ($label, 'utf8', 'windows-1251'); my ($f, $i, $o) = split /\s+/, $label; $_USER -> {id} = sql_select_id (users => { -fake => 0, id_role => 1, -login => $_REQUEST {login}, -label => $label, -f => $f, -i => $i, -o => $o, guid => uc unpack ('H*', $entry -> get_value ('objectGUID')), }, ['guid']); } $_USER -> {id} or return "#login#:Учётная запись с таким логином не обнаружена";
Проверка пароля
Чтобы убедиться в том, что переданный нам пароль соответствует найденной записи, придётся попытаться вторично подключиться к тому же LDAP-серверу, на этот раз — от имени найденной записи. Если получится — значит, пароль правильный:
my $mesg = $ldap -> bind ($_USER -> {dn}, password => $_REQUEST {password}); $mesg -> code && return "#password#:Вероятно, вы ошиблись при вводе пароля."; $ldap -> unbind;
Итак, все возможные ошибочние ситуации отработаны в validate_execute_logon, код которой разрезан на предыдущие разделы. Осталось выдать подходящий $_REQUEST {sid} пользователю с номером $_USER -> {id}:
sub do_execute_logon { $_REQUEST {sid} = sql_select_scalar ('SELECT id FROM sessions WHERE id_user = ? AND ip = ?', $_USER -> {id}, $ENV {REMOTE_ADDR}); if (!$_REQUEST {sid}) { $_REQUEST {sid} = sql_select_array ("select floor(rand() * 9223372036854775807)"); sql_do ('DELETE FROM sessions WHERE id_user = ?', $_USER -> {id}); sql_do ("INSERT INTO sessions (id, id_user, ip) VALUES (?, ?, ?)", $_REQUEST {sid}, $_USER -> {id}, $ENV {REMOTE_ADDR}); } delete $_REQUEST {type}; delete $_REQUEST {login}; delete $_REQUEST {password}; return; }
Особенности некоторых схем LDAP
MS Active Directory
- Уникальное поле
- objectGUID (бинарные значения!!!)
- Поле логина
- sAMAccountName
- Логин администратора
- cn=Administrator,cn=Users,dc=host,dc=domain,dc=ru
(вообще для bind вместо DN можно передавать строки вида 'DOMAIN_NAME\user_login') - Принадлежность к группе
- (memberOf=$group_dn)
- Фильтр по незаблокированным учётным записям пользователей
- (&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
(вы не поверите, но 1.2.840.113556.1.4.803 в LDAP-язычке — это & в С)
OpenLDAP / Samba
- Уникальное поле
- sambaSID
- Поле логина
- uid
- Логин администратора
- cn=admin,dc=host,dc=domain,dc=ru
Oracle Internet Directory
- Поле логина
- uid
- Уникального поля НЕТ
- Вместо него приходится использовать всё тот же uid.
- Принадлежность к группе
- DN'ы членов — множественные значения поля uniquemember записи группы.