PHP авторизация LDAP через Active Directory для Asterisk CDR Viewer

В этой статье мы прикрутим авторизацию через Active Directory к Asterisk CDR Viewer используя PHP скрипт. Это решение актуально для авторизации корпоративных пользователей, которым нужно предоставить доступ к какому-то web ресурсу внутри сети на основе присутствия пользователя в группе в домене AD. Вы можете легко переделать скрипт под свои нужны и использовать для доступа к подобным сервисам или страничкам.

Для использования нужных функций нам понадобится установить модуль php-ldap.
Далее мы создадим группу в AD с именем Статистика Asterisk и включим в неё всех пользователей, которым необходим доступ.

Итак, сначала создадим файл ldap.php в папке Asterisk CDR Viewer. В нем опишем настройки доступа к AD серверу:
<?php
//IP адрес сервера AD
$ldaphost = "192.168.0.100";
//Порт
$ldapport = "389";
//Путь к группе в которой должна быть учетка сотрудника,
//что бы пройти аутентификацию.
$memberof = "cn=Статистика Asterisk,ou=CompanyName,dc=domain,dc=local";
//Откуда начинаем искать
$base = "dc=domain,dc=local";
//Фильтр по которому будем аутентифицировать пользователя
$filter = "sAMAccountName=";
//Ваш домен, обязательно с собакой впереди.
$domain = "@domain.local";
?>
Чтобы при обновлении страницы не приходилось проходить авторизацию повторно мы сохраним данные в сессии пользователя. Для этого я использовал класс AuthClass который взял отсюда и внес свои изменения.

Для проверки авторизации используется функция login(), которая получает логин и пароль при заполнении формы. В случае ввода правильных данных происходит переадресация на основой файл index.php. За авторизацию у нас будет отвечать файл auth.php в той же папке:
<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Статистика звонков IP телефонии</title>
      <link rel="stylesheet" href="img/auth.css">
</head>
<body>

<?php
session_start(); //Запускаем сессии
/**
 * Класс для авторизации
 * @author дизайн студия ox2.ru
 */
class AuthClass {
    /**
     * Проверяет, авторизован пользователь или нет
     * Возвращает true если авторизован, иначе false
     * @return boolean
     */
    public function isAuth() {
        if (isset($_SESSION["is_auth"])) { //Если сессия существует
            return $_SESSION["is_auth"]; //Возвращаем значение переменной сессии is_auth (хранит true если авторизован, false если не авторизован)
        }
        else return false; //Пользователь не авторизован, т.к. переменная is_auth не создана
    }
    
    /**
     * Авторизация пользователя
     * @param string $login
     * @param string $passwors
     */
    public function auth() {
      //проверяем авторизацию в функции login()
        if (login() != false) {
          $_SESSION["is_auth"] = true; //Делаем пользователя авторизованным
          return true;
        }

        else { //Логин и пароль не подошел
            return false;
        }
    }
    
    /**
     * Метод возвращает логин авторизованного пользователя
     */
    public function getLogin() {
        if ($this->isAuth()) { //Если пользователь авторизован
            return $_SESSION["fullname"]; //Возвращаем логин, который записан в сессию
        }
    }
    
    public function out() {
        $_SESSION = array(); //Очищаем сессию
        session_destroy(); //Уничтожаем
    }
} /* class AuthClass ends */

$auth = new AuthClass();

if (!empty($_POST['username']) && !empty($_POST['password']))  //Если логин и пароль были введены
      {
        $auth->auth(); //проверяем авторизацию
      }

if (isset($_GET["is_exit"])) { //Если нажата кнопка выхода
    if ($_GET["is_exit"] == 1) {
        $auth->out(); //Выходим
        header("Location: auth.php?is_exit=0"); //Редирект после выхода
    }
}

if ($auth->isAuth()) { // Если пользователь авторизован
    /* Здесь выполняется код при успешной авторизации пользователя */
    header("Location: /index.php"); //перенаправляем его на главную страницу скрипта Asterisk CDR Viewer
}
else { //Если не авторизован, показываем форму ввода логина и пароля
?>
<div class="login-page">
<div class="form">
<form class="login-form" method="post" action="auth.php" >
      Авторизуйтесь<br>для доступа к статистике</p>
      <input type="text" placeholder="Имя в домене" id="username" name="username"/>
      <input type="password" placeholder="Пароль" type="password" name="password">
      <input type="submit" value="Войти" name="log_in" >
      <?php if (!empty($login_error)) {echo "<div style="."color:red;".">".$login_error."</div>";}  ?>
</form>
</div>
</div>
<?php } ?>

