LDAP

Материал из Eludia
Перейти к: навигация, поиск
Muzzle.jpg Тема этой заметки имеет некоторое отношение к компьютерной так называемой "безопасности".

— Откуда вычеркнули? — остолбенел Коротков.
— Хи. Известно откуда, из списков. Карандашиком — чирк, и готово — хи-кхи. — Старичок сладострастно засмеялся.


— Поз...вольте... Откуда же вы меня знаете?


— Хи. Шутник вы, Василий Павлович.


— Я — Варфоломей, — сказал Коротков и потрогал рукой свой холодный и скользкий лоб, — Петрович.

Улыбка на минуту покинула лицо страшного старичка.

М. Булгаков, «Дьяволиада».

LDAP — наиболее распространённый способ хранения информации о пользователях системы, использование которого может потребоваться в ходе реализации вашего проекта. Конечно, таблица users в реляционной БД гораздо удобнее, более того, без неё невозможно обойтись (ведь пользователи — это не только те, кто имеет право входа, но и объекты учёта, наряду с документами, организациями и т. п.), однако хранить в ней пароли крупный заказчик может просто не позволить. И его можно понять: систем, кроме вашей у него много, и пароль к каждой не упомнить.

Итак, при обработке авторизационной формы нам потребуется:

  1. соединиться с LDAP-сервером;
  2. выяснить, есть ли на LDAP-сервере учётная запись с заданным логином;
  3. если есть, то (заодно) импортировать её в БД приложения или освежить данные об этом пользователе;
  4. проверить, подходит ли заданный пароль: если да, то выдать корректный номер сессии.

Входу в систему в 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 записи группы.