classes/yf_csrf_guard.class.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

/**
 * CSRF protection.
 */
class yf_csrf_guard
{
    public $ENABLED = true;
    public $ENABLED_FOR = [
        'admin' => false,
        'user' => true,
    ];
    public $HASH_ALGO = 'sha256';
    public $FORM_ID = null;
    public $TOKEN_NAME = '_token';
    public $LOG_ERRORS = true;

    /**
     * Catch missing method call.
     * @param mixed $name
     * @param mixed $args
     */
    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args, $this->_chained_mode);
    }

    /**
     * @param mixed $name
     * @param mixed $func
     */
    public function _extend($name, $func)
    {
        $this->_extend[$name] = $func;
    }

    /**
     * @param mixed $params
     */
    public function configure($params = [])
    {
        if (isset($params['form_id'])) {
            $this->FORM_ID = $params['form_id'];
        }
        if (isset($params['token_name'])) {
            $this->TOKEN_NAME = $params['token_name'];
        }
        if (isset($params['hash_algo'])) {
            $this->HASH_ALGO = $params['hash_algo'];
        }
        return $this;
    }


    public function generate()
    {
        if (function_exists('hash_algos') && in_array($this->HASH_ALGO, hash_algos())) {
            $token = hash($this->HASH_ALGO, mt_rand(0, mt_getrandmax()) . '|' . $_SERVER['REMOTE_ADDR'] . '|' . microtime());
        } else {
            $token = '';
            for ($i = 0; $i < 64; ++$i) {
                $r = mt_rand(0, 35);
                if ($r < 26) {
                    $c = chr(ord('a') + $r);
                } else {
                    $c = chr(ord('0') + $r - 26);
                }
                $token .= $c;
            }
        }
        $this->set($token);
        return $token;
    }

    /**
     * @param mixed $token_value
     */
    public function validate($token_value)
    {
        if ( ! $this->ENABLED || ! $this->ENABLED_FOR[MAIN_TYPE]) {
            return true;
        }
        $token = $this->get();
        if ($token === false) {
            $result = false;
        } elseif ($token === $token_value) {
            $result = true;
        } else {
            $result = false;
        }
        $this->del();
        return $result;
    }


    public function get()
    {
        $key = $this->FORM_ID;
        if (isset($_SESSION[$this->TOKEN_NAME][$key])) {
            return $_SESSION[$this->TOKEN_NAME][$key];
        }
        return false;
    }

    /**
     * @param mixed $value
     */
    public function set($value)
    {
        $key = $this->FORM_ID;
        $_SESSION[$this->TOKEN_NAME][$key] = $value;
        return $this;
    }


    public function del()
    {
        $key = $this->FORM_ID;
        $_SESSION[$this->TOKEN_NAME][$key] = '';
        unset($_SESSION[$this->TOKEN_NAME][$key]);
        return $this;
    }

    /**
     * @param mixed $params
     */
    public function log_error($params = [])
    {
        if ( ! $this->LOG_ERRORS) {
            return false;
        }
        $main = main();
        // slice 2 first elements (__FUNCTION__ and $this->_go) and leave only 5 more trace elements to save space
        $trace = implode(PHP_EOL, array_slice(explode(PHP_EOL, $main->trace_string()), 2, 7));
        $is_https = ($_SERVER['HTTPS'] || $_SERVER['SSL_PROTOCOL']);
        return db()->insert_safe('log_csrf_errors', [
            'form_id' => $params['form_id'],

            'date' => date('Y-m-d H:i:s'),
            'ip' => common()->get_ip(),
            'url' => ($is_https ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''),
            'ua' => $_SERVER['HTTP_USER_AGENT'],
            'referer' => $_SERVER['HTTP_REFERER'],
            'object' => $_GET['object'],
            'action' => $_GET['action'],
            'user_id' => MAIN_TYPE_ADMIN ? $main->ADMIN_ID : $main->USER_ID,
            'user_group' => MAIN_TYPE_ADMIN ? $main->ADMIN_GROUP : $main->USER_GROUP,
            'site_id' => (int) $main->SITE_ID,
            'server_id' => (int) $main->SERVER_ID,

            'is_logged_in' => (bool) $main->is_logged_in(),
            'is_common_page' => $main->is_common_page(),
            'is_https' => $main->is_https(),
            'is_post' => $main->is_post(),
            'is_no_graphics' => (bool) $main->no_graphics(),
            'is_ajax' => $main->is_ajax(),
            'is_spider' => $main->is_spider(),
            'is_redirect' => $main->is_redirect(),
            'is_console' => $main->is_console(),
            'is_unit_test' => $main->is_unit_test(),
            'is_dev' => $main->is_dev(),
            'is_debug' => $main->is_debug(),
            'is_banned' => $main->is_banned(),
            'is_403' => $main->is_403(),
            'is_404' => $main->is_404(),
            'is_503' => $main->is_503(),
            'is_cache_on' => $main->is_cache_on(),
            'is_mobile' => $main->is_mobile(),

            'lang' => (string) conf('language'),
            'country' => (string) (conf('country') ?: $_SERVER['GEOIP_COUNTRY_CODE']),
            'utm_source' => (string) ($_GET['utm_source'] ?: ($_POST['utm_source'] ?: $_SESSION['utm_source'])),
            'post_data' => json_encode($_POST),
            'trace' => $trace,
            'memory' => (int) memory_get_peak_usage(),
            'exec_time' => str_replace(',', '.', round(microtime(true) - $main->_time_start, 4)),
            'num_dbq' => (int) db()->NUM_QUERIES,
            'page_size' => (int) tpl()->_output_body_length,
        ]);
    }

    /**
     * Do save.
     */
    public function go()
    {
        if ( ! $this->allow()) {
            return false;
        }
        $is = $this->is;
        $data = [
            'user_id' => (int) $_SESSION['user_id'],
            'user_group' => (int) $_SESSION['user_group'],
            'date' => time(),
            'user_agent' => (string) $_SERVER['HTTP_USER_AGENT'],
            'referer' => (string) $_SERVER['HTTP_REFERER'],
            'query_string' => (string) $_SERVER['QUERY_STRING'],
            'request_uri' => (string) $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
            'exec_time' => str_replace(',', '.', common()->_format_time_value($GLOBALS['time_end'] ?: microtime(true) - main()->_time_start)),
            'num_dbq' => (int) db()->NUM_QUERIES,
            'page_size' => (int) tpl()->_output_body_length,
            'memory' => (int) memory_get_peak_usage(),
            'site_id' => (int) conf('SITE_ID'),
            'ip' => (string) common()->get_ip(),
            'country' => (string) (conf('country') ?: $_SERVER['GEOIP_COUNTRY_CODE']),
            'lang' => (string) conf('language'),
            'utm_source' => (string) ($_GET['utm_source'] ?: ($_POST['utm_source'] ?: $_SESSION['utm_source'])),
            'is_common_page' => (int) $is['is_common_page'],
            'is_https' => (int) $is['is_https'],
            'is_post' => (int) $is['is_post'],
            'is_no_graphics' => (int) $is['is_no_graphics'],
            'is_ajax' => (int) $is['is_ajax'],
            'is_spider' => (int) $is['is_spider'],
            'is_redirect' => (int) $is['is_redirect'],
            'is_console' => (int) $is['is_console'],
            'is_unit_test' => (int) $is['is_unit_test'],
            'is_dev' => (int) $is['is_dev'],
            'is_debug' => (int) $is['is_debug'],
            'is_banned' => (int) $is['is_banned'],
            'is_403' => (int) $is['is_403'],
            'is_404' => (int) $is['is_404'],
            'is_503' => (int) $is['is_503'],
            'is_cache_on' => (int) $is['is_cache_on'],
//            'is_mobile'        => (int)$is['is_mobile'],
        ];
        if (in_array('db', $this->LOG_DRIVER)) {
            $sql = db()->insert_safe('log_exec', $data);
            db()->_add_shutdown_query($sql);
        }
        if (in_array('file', $this->LOG_DRIVER)) {
            $data['output_cache'] = '0';  // mean: exec full mode (not from output cache)
            $log_file_path = STORAGE_PATH . $this->LOG_DIR_NAME . 'log_exec_' . gmdate('Y-m-d') . '.log';
            $log_dir_path = dirname($log_file_path);
            if ( ! file_exists($log_dir_path)) {
                _mkdir_m($log_dir_path);
            }
            file_put_contents($log_file_path, implode('#@#', $data) . PHP_EOL, FILE_APPEND);
        }
    }
}