<?php
//Проверяем данные пользователя, используя LDAP
function login () {
  require_once "ldap.php"; // Конфиг для подключения к ldap
  global $login_error; // Сюда запишем текст ошибки, если авторизация не пройдена
  $username = $_POST['username'];
  $login = $_POST['username'].$domain;
  $password = $_POST['password'];
  //подсоединяемся к LDAP серверу
  $ldap = ldap_connect($ldaphost,$ldapport) or die('Cannot connect to LDAP Server.');
  //Включаем LDAP протокол версии 3
  ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3) or die('Unable to set LDAP protocol version');
  //Отключаем обработку рефералов для ldap v3
  ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0) or die('Unable to set LDAP OPT REFERRALS');

       if ($ldap) /* Получаем данные из AD */
            {
             // Пытаемся войти в LDAP при помощи введенных логина и пароля
             $bind = ldap_bind($ldap,$login,$password);
             if ($bind) //Привязка LDAP прошла успешно!
                  {
                   // Проверим, является ли пользователь членом указанной группы и не отключен ли он.
                   $result = ldap_search($ldap,$base,"(&(memberOf=".$memberof.")(".$filter.$username.")(!(userAccountControl:1.2.840.113556.1.4.803:=2)))");
                    if (!$result) {
                      $login_error = 'Ошибка обращения к LDAP.';
                      return false;
                    }
                   // Получаем количество результатов предыдущей проверки
                   $result_ent = ldap_get_entries($ldap,$result);
                    if (!$result_ent)
                    {
                      $login_error = 'Результатов проверки получить не удалось';
                      return false;
                    }
                   }
             else
                  {
                   $login_error = 'Вы ввели неправильный <br>логин или пароль';
                   return false;
                  }
            ldap_close($ldap); /* Закрываем соединение */
            }
      
       /* Смотрим результаты */
       // Если пользователь найден, т.е. результатов больше 0 (1 должен быть)
       if ($result_ent['count'] != 0)
             {
            // тут код в случае если авторизации пройдена
              $fullname = $result_ent[0]["displayname"][0]; //полное имя пользователя
              $_SESSION["fullname"] = $fullname; //сохраняем в переменной сессии для отображения
              return true;
            exit;
            }
       else
            {
             $login_error = 'К сожалению, вам доступ закрыт.';
             return false;
            }
}
?>
</body>
</html>
В файл index.php (Asterisk CDR Viewer) вверху нужно добавить код, который будет проверять авторизован ли пользователь (смотреть переменную сессии) и отправлять на форму ввода имени и пароля в случае, если пользователь не авторизован (переменная сессии не установлена).
//Проверяем, авторизацию пользователя
session_start();
if(!isset($_SESSION["is_auth"]))
{
 //если пользователь не авторизован отправляем его на страницу авторизации
 header("Location: auth.php");
 exit;
}
Ну и стили для формы авторизации у меня получились такие:
./img/auth.css

