Создаем пользователя AD через веб-интерфейс

в 7:57, , рубрики: active directory, ldap, php, Веб-разработка, метки: , ,

Создаем пользователя AD через веб интерфейс

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

Если сказать очень упрощенно, то именно этим AD и занимается — хранит информацию о сотрудниках и выдает ее разным приложениям и сервисам как база данных, допустим, MySQL. Для настройки и управления AD тоже может быть целый отдельный сотрудник, но реализация доступа через веб-интерфейс предсказуемо ляжет на веб-программиста. И тут могут возникнуть определенные трудности.

Если осознать общие принципы работы AD и реализовать, например, вывод структуры компании в браузер для человека, знакомого с базами данных, будет достаточно просто, то вот организовать ввод уже посложнее. Дело в том, что настоящие специалисты по AD в работе используют десктопные приложения, консоль и VBScript (кажется), и манипулируют пользователями, как объектами, а php-программисту доступны только некоторые функции и передача пользователя в виде массива данных. Определить, какие ключи нужно записать, и в каком формате должны быть значения — задача не самая тривиальная. Поскольку в сети для функции ldap_add() есть всего один пример использования.

Поэтому актуальным является создание еще одного примера — более подробного и со всем «обвесом», чтобы проверить работу можно было без всяких существенных правок.

Будем считать, что сервер у нас уже настроен, доступы получены, и нашему пользователю предоставлены права на запись. Первый этап — подключение — описан в руководствах достаточно подробно:

/* Connection parameters */
	$ldap_host = "xxx.local"; # domain controller or ip-address
	$ldap_port = "389"; # port for connection
	$ldap_user ="admin@xxx.local"; # AD-user
	$ldap_pass = "123321";# AD-user's password
   
/* Open connection */
	$connect = ldap_connect( $ldap_host, $ldap_port);
	ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
	ldap_set_option($connect, LDAP_OPT_REFERRALS, 0);
	$bind = ldap_bind($connect, $ldap_user, $ldap_pass);

Теперь проверим вывод и построим иерархическое дерево подразделений нашей компании:

/* Hierarchical tree */
	function build_tree($connect, $ou = '', $base_dn = 'OU=!Users_all,DC=xxx,DC=local')
	{
		$specific_dn = $base_dn;
		if ('' != $ou) {$specific_dn = 'OU='.$ou.','.$base_dn;}
		
		$filter = 'ou=*';
		
		$justthese = array("ou");

		$sr = ldap_list($connect, $specific_dn, $filter, $justthese);

		$info = ldap_get_entries($connect, $sr);
		
		$result = '';

		for ($i=0; $i < $info["count"]; $i++) 
		{
			$specific_ou = $info[$i]["ou"][0];
			$result .= '<ul>';
				$result .= '<li><input type="radio" name="dep" value="OU='.$specific_ou.','.$specific_dn.'" title="'.$specific_ou.'" /> '.$specific_ou;
				$result .= build_tree($connect, $specific_ou, $specific_dn);
				$result .= '</li>';
			$result .= '</ul>';
		}
			
		return $result;
	}

В вывод сразу добавляем элементы формы, которые понадобятся нам на следующем этапе. Функция вызывает саму себя, что позволит пройти любой уровень вложенности.
Если все работает, напишем простую форму добавления сотрудника:

/* Output */
	echo '
	<form method="POST" action="'.$_SERVER['REQUEST_URI'].'">
	
		<h3>Сотрудник</h3>	
		
		<input type="hidden" name="new_emp" value="1" />
		
		<input type="text" name="family_name" value="Фамилия" size="20" />
		<input type="text" name="first_name" value="Имя" size="20" />
		<input type="text" name="fathers_name" value="Отчество" size="20" />
		<br/>
		<h3>Должность</h3>
		<input type="text" name="position" value="Врач" size="40" />
		<br/>
		<h3>Номер мобильного телефона</h3>
		<input type="text" name="tel_num" value="+7(999) 999-9999" size="20" />
		
		<h3>Подразделение</h3>
		
		<div class="tree">
			'.build_tree($connect).'
		</div>
		
		<br/>
		<input type="submit" value="Создать" />
	
	</form>';	

