yf_auth_admin class

Special methods for admin authentification.

Fileplugins/auth/classes/auth/yf_auth_admin.class.php
Use_class('auth_admin', 'classes/auth/')

Public properties

A site sets them in its config with $PROJECT_CONF['auth_admin']['NAME'] = ...;

PropertyDefaultDescription
$ADMIN_INFO_IN_SESSION null
$AUTH_ONLY_HTTPS
bool
false Allow to login only by HTTPS protocol, else raise error @security
$BLOCK_BANNED_IPS
bool
false Track banned IPs list @security
$BLOCK_FAILED_LOGINS
bool
false Block failed logins after several attempts (To prevent password bruteforcing, hacking, etc) @security
$BLOCK_FAILED_TTL
bool
3600 Track failed logins TTL @security
$DEF_ADMIN_MODULE
string
'settings' Default user object (module) to redirect
$DO_LOG_LOGINS
bool
true Do log into db admin login actions
$EXEC_AFTER_INIT
array
[] @conf_skip
$EXEC_AFTER_LOGIN
array
[] @conf_skip Methods to execute after success login or logout
$EXEC_AFTER_LOGOUT
array
[] @conf_skip
$LOGIN_FIELD
string
'login' Login field name to use @conf_skip
$LOG_FAILED_LOGINS
bool
true Save failed logins @security
$LOG_FAILED_PSWD
bool
true Store the submitted password in log_admin_auth_fails (previous behaviour); false keeps secrets out of the log
$PSWD_FIELD
string
'password' Password field name to use @conf_skip
$SESSION_LOCK_TO_HOST
bool
false Lock session to Hostname (to prevent hacks) @security
$SESSION_LOCK_TO_IP
bool
true Lock session to IP address (to prevent hacks) @security
$SESSION_LOCK_TO_UA
bool
false Lock session to User Agent (to prevent hacks) @security
$SESSION_REFERER_CHECK
bool
false Check referer in session @security
$URL_AFTER_LOGOUT
string
'./' Redirect URL
$URL_SUCCESS_LOGIN
string
'' Redirect URL
$URL_WRONG_LOGIN
string
'./' Redirect URL
$VAR_ADMIN_GO_URL
string
'admin_go_url' field name @conf_skip
$VAR_ADMIN_GROUP_ID
string
'admin_group' field name @conf_skip
$VAR_ADMIN_ID
string
'admin_id' field name @conf_skip
$VAR_ADMIN_INFO 'admin_info'
$VAR_ADMIN_LOGIN_TIME
string
'admin_login_time' field name @conf_skip
$VAR_LOCK_HOST
string
'admin_auth_lock_to_host' field name @conf_skip
$VAR_LOCK_IP
string
'admin_auth_lock_to_ip' field name @conf_skip
$VAR_LOCK_UA
string
'admin_auth_lock_to_ua' field name @conf_skip

Public methods 9

init

public function init()

