Source of file AuthentificationHelper.php

Size: 10,904 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Helpers/AuthentificationHelper.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
<?php
/* This file is part of Jeedom.
 *
 * Jeedom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jeedom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
 */

/* This file is part of NextDom.
 *
 * NextDom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NextDom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NextDom. If not, see <http://www.gnu.org/licenses/>.
 */

namespace NextDom\Helpers;

use NextDom\Enums\DateFormat;
use NextDom\Enums\LogTarget;
use NextDom\Exceptions\CoreException;
use NextDom\Managers\ConfigManager;
use NextDom\Managers\UserManager;

/**
 * Class AuthentificationHelper
 * @package NextDom\Helpers
 */
class AuthentificationHelper
{
    /**
     * @var bool Status of the user connection
     */
    private static $connectedState = false;

    /**
     * @var bool Status of the user login as administrator
     */
    private static $connectedAdminState = false;
    /**
     * @var array Checked rights cache
     */
    private static $rightsCache = [];

    /**
     * @throws \Exception
     */
    public static function init()
    {
        // Init session
        $allowRemoteUser = ConfigManager::byKey('session_lifetime');
        SessionHelper::startSession();
        $_SESSION['ip'] = NetworkHelper::getClientIp();
        if (!headers_sent()) {
            setcookie('sess_id', session_id(), time() + 24 * 3600, "/", '', false, true);
        }
        session_write_close();

        if (UserManager::isBanned()) {
            header("Statut: 403 Forbidden");
            header('HTTP/1.1 403 Forbidden');
            $_SERVER['REDIRECT_STATUS'] = 403;
            require(NEXTDOM_ROOT . '/public/403.html');
            die();
        }

        // Autologin on register device
        if (!self::isConnected() && isset($_COOKIE['registerDevice'])) {
            if (self::loginByHash($_COOKIE['registerDevice'])) {
                setcookie('registerDevice', $_COOKIE['registerDevice'], time() + 365 * 24 * 3600, "/", '', false, true);
                if (isset($_COOKIE['nextdom_token'])) {
                    @session_start();
                    $_SESSION['nextdom_token'] = $_COOKIE['nextdom_token'];
                    @session_write_close();
                }
            } else {
                setcookie('registerDevice', '', time() - 3600, "/", '', false, true);
            }
        }

        // Login with user/password
        if (!self::isConnected() && $allowRemoteUser == 1 && isset($_SERVER['REMOTE_USER'])) {
            $user = UserManager::byLogin($_SERVER['REMOTE_USER']);
            if (is_object($user) && $user->getEnable() == 1) {
                @session_start();
                UserManager::storeUserInSession($user);
                @session_write_close();
                LogHelper::addInfo(LogTarget::CONNECTION, __('Connexion de l\'utilisateur par REMOTE_USER : ') . $user->getLogin());
            }
        }

        // Login with hash
        if (!self::isConnected() && Utils::init('auth') != '') {
            self::loginByHash(Utils::init('auth'));
        }

        // Logout
        if (Utils::init('logout') == 1) {
            self::logout();
            Utils::redirect('index.php');
            die();
        }

        self::$connectedState = AuthentificationHelper::isConnectedWithRights();
        self::$connectedAdminState = AuthentificationHelper::isConnectedWithRights('admin');
    }

    /**
     * Get the status of the user login
     * @return bool Status of the user connection
     */
    public static function isConnected(): bool
    {
        return self::$connectedState;
    }

    /**
     * @param $rawHashs
     * @return bool
     * @throws \Exception
     */
    public static function loginByHash($rawHashs)
    {
        $hashs = explode('-', $rawHashs);
        // Malformed hashs
        if (count($hashs) < 2) {
            return false;
        }
        // Get user by hash
        $user = UserManager::byHash($hashs[0]);
        if (!is_object($user) || $user->getEnable() == 0) {
            UserManager::failedLogin();
            return false;
        }
        // Check if user is limited
        if ($user->getOptions('localOnly', 0) == 1 && NetworkHelper::getUserLocation() != 'internal') {
            UserManager::failedLogin();
            return false;
        }
        // Bad hashs
        if (!isset($hashs[1])) {
            UserManager::failedLogin();
            return false;
        }
        // Test registered device
        $registeredDevices = $user->getOptions('registerDevice', []);
        $currentDeviceHash = Utils::sha512($hashs[1]);
        if (!isset($registeredDevices[$currentDeviceHash])) {
            UserManager::failedLogin();
            return false;
        }
        @session_start();
        UserManager::storeUserInSession($user);
        @session_write_close();
        if (!is_array($registeredDevices)) {
            $registeredDevices = [];
        }
        $registeredDevices[$currentDeviceHash] = [];
        $registeredDevices[$currentDeviceHash]['datetime'] = date(DateFormat::FULL);
        $registeredDevices[$currentDeviceHash]['ip'] = NetworkHelper::getClientIp();
        $registeredDevices[$currentDeviceHash]['session_id'] = session_id();
        @session_start();
        UserManager::getStoredUser()->setOptions('registerDevice', $registeredDevices);
        UserManager::getStoredUser()->save();
        @session_write_close();
        if (!isset($_COOKIE['nextdom_token'])) {
            setcookie('nextdom_token', AjaxHelper::getToken(), time() + 365 * 24 * 3600, "/", '', false, true);
        }
        LogHelper::addInfo(LogTarget::CONNECTION, __('Connexion de l\'utilisateur par clé : ') . $user->getLogin());
        return true;
    }