.login-page {
  width: 360px;
  padding: 8% 0 0;
  margin: auto;
}
.form {
  position: relative;
  z-index: 1;
  background: #FFFFFF;
  max-width: 360px;
  margin: 0 auto 100px;
  padding: 45px;
  text-align: center;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
  font-family: Helvetica, Arial, sans-serif;
  outline: 0;
  background: #f2f2f2;
  width: 100%;
  border: 0;
  margin: 0 0 15px;
  padding: 15px;
  box-sizing: border-box;
  font-size: 14px;
}
.form button {
  font-family: Helvetica, Arial, sans-serif;
  text-transform: uppercase;
  outline: 0;
  background: #4CAF50;
  width: 100%;
  border: 0;
  padding: 15px;
  color: #FFFFFF;
  font-size: 14px;
  -webkit-transition: all 0.3 ease;
  transition: all 0.3 ease;
  cursor: pointer;
}
.form button:hover,.form button:active,.form button:focus {
  background: #43A047;
}
.form .message {
  margin: 15px 0 0;
  color: #b3b3b3;
  font-size: 12px;
}
.form .message a {
  color: #4CAF50;
  text-decoration: none;
}
.form .register-form {
  display: none;
}
.container {
  position: relative;
  z-index: 1;
  max-width: 300px;
  margin: 0 auto;
}
.container:before, .container:after {
  content: "";
  display: block;
  clear: both;
}
.container .info {
  margin: 50px auto;
  text-align: center;
}
.container .info h1 {
  margin: 0 0 15px;
  padding: 0;
  font-size: 36px;
  font-weight: 300;
  color: #1a1a1a;
}
.container .info span {
  color: #4d4d4d;
  font-size: 12px;
}
.container .info span a {
  color: #000000;
  text-decoration: none;
}
.container .info span .fa {
  color: #EF3B3A;
}
body {
  background: #B8F0FF;
  background: -webkit-linear-gradient(right, #B8F0FF, #187b96);
  background: -moz-linear-gradient(right, #B8F0FF, #187b96);
  background: -o-linear-gradient(right, #B8F0FF, #187b96);
  background: linear-gradient(to left, #B8F0FF, #187b96);
  font-family: Helvetica, Arial, sans-serif; 
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;     
}
Осталось добавить ссылку для выхода из статистики вверху страницы. Для этого открываем файл ./templates/header.php и вписываем следующий код внизу перед последним закрывающим тегом </table>
<?php
  //Показываем кнопку выхода
  echo "<br/><div style='color: #3c8dbc;text-align: right;font-size: 13px;margin-right:6px;'><a href='auth.php?is_exit=1'>Завершить сеанс? (".$_SESSION["fullname"].")</a></div>";
?>
Следует добавить, что время активности сессии по умолчанию составляет 24 минуты. По прошествии этого времени пользователю нужно будет заново пройти авторизацию. В моём случае этого времени достаточно, но если вы хотите увеличить время, можете изменить настройки php.ini на вашем сервере, либо добавить в код такую функцию: ini_set('session.gc_maxlifetime', 86400); Это увеличит время до 1 суток.

Безопасность

Для того, чтобы пароли не передавались в открытом виде, доступ к статистике нужно предоставить по HTTPS протоколу (сгенерировать SSL сертификат) и разрешить только нужную подсеть (или диапазона адресов) средствами web сервера и/или файрвола.

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

Связь Asterisk с Active Directory

Вы можете ознакомиться еще с несколькими статьями по связи Asterisk c AD в моём блоге:
  1. Asterisk и Openfire/Jabber: XMPP сообщение о входящем звонке с именем и отделом из Active Directory
  2. Кнопка звонка в Miranda NG через Asterisk AMI

5 комментариев:

  1. Постоянно "К сожалению, вам доступ закрыт." Не понимаю как исправить, логов нет (

    ОтветитьУдалить
    Ответы
    1. Проверьте настройки ldap подключения, фильтра ($memberof). Пользователь AD должен быть в группе с именем "Статистика Asterisk".

      Удалить
  2. Здравствуйте, можете подсказать, возвращается ошибка Warning: ldap_search(): Search: Operations error in /var/www/html/auth.php on line 117 в файле эта строчка выглядит как:
    $result = ldap_search($ldap,$base,"(&(memberOf=".$memberof.")(".$filter.$username.")(!(userAccountControl:1.2.840.113556.1.4.803:=2)))");

    Так же ранее получил ошибку на работу ф-ии session_start(); в файле auth.php переместил ее в начало файла ошибку ушла
    В любом случаи спасибо за Ваш труд в написании данной статьи)

    ОтветитьУдалить
  3. Здравствуйте, можете подсказать, возвращается ошибка Warning: ldap_search(): Search: Operations error in /var/www/html/auth.php on line 117 в файле эта строчка выглядит как:
    $result = ldap_search($ldap,$base,"(&(memberOf=".$memberof.")(".$filter.$username.")(!(userAccountControl:1.2.840.113556.1.4.803:=2)))");

    Так же ранее получил ошибку на работу ф-ии session_start(); в файле auth.php переместил ее в начало файла ошибку ушла
    В любом случаи спасибо за Ваш труд в написании данной статьи)

    ОтветитьУдалить
    Ответы
    1. Спасибо за отзыв. По поводу вашей ошибки: у вас установлен модуль php-ldap? Какая версия php используется?

      Удалить