partenit
8/26/2018 - 1:36 PM

AccessIPController - ограничение доступа по IP адресу посетителя

AccessIPController - ограничение доступа по IP адресу посетителя

<?php

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Redirect;

/**
 * для ограничения доступа по IP адресу посетителя
 */
class AccessIPController extends Controller
{
    // сколько дней "живет" подтвержденный IP
    private $days = 7;
    
    // сколько раз можем послать e-mail для подтверждения
    private $try_send_email = 2; 
    
    // пока не реализовано - в течение скольких секунд можно зааппрувить через почту сохраненный и не подтвержденный IP
    private $time_for_approve = 600;

    public function getTrySendEmail()
    {
        return $this->try_send_email;
    }

    /**
     * ищем IP адрес пользователя в белом списке
     * Возвращаем true если адрес подтвержден, даже если под другим user_id
     */
    public function searchIP($client_ip = '')
    {
        $is_allow = false;

        $enabled_always_ips = Config::get('custom.allow_ips', []);

        if (!empty($client_ip)) {
            // сначала проверяем по списку IP, которые не просрочиваются (к примеру IP наших серверов)

            if (count($enabled_always_ips)) {
                foreach ($enabled_always_ips as $ip) {
                    if (strpos($client_ip, $ip) !== false) {
                        $is_allow = true;
                        break;
                    }
                }
            }

            if (!$is_allow) {
                // потом проверяем по базе
                $allow_ips = Ips::where('is_allow', 1)
                    ->whereRaw(" (TO_DAYS(NOW()) - TO_DAYS(created_at) < $this->days) ")
                    ->get();
                if (count($allow_ips)) {
                    foreach ($allow_ips as $ip) {
                        if (strpos($client_ip, $ip->ip_mask) !== false) {
                            $is_allow = true;
                            break;
                        }
                    }
                }
            }
        }

        return $is_allow;
    }

    /**
     * сохраняем новый IP, возвращаем key для последующего подтверждения.
     */
    public function saveIP($client_ip = '', $user_id = 0, $note = '')
    {
        $key = '';
        if (!empty($client_ip)) {
            // ищем такой IP среди не подтвержденных
            $ips = Ips::where('ip_mask', $client_ip)
                ->where('is_allow', 0)
                ->where('akey', '!=', '')
                ->whereRaw(" (TO_DAYS(NOW()) - TO_DAYS(created_at) < $this->days) ")
                ->first();
            if (is_object($ips)) {
                $key = $ips->akey;
                $ips->note = $note;
                $ips->save();
            } else {
                // добавляем новый IP для последующего подтверждения
                $ip = new Ips();
                $ip->user_id = $user_id;
                $ip->ip_mask = $client_ip; // позже может быть сокращен до маски типа 192.186.12.*
                $ip->client_ip = $client_ip;
                $ip->akey = uniqid(md5(microtime(true)));
                $ip->note = $note;
                $ip->save();
                $key = $ip->akey;
            }
        }
        return $key;
    }

    /**
     * вызываем когда пользователь переходит по ссылке из письма.
     * Ищем по IP, key
     * в случае подтверждения ключ очищаем.
     * Возвращаем true если IP подтвержден.
     */
    public function approveIP($client_ip = '', $akey = '')
    {
        $return = false;
        if (!empty($client_ip) && !empty($akey) && strlen($akey) == 45) {
            // ищем в списке IP запись с указанными IP и ключом
            $ip = Ips::where('is_allow', 0)
                ->where('client_ip', $client_ip)
                ->where('akey', $akey)
                ->whereRaw(" (TO_DAYS(NOW()) - TO_DAYS(created_at) < $this->days) ")
                ->first();
            if (is_object($ip)) {
                $ip->akey = '';
                $ip->is_allow = 1;
                if ($ip->save()) {
                    $return = true;
                    // если есть кука - обновляем дату в ней
                    $this->updateCookieIfExist();
                }
            }
        }

        return $return;
    }