Initialize auth.

    public function init()
    {
        // Chanined config rule
        if ($this->BLOCK_FAILED_LOGINS) {
            $this->LOG_FAILED_LOGINS = true;
        }
        // Remember last query string to process it after succesful login
        if (empty($_SESSION[$this->VAR_ADMIN_ID]) && ($_GET['task'] ?? null) != 'login') {
            $_SESSION[$this->VAR_ADMIN_GO_URL] = ($_SERVER['QUERY_STRING'] ?? null);
        }
        // Try to assign first page of the site (if $_GET['object'] is empty)
        if (empty($_GET['object'])) {
            $go = defined('SITE_DEFAULT_PAGE') ? SITE_DEFAULT_PAGE : '';
            // Check if default url is not empty and then use it
            if ( ! empty($go)) {
                $go = str_replace(['./?', './'], '', $go);
                $tmp_array = [];
                parse_str($go, $tmp_array);
                foreach ((array) $tmp_array as $k => $v) {
                    $_GET[$k] = $v;
                }
            }
        }
        if (empty($_GET['object'])) {
            $_GET['object'] = $this->DEF_ADMIN_MODULE;
        }
        // Check for session IP
        if ($this->SESSION_LOCK_TO_IP && ! empty($_SESSION[$this->VAR_ADMIN_ID]) && ($_GET['task'] ?? null) !== 'logout') {
            // User has changed IP, logout immediately
            $ip = common()->get_ip();
            if ( ! isset($_SESSION[$this->VAR_LOCK_IP]) || ($_SESSION[$this->VAR_LOCK_IP] ?? null) !== $ip) {
                trigger_error('AUTH: Attempt to use session with changed IP blocked, auth_ip:' . ($_SESSION[$this->VAR_LOCK_IP] ?? null) . ', new_ip:' . $ip . ', admin_id: ' . (int) (($_SESSION[$this->VAR_ADMIN_ID] ?? null)), E_USER_WARNING);
                $this->_log_fail([
                    'reason' => 'auth_blocked_by_ip',
                ]);
                $_GET['task'] = 'logout';
            }
        }
        // Check for session User Agent
        if ($this->SESSION_LOCK_TO_UA && ! empty($_SESSION[$this->VAR_ADMIN_ID]) && ($_GET['task'] ?? null) !== 'logout') {
            // User has changed User Agent, logout immediately
            $ua = ($_SERVER['HTTP_USER_AGENT'] ?? null);
            if ( ! isset($_SESSION[$this->VAR_LOCK_UA]) || ($_SESSION[$this->VAR_LOCK_UA] ?? null) !== $ua) {
                trigger_error('AUTH: Attempt to use session with changed User Agent blocked, auth_ua:"' . ($_SESSION[$this->VAR_LOCK_UA] ?? null) . '", new_ua:"' . $ua . '", admin_id: ' . (int) (($_SESSION[$this->VAR_ADMIN_ID] ?? null)), E_USER_WARNING);
                $this->_log_fail([
                    'reason' => 'auth_blocked_by_ua',
                ]);
                $_GET['task'] = 'logout';
            }
        }
        // Check for session hostname
        if ($this->SESSION_LOCK_TO_HOST && ! empty($_SESSION[$this->VAR_ADMIN_ID]) && ($_GET['task'] ?? null) !== 'logout') {
            // User has changed Host, logout immediately
            $host = $_SERVER['HTTP_HOST'];
            if ( ! isset($_SESSION[$this->VAR_LOCK_HOST]) || $_SESSION[$this->VAR_LOCK_HOST] !== $host) {
                trigger_error('AUTH: Attempt to use session with changed Host blocked, auth_host:"' . $_SESSION[$this->VAR_LOCK_HOST] . '", new_host:"' . $host . '", admin_id: ' . (int) ($_SESSION[$this->VAR_ADMIN_ID]), E_USER_WARNING);
                $this->_log_fail([
                    'reason' => 'auth_blocked_by_host',
                ]);
                $_GET['task'] = 'logout';
            }
        }
        // Check referer matched to WEB_PATH
        $referer = ($_SERVER['HTTP_REFERER'] ?? null);
        if ($this->SESSION_REFERER_CHECK && ( ! $referer || substr($referer, 0, strlen(WEB_PATH)) != WEB_PATH) && ($_GET['task'] ?? null) !== 'logout') {
            trigger_error('AUTH: Referer not matched and session blocked, referer:' . $referer, E_USER_WARNING);
            $this->_log_fail([
                'reason' => 'auth_blocked_by_referer',
            ]);
            $_GET['task'] = 'logout';
        }
        // Process log in or log out
        if (($_GET['task'] ?? null) == 'login') {
            if (empty($_SESSION[$this->VAR_ADMIN_ID])) {
                $this->_do_login();
            } elseif (($_GET['id'] ?? null) == 'prev_info' && ! empty($_SESSION['admin_prev_info'])) {
                $this->_login_with_prev_info();
            }
        } elseif (($_GET['task'] ?? null) == 'logout') {
            $this->_do_logout();
        }
        // Store admin info inside the main module
        if ( ! empty($_SESSION[$this->VAR_ADMIN_ID]) && ! empty($_SESSION[$this->VAR_ADMIN_GROUP_ID])) {
            $main = main();
            $main->ADMIN_ID = ($_SESSION[$this->VAR_ADMIN_ID] ?? null);
            $main->ADMIN_GROUP = ($_SESSION[$this->VAR_ADMIN_GROUP_ID] ?? null);
            if ($this->ADMIN_INFO_IN_SESSION && ! empty($_SESSION[$this->VAR_ADMIN_INFO])) {
                main()->ADMIN_INFO = &$_SESSION[$this->VAR_ADMIN_INFO];
            }
        }
        $this->_exec_method_on_action('init');
    }

login_as

