YII2: авторизация через Active Directory в шаблоне basic

Почти наверняка это делается легко и не принужденно, каким то другим способом, более стандартным, но: Мы не ищем легких путей (с). Поэтому велосипед.

Для начала в файле params.php добавим настройки необходимые для соединения с AD:

return [
    "ldap_server"=>"wrfwerfwer.ru",    
];

Далее изменим модель User.php следующим образом, добавив в него следующие функции:

 /**
     * Проверяем логин-пароль используя AD. Если в БД такого пользователя нет, до добавляем
     * @param type $username
     * @param type $password
     * @return null/int если авторизация удалась возвращаем id из БД.
     */
    public static function AuthByAD($username,$password){
        $params = require(__DIR__ . '/../config/web.php');
        $value = \yii\helpers\ArrayHelper::getValue($params, 'params');
        
        $ldap_con = ldap_connect($value["ldap_server"]) or die("Could not connect to LDAP server.");
    
        // Устанавливаем флажки протокола
        ldap_set_option($ldap_con, LDAP_OPT_PROTOCOL_VERSION, 3);  // AD Windows 2003 и выше
        ldap_set_option($ldap_con, LDAP_OPT_REFERRALS, 0);         //Определяет, следовать ли автоматически рефералам, возвращенным сервером LDAP. Зачем нужно - не понятно

        // авторизуемся в AD
        $ldapbind = @ldap_bind($ldap_con, $username."@".$value["ldap_server"], $password);                        
        if ($ldapbind==false) return null;

        $user_id=SELF::GetIdByLoginFromDB($username);      
        if ($user_id!=null) return $user_id;
              
        //если такого логина нет в БД, то добавляем..
        $res=(new \yii\db\Query())->createCommand()->insert('users', ['login' =>$username])->execute();
        if ($res==false){
            die("Не удалось вставить запись с новым пользователем..Увы и ах..");
        };        
        $user_id=$this->GetIdByLoginFromDB($username);
        if ($user_id!=null) return $user_id;
        
        return null;
        
    }
         
    /**
     * Получаем id пользователя по логину из БД
     * @param type $login
     * @return type null/id null если пользователь не найден
     */
    public static function GetIdByLoginFromDB($login){
       $rows = (new \yii\db\Query())->select(['id'])->from('users')->where(['login' => $login])->limit(1)->all();
       if (count($rows)>0) {
           return $rows[0]["id"];
       } else {
         return null;  
       };
    }
    
    /**
     * Получить расширенную информацию о пользователе БД по его ID
     * @param type $id
     * @return string
     */
    public static function GetExtendInfoByIdFromDB($id){
       $rows = (new \yii\db\Query())->select(['id',"login","name","photo","email","last_updated"])->from('users')->where(['id' => $id])->limit(1)->all();
       if (count($rows)>0) {
           $rows[0]["username"]=$rows[0]["login"];
           $rows[0]["password"]="";
           $rows[0]["authKey"]=$rows[0]["id"]."key";
           $rows[0]["accessToken"]=$rows[0]["id"]."-token";
           return $rows[0];
       } else {
         return null;  
       };
        
    }

И там-же заменим функцию:

 public static function findIdentity($id)
    {
        $arr=self::GetExtendInfoByIdFromDB($id);        
        $res=new User();
        $res->id=$arr["id"];
        $res->username=$arr["username"];
        $res->password=$arr["password"];
        $res->authKey=$arr["authKey"];
        $res->accessToken=$arr["accessToken"];        
        return $res;      
    }

В модели LoginFrom.php изменим функцию login():

 public function login()
    {
        $user_id=User::AuthByAD(Yii::$app->request->post()["LoginForm"]["username"],Yii::$app->request->post()["LoginForm"]["password"]);        
        if ($user_id==null) {
            $this->addError("password", 'Не верный пароль или логин');
            return false;
        };
        $extended_info=User::GetExtendInfoByIdFromDB($user_id);
        $tuser=new User();
        $tuser->id=$extended_info["id"];
        $tuser->username=$extended_info["username"];
        $tuser->password=$extended_info["password"];
        $tuser->authKey=$extended_info["authKey"];
        $tuser->accessToken=$extended_info["accessToken"];

        return Yii::$app->user->login($tuser, $this->rememberMe ? 3600*24*30 : 0);
        
    }

В результате получим собственно возможность авторизации через AD, с сохранением пользователя AD если такового в БД нет, в базе данных.

PHP: интересное поведение сложения чисел типа float

Был немножно удивлен сегодня, когда пытался сложить два числа типа float обычным оператором +. Например сложение чисел вида 59.86601 + 0,01 успешно выполнялось, а 59.86601+0,001 уже нет. Т.е. результирующее число оставалось прежним. Оказывается для точного сложения чисел, в PHP нужно использовать специальные операторы:

bcadd — Сложить 2 числа произвольной точности
bccomp — Сравнение двух чисел произвольной точности
bcdiv — Операция деления для чисел произвольной точности
bcmod — Получает остаток от деления чисел с произвольной точностью
bcmul — Умножение двух чисел с произвольной точностью
bcpow — Возведение в степень чисел с произвольной точностью
bcpowmod — Возводит одно число в степень другого и возвращает остаток от деления результата на третье число
bcscale — Задает количество чисел после десятичной точки по умолчанию для всех bc math функций.
bcsqrt — Извлекает квадратный корень из числа с произвольной точностью
bcsub — Вычитает одно число с произвольной точностью из другого