А теперь — важная часть: проверка и обработка ввода

/* Input check */

	$errors = '';
	
	/*
	EXAMPLE OF CORRECT POST
	new_emp: 1
	family_name: Фамилия
	first_name: Имя
	fathers_name: Отчество
	position: Врач
	tel_num: +7(999) 999-9999
	dep: Группа по организации Лабораторной диагностики
	*/
	
	if(isset($_POST['new_emp']))
	{
		foreach($_POST as $key => $value)
		{
			if('' == $value) {$errors .= 'Поле '.$key.' не заполнено<br/>'; break;} 
			else {$$key = $value;}
		}
		if(!isset($_POST['dep'])) {$errors .= 'Поле «Подразделение» не заполнено<br/>';}
		
		/*  Creating employee */
		if('' == $errors)
		{
			$trans_family_name = translit($family_name);
			$trans_first_name = translit($first_name);
			$trans_fathers_name = translit($fathers_name);
			$account = $trans_family_name.substr($trans_first_name, 0, 1).substr($trans_fathers_name, 0, 1);
			$dn = 'CN='.$family_name.' '.$first_name.' '.$fathers_name.','.$dep;
			
			//Getting manager
			$filter = '(&(objectClass=user)(objectCategory=PERSON))';
			$sr = ldap_search($connect, $dep, $filter);
			$info = ldap_get_entries($connect, $sr);
			//echo Arratomy($info);
			$manager = $info[0]['manager'][0];
			
			$department = explode(',', $dep);			
			$department = str_replace('OU=', '', $department[0]);
			
			$newuser['cn'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser["objectclass"][0] = "top";
			$newuser["objectclass"][1] = "person";
			$newuser["objectclass"][2] = "organizationalPerson";
			$newuser["objectclass"][3] = "user";
			$newuser['displayname'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser['givenname'] = $first_name.' '.$fathers_name;
			$newuser['name'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser['sn'] = $family_name;
			$newuser['description'] = $position;
			$newuser['mobile'] = $tel_num;
			$newuser['samAccountName'] = $account;
			$newuser['userPrincipalName'] = $account.'@xxx.local';
			$newuser['title'] = $position;
			$newuser['department'] = $department;
			$newuser['manager'] = $manager;
			$newuser['useraccountcontrol'] = 0x0002; // Account disabled
			$newuser['pwdLastSet'] = 0; // User must change password
			$newuser['userPassword'] = '123321';
			
			//echo '<h2>New AD user</h2>'.Arratomy($newuser);
					
			$result = ldap_add($connect, $dn, $newuser); 			
			
			if($result) {echo '<div class="success">Сотрудник создан</div>';} else {$errors = ldap_error($connect);}
						
		}	
		if('' != $errors) {echo '<h2 style="color:red;">Ошибки</h2><div class="errors">'.$errors.'</div>';}
	}

Конечно, может случиться так, что вам не потребуется заполнять столько атрибутов пользователя, или наоборот — что их потребуется намного больше. Однако, такой набор уже гораздо яснее показывает, с чем придется столкнуться.
Критически важными являются переменные:

	$dn = 'CN=Фамилия Имя Отчество,OU=!Users_all,DC=xxx,DC=local'; // Distinguished name
	$newuser['cn'] = $family_name.' '.$first_name.' '.$fathers_name;
	$newuser["objectclass"][0] = "top";
	$newuser["objectclass"][1] = "person";
	$newuser["objectclass"][2] = "organizationalPerson";
	$newuser["objectclass"][3] = "user";
	$newuser['samAccountName'] = $account;
	$newuser['userPrincipalName'] = $account.'@xxx.local';

Без всего остального, как правило, можно обойтись.

Собираем все вместе (+ стили и служебные функции):


<style>

.tree {height:500px; overflow-y:scroll; border:1px solid #ccc;}
	.tree li:hover {cursor:pointer;}
	.tree input {display:inline;}
.errors {margin:10px 0px; border: 1px dotted red; padding:10px;}
.success {margin:10px 0px; border: 1px dotted green; padding:10px;}

</style>

<?php

//ini_set('display_errors','On'); error_reporting(E_ALL | E_STRICT);

/* Service functions */
	function Arratomy($array)
	{
		$result = "n".'<span>Array (<b>'.sizeof($array).'</b> items)</span>'."n".'<ol>'."n";
		foreach ($array as $key => $value)
		{
			$result .= '<li>';
			if(is_array($value)) {$result .= '<b>'.$key.'</b>: '.Arratomy($array[$key]);}
			else {$result .= '<b>'.$key.'</b>: '.$value.'</br>'."n";}
			$result .= '</li>';
		}
		$result .= '</ol>'."n";
		return $result;
	}

	function translit($text) 
	{
		$alphabet_ru = array
			(
				'ё','й','ц','у','к','е','н','г', 'ш','щ','з','х','ъ','ф','ы','в', 'а','п','р','о','л','д','ж','э', 'я','ч','с','м','и','т','ь','б','ю',
				'Ё','Й','Ц','У','К','Е','Н','Г', 'Ш','Щ','З','Х','Ъ','Ф','Ы','В', 'А','П','Р','О','Л','Д','Ж','Э', 'Я','Ч','С','М','И','Т','Ь','Б','Ю',
				' '
			);
		 
		$alphabet_eng = array
			(
				'e','y','ts','u','k','e','n','g', 'sh','sch','z','h','','f','y','v', 'a','p','r','o','l','d','j','e', 'ya','ch','s','m','i','t','','b', 'yu',
				'E','Y','Ts','U','K','E','N','G', 'Sh','Sch','Z','H','','F','Y','V', 'A','P','R','O','L','D','J','E', 'Ya','Ch','S','M','I','T','','B', 'Yu',
				'_'
			);
		 
		return str_replace($alphabet_ru, $alphabet_eng, $text);
	}
	
 
/* Connection parameters */
	$ldap_host = "xxx.local"; # domain controller or ip-address
	$ldap_port = "389"; # port for connection
	// $filter = "ou=*"; # search any department // overridden in the function
	// $base_dn = "OU=!Users_all,DC=xxx,DC=local"; # specific OU // overridden in the function
	$ldap_user ="admin@xxx.local"; # AD-user
	$ldap_pass = "123321";# AD-user's password
   
/* Open connection */
	$connect = ldap_connect( $ldap_host, $ldap_port);
	ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
	ldap_set_option($connect, LDAP_OPT_REFERRALS, 0);
	$bind = ldap_bind($connect, $ldap_user, $ldap_pass);

/* Input check */

	//echo '<h2>POST</h2>'.Arratomy($_POST);

	$errors = '';
	
	/*
	EXAMPLE OF CORRECT POST
	new_emp: 1
	family_name: Фамилия
	first_name: Имя
	fathers_name: Отчество
	position: Врач
	tel_num: +7(999) 999-9999
	dep: Группа по организации Лабораторной диагностики
	*/
	
	if(isset($_POST['new_emp']))
	{
		foreach($_POST as $key => $value)
		{
			if('' == $value) {$errors .= 'Поле '.$key.' не заполнено<br/>'; break;} 
			else {$$key = $value;}
		}
		if(!isset($_POST['dep'])) {$errors .= 'Поле «Подразделение» не заполнено<br/>';}
		
		/*  Creating employee */
		if('' == $errors)
		{
			$trans_family_name = translit($family_name);
			$trans_first_name = translit($first_name);
			$trans_fathers_name = translit($fathers_name);
			$account = $trans_family_name.substr($trans_first_name, 0, 1).substr($trans_fathers_name, 0, 1);
			$dn = 'CN='.$family_name.' '.$first_name.' '.$fathers_name.','.$dep;
			
			//Getting manager
			$filter = '(&(objectClass=user)(objectCategory=PERSON))';
			$sr = ldap_search($connect, $dep, $filter);
			$info = ldap_get_entries($connect, $sr);
			//echo Arratomy($info);
			$manager = $info[0]['manager'][0];
			
			$department = explode(',', $dep);			
			$department = str_replace('OU=', '', $department[0]);
			
			$newuser['cn'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser["objectclass"][0] = "top";
			$newuser["objectclass"][1] = "person";
			$newuser["objectclass"][2] = "organizationalPerson";
			$newuser["objectclass"][3] = "user";
			$newuser['displayname'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser['givenname'] = $first_name.' '.$fathers_name;
			$newuser['name'] = $family_name.' '.$first_name.' '.$fathers_name;
			$newuser['sn'] = $family_name;
			$newuser['description'] = $position;
			$newuser['mobile'] = $tel_num;
			$newuser['samAccountName'] = $account;
			$newuser['userPrincipalName'] = $account.'@xxx.local';
			$newuser['title'] = $position;
			$newuser['department'] = $department;
			$newuser['manager'] = $manager;
			$newuser['useraccountcontrol'] = 0x0002; // Account disabled
			$newuser['pwdLastSet'] = 0; // User must change password
			$newuser['userPassword'] = '123321';
			
			//echo '<h2>New AD user</h2>'.Arratomy($newuser);
					
			$result = ldap_add($connect, $dn, $newuser); 			
			
			if($result) {echo '<div class="success">Сотрудник создан</div>';} else {$errors = ldap_error($connect);}
						
		}	
		if('' != $errors) {echo '<h2 style="color:red;">Ошибки</h2><div class="errors">'.$errors.'</div>';}
	}
	
/* Hierarchical tree */
	function build_tree($connect, $ou = '', $base_dn = 'OU=!Users_all,DC=xxx,DC=local')
	{
		$specific_dn = $base_dn;
		if ('' != $ou) {$specific_dn = 'OU='.$ou.','.$base_dn;}
		
		$filter = 'ou=*';
		
		$justthese = array("ou");

		$sr = ldap_list($connect, $specific_dn, $filter, $justthese);

		$info = ldap_get_entries($connect, $sr);
		
		$result = '';

		for ($i=0; $i < $info["count"]; $i++) 
		{
			$specific_ou = $info[$i]["ou"][0];
			$result .= '<ul>';
				$result .= '<li><input type="radio" name="dep" value="OU='.$specific_ou.','.$specific_dn.'" title="'.$specific_ou.'" /> '.$specific_ou;
				$result .= build_tree($connect, $specific_ou, $specific_dn);
				$result .= '</li>';
			$result .= '</ul>';
		}
			
		return $result;
	}
	
/* Output */
	echo '
	<form method="POST" action="'.$_SERVER['REQUEST_URI'].'">
	
		<h3>Сотрудник</h3>	
		
		<input type="hidden" name="new_emp" value="1" />
		
		<input type="text" name="family_name" value="Фамилия" size="20" />
		<input type="text" name="first_name" value="Имя" size="20" />
		<input type="text" name="fathers_name" value="Отчество" size="20" />
		<br/>
		<h3>Должность</h3>
		<input type="text" name="position" value="Врач" size="40" />
		<br/>
		<h3>Номер мобильного телефона</h3>
		<input type="text" name="tel_num" value="+7(999) 999-9999" size="20" />
		
		<h3>Подразделение</h3>
		
		<div class="tree">
			'.build_tree($connect).'
		</div>
		
		<br/>
		<input type="submit" value="Создать" />
	
	</form>';	

	
/* Close connection */	
	ldap_close($connect);

?>

Автор: lutov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js