public function login_as($id)
    public function login_as($id)
    {
        if ( ! $id) {
            return _e('Wrong id');
        }
        if (main()->ADMIN_GROUP != 1) {
            return _e('Allowed only for super-admins');
        }
        $a = db()->from('admin')->whereid($id)->get();
        if ( ! $a) {
            return _e('Target admin user info not found');
        }
        $t_group = db()->from('admin_groups')->whereid((int) $a['group'])->get();
        // Save previous session
        $tmp = $_SESSION;
        $_SESSION['admin_prev_info'] = $tmp;
        // Login as different admin user
        session_regenerate_id($destroy = true);
        $_SESSION[$this->VAR_ADMIN_ID] = $a['id'];
        $_SESSION[$this->VAR_ADMIN_GROUP_ID] = $a['group'];
        $_SESSION[$this->VAR_ADMIN_LOGIN_TIME] = time();
        $_SESSION[$this->VAR_LOCK_IP] = common()->get_ip();
        $_SESSION[$this->VAR_LOCK_UA] = ($_SERVER['HTTP_USER_AGENT'] ?? null);
        $_SESSION[$this->VAR_LOCK_HOST] = ($_SERVER['HTTP_HOST'] ?? null);

        $after_login = ($t_group['go_after_login'] ?? null) ?: $t_group['go_after_login'];
        return js_redirect($after_login ?: './');
    }

Methods starting with _ hooks and helpers; not routable as page actions

public function _check_password($plain, $stored)

The stored form is detected by yf_password; the legacy unsalted md5, also of the mysql-escaped value (how the old query hashed), stays accepted.

    public function _check_password($plain, $stored)
    {
        return _class('password')->verify($plain, $stored, ['salts' => [''], 'md5_escaped' => true]);
    }
public function _csrf_login_ok()

The login POST must carry the session token (the form2 login form sends it) once csrf_guard is enabled for admin.

    public function _csrf_login_ok()
    {
        $guard = _class('csrf_guard');
        if ( ! $guard->is_enabled('admin')) {
            return true;
        }
        return (bool) $guard->check(null, 'admin');
    }

_do_login

public function _do_login()