Например:

$point2["lat"]=Bcadd($point2["lat"],$cnt,8);

Быстрый поиск разницы файлов

Ну собственно это история одной маленькой победы, которые происходят обычно у ИТишников каждый день 😉

Предыстория: при работе скрипта по заливке данных в БД из файла произошло зависание сервера. Скрипт работал в несколько потоков с одним файлом. Потому определить на каком именно месте файла произошла остановка не представлялось возможным. Удалять уже залитое в БД и стартовать скрипт заново — не вариант, скрипт и так работал двое суток, и терять их снова — ну так себе решение.

Решение №1. «В лоб». Ну думаю доработаю скрипт так, что если данные уже есть, то просто пропускаем. Т.е. перед вставкой выполняем проверку функцией вида:

function lsYet($period,$ls){
    global $db;
  $res=false;
  $sql="select posting_addresses.id from posting_addresses
    inner join ls on ls.id=posting_addresses.ls
    where 
    posting_addresses.period='$period' and ls.ls='$ls'";
    $query = $db->connection->prepare($sql);
    $query->execute();
    while ($row99 = $query->fetch()) {
      $res=true;
    };        
 return $res;
}

И без проблем дозальем то, чего нет в БД. Да не тут то было, оказывается операция select в этом случае весьма дорогостоящая, и т.к. в БД записей порядка 600тыс, и индексы проставлены на ls и period корректно, но всёж скорость проверки крайне низкая, и т.о. скорость «дозалития» сокращается с двух суток до суток. Ну что собственно не устраивает.

Решение №2. Вдумчивое. Решил было выгрузить ключевые строки (лицевой счет) в файлы: файл ls_in_base.txt — лицевые счета которые уже в БД и ls_all.txt — файл со всеми лицевыми счетами, которые должны быть в БД, отсортировав их командой sort:

sort ls_in_base.txt > ls_in_base.txt
sort ls_all.txt > ls_all.txt

Далее воспользуемся Linux командой comm, вычленив уникальные записи файла ls_all которые не содержаться в файле ls_in_base:

comm -23 ls_all.txt ls_in_base.txt > no_in_base.txt

И далее уже в скрипте вместо проверки наличия лс в БД при помощи запроса, проверяем наличие лс в БД при помощи in_array:

$no_in_base_ls=file_get_contents("no_in_base.txt");
$no_in_base_arr=explode("\n",$no_in_base_ls);
...
if (in_array($ls, $no_in_base_arr)==true){
...
заливаем данные в БД
...
}

В результате скорость увеличилась в разы, и БД дозалить удалось в течении часа

PHP: нормализация номера телефона

Задача: на входе строка, которая может содержать номер, или номер + какие то комментарии к нему. Необходимо нормализовать номер, что бы были только цифры.

Решение:

Сначала напишем функцию проверки наличия букв в строке:

function LeetterYet($mobile){
    $yet=false;            
        if (preg_match('/[а-яё]/iu',    $mobile)) {$yet=true;};    // символ ё, отдельно проверяем
        if (preg_match('/[a-zA-Z]/',    $mobile)) {$yet=true;};    // английский алфавит
        if (preg_match('/[^a-zA-Z\d]/',    $mobile)) {$yet=true;}; // специальные символы
    return $yet;
}

Далее, если есть буквы, то удалим их, и ограничим длинну поля 11 символами..

function NormalizeMobile($mobile){
  $mobile=preg_replace('/[^0-9]/', '', $mobile);
  $mobile = substr($mobile, 0,11);
  return $mobile;
};

И общий результат:

        $mobile=$res->result->mobile;            
        if (LeetterYet($mobile)==false){                
          $mobile=  NormalizeMobile($mobile);
        };

Linux: цветной вывод в консоль

Иногда бывает полезно вывод в консоль скриптов чуть «раскрасить», чтобы более наглядно акцентировать внимание пользователя на как-то детали. Для того чтобы вывести текст цветным, необходимо применять специальные наборы спецсимволов. Цвет включается по принципу «реле», т.е. цвет нужно включить, а затем выключить.

ЦветОсновнойФон
Стандартный\033[39m\033[49m
Чёрный\033[30m\033[40m
Тёмно-красный\033[31m\033[41m
Тёмно-зелёный\033[32m\033[42m
Тёмно-жёлтый «Оранжевый»\033[33m\033[43m
Тёмно-синий\033[34m\033[44m
Темно-пурпурный\033[35m\033[45m
Тёмно-голубой\033[36m\033[46m
Светло-серый\033[37m\033[47m
Тёмно-серый\033[90m\033[100m
Красный\033[91m\033[101m
Зелёный\033[92m\033[101m
Оранжевый\033[93m\033[103m
Синий\033[94m\033[104m
Пурпурный\033[95m\033[105m
Голубой\033[96m\033[106m
Белый\033[97m\033[107m
Сброс цвета\033[0m\033[0m
Таблица цветов

Например выведем текст зеленым цветом на PHP:

<?php
echo "###############################################################\n";
echo "\033[;32mЯ весь\033[0m зелёный";
echo "###############################################################\n";
?>

Результат работы скрипта:

1 … 4 5 6 7 8 … 24