    /**
     * принимаем akey записи, если запись с akey еще не подтверждена - генерируем письмо
     * и отправляем его.
     * Возвращаем true после отправки письма.
     */
    public function sendEmailForApprove($akey = '', $user_email = '', $resend_email = 0)
    {
        $return = 0;
        if (!empty($akey) && strlen($akey) == 45 && !empty($user_email)) {
            // ищем запись, для которой будем отправлять email
            $ip = Ips::where('akey', $akey)
                ->where('is_allow', 0)
                ->whereRaw(" (TO_DAYS(NOW()) - TO_DAYS(created_at) < $this->days) ")
                ->first();
            $return = $ip->is_email;
            if (is_object($ip)) {
                // не превысили ли лимит повторных отправко e-mail?
                if ($ip->is_email < $this->try_send_email) {
                    // первую отправку делаем без доп. условий, а все последуюшие -
                    // только если пользователь сознательно запросил повторную отправку
                    if ($ip->is_email == 0 || $ip->is_email > 0 && $resend_email == 1) {
                        // подготавливаем данные для письма
                        $email_from = Config::get('custom.email_from', 'postmaster@estcrm.com');
                        $reply_to = Config::get('custom.admin_email', ['ekaterinoslav@gmail.com']);
                        if (isset($reply_to[0])) {
                            $reply_to = $reply_to[0];
                        } else {
                            $reply_to = '';
                        }
                        $url = URL::to("/pre?ip_key=$akey");
                        $data = [
                            'email_to' => $user_email,
                            'email_from' => $email_from,
                            'reply_to' => $reply_to,
                            'url_approve' => $url,
                            'client_ip' => $ip->client_ip,
                            'subject' => 'SOM: подтвердить доступ'
                        ];

                        // шлем письмо
                        Mail::send('emails2.ip_approve', $data, function ($message) use ($data) {
                            $message->from($data['email_from'], 'SOM');
                            $message->replyTo($data['reply_to']);
                            $message->subject($data['subject']);
                            $message->to($data['email_to']);
                        });

                        $ip->is_email++; // считаем все попытки повторно отправить e-mail
                        $ip->save();
                        $return = $ip->is_email;
                    }
                }
            }
        }
        return $return;
    }

    /**
     * показываем страницу привязки к устройству
     * @return mixed
     */
    public function getLinkToDevice() 
    {
        // страницу привязки к устройству показываем  только если включена проверка по IP
        if (Config::get('custom.check_ips')) {
            $agent = new \Jenssegers\Agent\Agent();
            $is_linked = false;
            $cookie_name = "som_ld";
            $cookie = null;
            if (isset($_COOKIE[$cookie_name])) {
                $cookie = $_COOKIE[$cookie_name];
            }
            if (!is_null($cookie)) {
                $is_linked = true;
            }
            return View::make('admin/link_to_device', compact(
                'is_linked',
                'agent'
            ));
        } else {
            return App::abort(404);
        }
    }

    /**
     * устанавливаем куку как итог привязки или отвязки устройства
     * @return mixed
     */
    public function postLinkToDevice() 
    {
        $action = Input::get('action', '');
        $message = '';
        if (!empty($action)) {
            $cookie_name = "som_ld";
            if ($action == 'link') {
                // ставим вечную куку
                setcookie($cookie_name, time(), 2147483647, '/');
                $message = 'Устройство успешно привязано';
            } elseif ($action == 'unlink') {
                // убираем куку
                setcookie($cookie_name, '', time() - 3600, '/');
                $message = 'Устройство успешно отвязано';
            }
        }
        return Redirect::to('admin/linkdevice')->with('success', $message);
    }

    /**
     * проверяем, установлена ли кука, которую используем для связывания устройства пользователя с SOM
     * и валидна ли она (значение time() в куке меньше недели)
     * 0 - куки нет
     * 1 - кука есть, но не валидна
     * 2 - кука есть и валидна
     * @return int
     */
    public function checkAndValidateCookie() 
    {
        $return = 0;
        $period = 7*24*60*60; // 1 неделя
        $cookie_name = "som_ld";
        $cookie = null;
        if (isset($_COOKIE[$cookie_name])) {
            $cookie = $_COOKIE[$cookie_name];
            if (!is_null($cookie)) {
                $return = 1;
                // проверяем сколько времени прошло после последнего обновления куки
                $period2 = time() - (int)$cookie;
                if ($period2 < $period) {
                    $return = 2;
                }
            }
        }
        return $return;
    }

    /**
     * обновляем куку, чтобы продлить срок привязки
     */
    public function updateCookieIfExist() 
    {
        if ($this->checkAndValidateCookie() == 1) {
            $cookie_name = "som_ld";
            if (isset($_COOKIE[$cookie_name])) {
                setcookie($cookie_name, time(), 2147483647, '/');
            }
        }
    }
}