Process login.

    public function _do_login()
    {
        $AUTH_LOGIN = trim(($_POST[$this->LOGIN_FIELD] ?? ''));
        $AUTH_PSWD = trim(($_POST[$this->PSWD_FIELD] ?? ''));

        if (empty($AUTH_LOGIN) || empty($AUTH_PSWD)) {
            return false;
        }
        // task=login runs before core_blocks::tasks(), so the dispatcher's CSRF check never sees it
        if ( ! $this->_csrf_login_ok()) {
            _class('csrf_guard')->log_error(['form_id' => 'admin.login']);
            // the password was never checked, so it is not worth logging even with LOG_FAILED_PSWD
            $this->_log_fail(['login' => $AUTH_LOGIN, 'pswd' => '', 'reason' => 'csrf']);
            if ( ! empty($this->URL_WRONG_LOGIN)) {
                return js_redirect($this->URL_WRONG_LOGIN);
            }
            return false;
        }
        if ($this->AUTH_ONLY_HTTPS && ! ($_SERVER['HTTPS'] || $_SERVER['SSL_PROTOCOL'])) {
            $redirect_url = '';
            if (($_SERVER['HTTP_REFERER'] ?? null)) {
                $redirect_url = str_replace('http://', 'https://', $_SERVER['HTTP_REFERER']);
            }
            if ( ! $redirect_url) {
                $request_uri = getenv('REQUEST_URI');
                $cur_web_path = $request_uri[strlen($request_uri) - 1] == '/' ? substr($request_uri, 0, -1) : dirname($request_uri);
                $redirect_url = 'https://' . getenv('HTTP_HOST') . str_replace(['\\', '//'], ['/', '/'], (MAIN_TYPE_ADMIN ? dirname($cur_web_path) : $cur_web_path) . '/');
            }
            return js_redirect($redirect_url);
        }
        $NEED_QUERY_DB = true;

        $CUR_IP = common()->get_ip();
        if ($this->BLOCK_BANNED_IPS) {
            if (common()->_ip_is_banned()) {
                $NEED_QUERY_DB = false;
                trigger_error('AUTH ADMIN: Attempt to login from banned IP (' . $CUR_IP . ') as "' . $AUTH_LOGIN . '" blocked', E_USER_WARNING);
                return js_redirect($this->URL_WRONG_LOGIN);
            }
        }
        if ($this->BLOCK_FAILED_LOGINS) {
            $_fails_by_login = db()->get_one('SELECT COUNT(*) AS `0` FROM ' . db('log_admin_auth_fails') . ' WHERE time > ' . (time() - $this->BLOCK_FAILED_TTL) . ' AND login="' . _es($AUTH_LOGIN) . '"');
            $_fails_by_ip = db()->get_one('SELECT COUNT(*) AS `0` FROM ' . db('log_admin_auth_fails') . ' WHERE time > ' . (time() - $this->BLOCK_FAILED_TTL) . ' AND ip="' . _es(common()->get_ip()) . '"');
            if ($_fails_by_login >= 5 || $_fails_by_ip >= 10) {
                $NEED_QUERY_DB = false;
                trigger_error('AUTH ADMIN: Attempt to login as "' . $AUTH_LOGIN . '" blocked, fails_by_login: ' . (int) $_fails_by_login . ', fails_by_ip: ' . (int) $_fails_by_ip, E_USER_WARNING);
            }
        }
        $admin_info = null;
        $group_info = null;
        if ($NEED_QUERY_DB) {
            $admin_info = db()->query_fetch('SELECT * FROM ' . db('admin') . ' WHERE ' . $this->LOGIN_FIELD . '="' . _es($AUTH_LOGIN) . '" AND active="1"');
            $stored = is_array($admin_info) ? (string) ($admin_info['password'] ?? '') : '';
            $check = $this->_check_password($AUTH_PSWD, $stored);
            if ( ! $check['ok']) {
                $admin_info = [];
            } elseif ($check['rehash'] && ! empty($admin_info['id'])) {
                _class('password')->rehash('admin', $admin_info['id'], $stored, $AUTH_PSWD);
            }
        }
        if ( ! empty($admin_info['id'])) {
            $groups = main()->get_data('admin_groups_details');
            $group_info = $groups[$admin_info['group']];
        }
        // Login is ok
        if ($admin_info['id'] && $group_info['id']) {
            ob_start();
            if ($this->DO_LOG_LOGINS) {
                _class('logs')->store_admin_auth($admin_info);
            }
            session_regenerate_id($destroy = true);
            $_SESSION[$this->VAR_ADMIN_ID] = $admin_info['id'];
            $_SESSION[$this->VAR_ADMIN_GROUP_ID] = $admin_info['group'];
            $_SESSION[$this->VAR_ADMIN_LOGIN_TIME] = time();
            $_SESSION[$this->VAR_LOCK_IP] = common()->get_ip();
            $_SESSION[$this->VAR_LOCK_UA] = ($_SERVER['HTTP_USER_AGENT'] ?? null);
            $_SESSION[$this->VAR_LOCK_HOST] = ($_SERVER['HTTP_HOST'] ?? null);
            $main = main();
            $main->_init_cur_user_info($main);

            $REDIRECT_URL = null;
            // Auto-redirect to the page before login form if needed
            if ( ! empty($_SESSION[$this->VAR_ADMIN_GO_URL])) {
                $REDIRECT_URL = (substr(($_SESSION[$this->VAR_ADMIN_GO_URL] ?? ''), 0, 2) != './' ? './?' : '') . ($_SESSION[$this->VAR_ADMIN_GO_URL] ?? null);
                // Cleanup redirect url
                $_SESSION[$this->VAR_ADMIN_GO_URL] = '';
                // Redirect user to the user default
            } elseif ( ! empty($admin_info['go_after_login'])) {
                $REDIRECT_URL = $admin_info['go_after_login'];
                // Redirect user to the group default
            } elseif ( ! empty($group_info['go_after_login'])) {
                $REDIRECT_URL = $group_info['go_after_login'];
                // Force redirect user to the default location
            } elseif ( ! empty($this->URL_SUCCESS_LOGIN)) {
                $REDIRECT_URL = $this->URL_SUCCESS_LOGIN;
            }
            if ($REDIRECT_URL) {
                js_redirect($REDIRECT_URL);
            }
            // Execute custom code
            $this->_exec_method_on_action('login');
            ob_end_flush();

            // Login is wrong
        } else {
            unset($admin_info);
            $this->_log_fail([
                'login' => $AUTH_LOGIN,
                'pswd' => $AUTH_PSWD,
                'reason' => $NEED_QUERY_DB ? 'wrong_login' : 'blocked',
            ]);
            // Force redirect if given info is wrong
            if ( ! empty($this->URL_WRONG_LOGIN)) {
                js_redirect($this->URL_WRONG_LOGIN);
            }
        }
    }

_do_logout

public function _do_logout()