    /**
     * Disconnect user
     */
    public static function logout()
    {
        $expirationTime = time() - 3600;
        @session_start();
        setcookie('sess_id', '', $expirationTime, '/', '', false, true);
        setcookie('PHPSESSID', '', $expirationTime, '/', '', false, true);
        setcookie('registerDevice', '', $expirationTime, '/', '', false, true);
        setcookie('nextdom_token', '', $expirationTime, '/', '', false, true);
        session_unset();
        session_destroy();
    }

    /**
     * Test if user have some rights
     *
     * @param string $rights Droits à tester (admin)
     *
     * @return boolean True si l'utilisateur est connecté avec les droits demandés
     */
    public static function isConnectedWithRights(string $rights = ''): bool
    {
        $rightsKey = 'isConnect::' . $rights;
        $user = UserManager::getStoredUser();

        $result = false;

        if (!is_object($user) || session_status() == PHP_SESSION_DISABLED) {
            $result = false;
        } else {
            // Check cache
            if (isset(self::$rightsCache[$rightsKey]) && self::$rightsCache[$rightsKey]) {
                $result = self::$rightsCache[$rightsKey];
            } elseif ($user->isConnected()) {
                // Check specific rights
                if (!empty($rights)) {
                    if (UserManager::getStoredUser()->getProfils() == $rights) {
                        $result = true;
                    }
                } else {
                    // No specific rights
                    $result = true;
                }
            }
            // Store in cache
            self::$rightsCache[$rightsKey] = $result;
        }
        return $result;
    }

    /**
     * @param string $login
     * @param string $password
     * @param null $twoFactorCode
     * @return bool
     * @throws \Exception
     */
    public static function login($login, $password, $twoFactorCode = null): bool
    {
        $user = UserManager::connect($login, $password);
        if (!is_object($user) || !$user->isEnabled()) {
            UserManager::failedLogin();
            return false;
        }
        if ($user->getOptions('localOnly', 0) == 1 && NetworkHelper::getUserLocation() != 'internal') {
            UserManager::failedLogin();
            return false;
        }
        if (NetworkHelper::getUserLocation() != 'internal' && $user->getOptions('twoFactorAuthentification', 0) == 1 && $user->getOptions('twoFactorAuthentificationSecret') != '') {
            if (trim($twoFactorCode) == '' || $twoFactorCode === null || !$user->validateTwoFactorCode($twoFactorCode)) {
                UserManager::failedLogin();
                return false;
            }
        }
        @session_start();
        UserManager::storeUserInSession($user);
        @session_write_close();
        LogHelper::addInfo(LogTarget::CONNECTION, __('Connexion de l\'utilisateur : ') . $login);
        return true;
    }

    /**
     * Test if the user is logged in and throws an exception if this is not the case.
     * @return bool
     * @throws CoreException
     */
    public static function isConnectedOrFail(): bool
    {
        if (!self::$connectedState) {
            throw new CoreException(__('core.error-401'), 401);
        }
        return self::isConnected();
    }

    /**
     * @abstract Test if user is connected with admins right or throw CoreException if not.
     * @return bool
     * @throws CoreException
     */
    public static function isConnectedAsAdminOrFail(): bool
    {
        if (!self::$connectedAdminState) {
            throw new CoreException(__('core.error-401'), 401);
        }
        return self::isConnectedAsAdmin();
    }

    /**
     * Get the login status of the user as an administrator
     * @return bool Status of the user login as administrator
     */
    public static function isConnectedAsAdmin(): bool
    {
        return self::$connectedAdminState;
    }

    /**
     * @return bool
     */
    public static function isInDeveloperMode(): bool
    {
        return ConfigManager::getDefaultConfiguration()['core']['developer::mode'] == '1';
    }
}