Process logout.

    public function _do_logout()
    {
        $this->_exec_method_on_action('logout');
        $admin_session_vars = [
            $this->VAR_ADMIN_ID,
            $this->VAR_ADMIN_GROUP_ID,
            $this->VAR_ADMIN_LOGIN_TIME,
            $this->VAR_LOCK_IP,
            $this->VAR_LOCK_UA,
            $this->VAR_LOCK_HOST,
        ];
        // Unset session variables except user id and group
        // (in case when session contains both user and admin info)
        foreach ((array) $_SESSION as $k => $v) {
            if (in_array($k, $admin_session_vars)) {
                unset($_SESSION[$k]);
            }
        }
        $main = main();
        $main->_admin_info = null;
        $main->ADMIN_ID = null;
        $main->ADMIN_GROUP = null;
        $main->ADMIN_INFO = &$main->_admin_info;

        session_regenerate_id($destroy = true);

        if ( ! empty($this->URL_AFTER_LOGOUT)) {
            js_redirect($this->URL_AFTER_LOGOUT);
        }
    }
public function _exec_method_on_action($action = 'login')

Execute user method after specified action.

    public function _exec_method_on_action($action = 'login')
    {
        if ($action == 'login') {
            $callbacks = $this->EXEC_AFTER_LOGIN;
        } elseif ($action == 'logout') {
            $callbacks = $this->EXEC_AFTER_LOGOUT;
        } elseif ($action == 'init') {
            $callbacks = $this->EXEC_AFTER_INIT;
        }
        if (empty($callbacks) || ! is_array($callbacks)) {
            return false;
        }
        foreach ((array) $callbacks as $callback) {
            if (is_callable($callback)) {
                call_user_func_array($callback, ['self' => $this]);
            }
        }
    }

_log_fail

public function _log_fail($data = [])
    public function _log_fail($data = [])
    {
        if ( ! $this->LOG_FAILED_LOGINS) {
            return false;
        }
        return db()->insert_safe('log_admin_auth_fails', [
            'time'         => str_replace(',', '.', microtime(true)),
            'ip'           => common()->get_ip(),
            'user_id'      => ($data['user_id'] ?? null) ?: ($_SESSION[$this->VAR_ADMIN_ID] ?? null),
            'login'        => $data['login'],
            'pswd'         => $this->LOG_FAILED_PSWD ? $data['pswd'] : '',
            'reason'       => $data['reason'],
            'host'         => ($_SERVER['HTTP_HOST'] ?? null),
            'user_agent'   => ($_SERVER['HTTP_USER_AGENT'] ?? null),
            'referer'      => ($_SERVER['HTTP_REFERER'] ?? null),
            'query_string' => ($_SERVER['QUERY_STRING'] ?? null),
            'site_id'      => (int) conf('SITE_ID'),
            'server_id'    => (int) conf('SERVER_ID'),
        ]);
    }
public function _login_with_prev_info()

Process previous login (used when super-admin logged in as less privileged user and want to login back as super-admin).

    public function _login_with_prev_info()
    {
        $prev_info = ($_SESSION['admin_prev_info'] ?? null);
        if ( ! $prev_info || ! $prev_info[$this->VAR_ADMIN_ID] || ! $prev_info[$this->VAR_ADMIN_GROUP_ID]) {
            if ( ! empty($this->URL_WRONG_LOGIN)) {
                return js_redirect($this->URL_WRONG_LOGIN);
            }
            return false;
        }
        $a = db()->from('admin')->whereid((int) $prev_info[$this->VAR_ADMIN_ID])->get();
        if ( ! $a) {
            if ( ! empty($this->URL_WRONG_LOGIN)) {
                return js_redirect($this->URL_WRONG_LOGIN);
            }
            return false;
        }
        $t_group = db()->from('admin_groups')->whereid((int) $a['group'])->get();
        // Save previous session
        $_SESSION = ($_SESSION['admin_prev_info'] ?? null);
        unset($_SESSION['admin_prev_info']); // Prevent recursion
        // Login as different admin user
        session_regenerate_id($destroy = true);
        $_SESSION[$this->VAR_ADMIN_ID] = $a['id'];
        $_SESSION[$this->VAR_ADMIN_GROUP_ID] = $a['group'];
        $_SESSION[$this->VAR_ADMIN_LOGIN_TIME] = time();
        $_SESSION[$this->VAR_LOCK_IP] = common()->get_ip();
        $_SESSION[$this->VAR_LOCK_UA] = ($_SERVER['HTTP_USER_AGENT'] ?? null);
        $_SESSION[$this->VAR_LOCK_HOST] = ($_SERVER['HTTP_HOST'] ?? null);

        $after_login = ($t_group[$this->VAR_ADMIN_GO_URL] ?? null) ?: $t_group[$this->VAR_ADMIN_GO_URL];
        return js_redirect($after_login ?: './');
    }