summaryrefslogtreecommitdiff
path: root/classes/userhelper.php
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2023-10-25 12:55:09 +0300
committerAndrew Dolgov <[email protected]>2023-10-25 12:55:09 +0300
commit865ecc87963dc3b26e66296616eef2a1cc41ac3f (patch)
treebf2ecd8a391103bdb2c8b70cd33c47467310754b /classes/userhelper.php
parent0a5507d3bd79d04c860455664f919bf8e7274fda (diff)
move to psr-4 autoloader
Diffstat (limited to 'classes/userhelper.php')
-rw-r--r--classes/userhelper.php520
1 files changed, 0 insertions, 520 deletions
diff --git a/classes/userhelper.php b/classes/userhelper.php
deleted file mode 100644
index 4d9f30548..000000000
--- a/classes/userhelper.php
+++ /dev/null
@@ -1,520 +0,0 @@
-<?php
-use OTPHP\TOTP;
-
-class UserHelper {
-
- const HASH_ALGO_SSHA512 = 'SSHA-512';
- const HASH_ALGO_SSHA256 = 'SSHA-256';
- const HASH_ALGO_MODE2 = 'MODE2';
- const HASH_ALGO_SHA1X = 'SHA1X';
- const HASH_ALGO_SHA1 = 'SHA1';
-
- const HASH_ALGOS = [
- self::HASH_ALGO_SSHA512,
- self::HASH_ALGO_SSHA256,
- self::HASH_ALGO_MODE2,
- self::HASH_ALGO_SHA1X,
- self::HASH_ALGO_SHA1
- ];
-
- const ACCESS_LEVELS = [
- self::ACCESS_LEVEL_DISABLED,
- self::ACCESS_LEVEL_READONLY,
- self::ACCESS_LEVEL_USER,
- self::ACCESS_LEVEL_POWERUSER,
- self::ACCESS_LEVEL_ADMIN,
- self::ACCESS_LEVEL_KEEP_CURRENT
- ];
-
- /** forbidden to login */
- const ACCESS_LEVEL_DISABLED = -2;
-
- /** can't subscribe to new feeds, feeds are not updated */
- const ACCESS_LEVEL_READONLY = -1;
-
- /** no restrictions, regular user */
- const ACCESS_LEVEL_USER = 0;
-
- /** not used, same as regular user */
- const ACCESS_LEVEL_POWERUSER = 5;
-
- /** has administrator permissions */
- const ACCESS_LEVEL_ADMIN = 10;
-
- /** used by self::user_modify() to keep current access level */
- const ACCESS_LEVEL_KEEP_CURRENT = -1024;
-
- /**
- * @param int $level integer loglevel value
- * @return UserHelper::ACCESS_LEVEL_* if valid, warn and return ACCESS_LEVEL_KEEP_CURRENT otherwise
- */
- public static function map_access_level(int $level) : int {
- if (in_array($level, self::ACCESS_LEVELS)) {
- /** @phpstan-ignore-next-line */
- return $level;
- } else {
- user_error("Passed invalid user access level: $level", E_USER_WARNING);
- return self::ACCESS_LEVEL_KEEP_CURRENT;
- }
- }
-
- static function authenticate(string $login = null, string $password = null, bool $check_only = false, string $service = null): bool {
- if (!Config::get(Config::SINGLE_USER_MODE)) {
- $user_id = false;
- $auth_module = false;
-
- PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_AUTH_USER,
- function ($result, $plugin) use (&$user_id, &$auth_module) {
- if ($result) {
- $user_id = (int)$result;
- $auth_module = strtolower(get_class($plugin));
- return true;
- }
- },
- $login, $password, $service);
-
- if ($user_id && !$check_only) {
-
- if (session_status() != PHP_SESSION_ACTIVE)
- session_start();
-
- session_regenerate_id(true);
-
- $user = ORM::for_table('ttrss_users')->find_one($user_id);
-
- if ($user && $user->access_level != self::ACCESS_LEVEL_DISABLED) {
- self::set_session_for_user($user_id);
- $_SESSION["auth_module"] = $auth_module;
- $_SESSION["name"] = $user->login;
- $_SESSION["access_level"] = $user->access_level;
- $_SESSION["pwd_hash"] = $user->pwd_hash;
-
- $user->last_login = Db::NOW();
- $user->save();
-
- return true;
- }
-
- return false;
- }
-
- if ($login && $password && !$user_id && !$check_only)
- Logger::log(E_USER_WARNING, "Failed login attempt for $login (service: $service) from " . UserHelper::get_user_ip());
-
- return false;
-
- } else {
- self::set_session_for_user(1);
- $_SESSION["name"] = "admin";
- $_SESSION["access_level"] = self::ACCESS_LEVEL_ADMIN;
-
- $_SESSION["hide_hello"] = true;
- $_SESSION["hide_logout"] = true;
-
- $_SESSION["auth_module"] = false;
-
- return true;
- }
- }
-
- static function set_session_for_user(int $owner_uid): void {
- $_SESSION["uid"] = $owner_uid;
- $_SESSION["last_login_update"] = time();
- $_SESSION["ip_address"] = UserHelper::get_user_ip();
-
- if (empty($_SESSION["csrf_token"]))
- $_SESSION["csrf_token"] = bin2hex(get_random_bytes(16));
-
- if (Config::get_schema_version() >= 120) {
- $_SESSION["language"] = get_pref(Prefs::USER_LANGUAGE, $owner_uid);
- }
- }
-
- static function load_user_plugins(int $owner_uid, PluginHost $pluginhost = null): void {
-
- if (!$pluginhost) $pluginhost = PluginHost::getInstance();
-
- if ($owner_uid && Config::get_schema_version() >= 100 && empty($_SESSION["safe_mode"])) {
- $plugins = get_pref(Prefs::_ENABLED_PLUGINS, $owner_uid);
-
- $pluginhost->load((string)$plugins, PluginHost::KIND_USER, $owner_uid);
-
- /*if (get_schema_version() > 100) {
- $pluginhost->load_data();
- }*/
- }
- }
-
- static function login_sequence(): void {
- $pdo = Db::pdo();
-
- if (Config::get(Config::SINGLE_USER_MODE)) {
- if (session_status() != PHP_SESSION_ACTIVE)
- session_start();
-
- self::authenticate("admin", null);
- startup_gettext();
- self::load_user_plugins($_SESSION["uid"]);
- } else {
- if (!\Sessions\validate_session())
- $_SESSION["uid"] = null;
-
- if (empty($_SESSION["uid"])) {
-
- if (Config::get(Config::AUTH_AUTO_LOGIN) && self::authenticate(null, null)) {
- $_SESSION["ref_schema_version"] = Config::get_schema_version();
- } else {
- self::authenticate(null, null, true);
- }
-
- if (empty($_SESSION["uid"])) {
- UserHelper::logout();
-
- Handler_Public::_render_login_form();
- exit;
- }
-
- } else {
- /* bump login timestamp */
- $user = ORM::for_table('ttrss_users')->find_one($_SESSION["uid"]);
- $user->last_login = Db::NOW();
- $user->save();
-
- $_SESSION["last_login_update"] = time();
- }
-
- if ($_SESSION["uid"]) {
- startup_gettext();
- self::load_user_plugins($_SESSION["uid"]);
- }
- }
- }
-
- static function print_user_stylesheet(): void {
- $value = get_pref(Prefs::USER_STYLESHEET);
-
- if ($value) {
- print "<style type='text/css' id='user_css_style'>";
- print str_replace("<br/>", "\n", $value);
- print "</style>";
- }
-
- }
-
- static function get_user_ip(): ?string {
- foreach (["HTTP_X_REAL_IP", "REMOTE_ADDR"] as $hdr) {
- if (isset($_SERVER[$hdr]))
- return $_SERVER[$hdr];
- }
-
- return null;
- }
-
- static function get_login_by_id(int $id): ?string {
- $user = ORM::for_table('ttrss_users')
- ->find_one($id);
-
- if ($user)
- return $user->login;
- else
- return null;
- }
-
- static function find_user_by_login(string $login): ?int {
- $user = ORM::for_table('ttrss_users')
- ->where('login', $login)
- ->find_one();
-
- if ($user)
- return $user->id;
- else
- return null;
- }
-
- static function logout(): void {
- if (session_status() === PHP_SESSION_ACTIVE)
- session_destroy();
-
- if (isset($_COOKIE[session_name()])) {
- setcookie(session_name(), '', time()-42000, '/');
-
- }
- session_commit();
- }
-
- static function get_salt(): string {
- return substr(bin2hex(get_random_bytes(125)), 0, 250);
- }
-
- /** TODO: this should invoke UserHelper::user_modify() */
- static function reset_password(int $uid, bool $format_output = false, string $new_password = ""): void {
-
- $user = ORM::for_table('ttrss_users')->find_one($uid);
- $message = "";
-
- if ($user) {
-
- $login = $user->login;
-
- $new_salt = self::get_salt();
- $tmp_user_pwd = $new_password ? $new_password : make_password();
-
- $pwd_hash = self::hash_password($tmp_user_pwd, $new_salt, self::HASH_ALGOS[0]);
-
- $user->pwd_hash = $pwd_hash;
- $user->salt = $new_salt;
- $user->save();
-
- $message = T_sprintf("Changed password of user %s to %s", "<strong>$login</strong>", "<strong>$tmp_user_pwd</strong>");
- } else {
- $message = __("User not found");
- }
-
- if ($format_output)
- print_notice($message);
- else
- print $message;
- }
-
- static function check_otp(int $owner_uid, int $otp_check) : bool {
- $otp = TOTP::create(self::get_otp_secret($owner_uid, true));
-
- return $otp->now() == $otp_check;
- }
-
- static function disable_otp(int $owner_uid) : bool {
- $user = ORM::for_table('ttrss_users')->find_one($owner_uid);
-
- if ($user) {
- $user->otp_enabled = false;
-
- // force new OTP secret when next enabled
- if (Config::get_schema_version() >= 143) {
- $user->otp_secret = null;
- }
-
- $user->save();
-
- return true;
- } else {
- return false;
- }
- }
-
- static function enable_otp(int $owner_uid, int $otp_check) : bool {
- $secret = self::get_otp_secret($owner_uid);
-
- if ($secret) {
- $otp = TOTP::create($secret);
- $user = ORM::for_table('ttrss_users')->find_one($owner_uid);
-
- if ($otp->now() == $otp_check && $user) {
-
- $user->otp_enabled = true;
- $user->save();
-
- return true;
- }
- }
- return false;
- }
-
-
- static function is_otp_enabled(int $owner_uid) : bool {
- $user = ORM::for_table('ttrss_users')->find_one($owner_uid);
-
- if ($user) {
- return $user->otp_enabled;
- } else {
- return false;
- }
- }
-
- static function get_otp_secret(int $owner_uid, bool $show_if_enabled = false): ?string {
- $user = ORM::for_table('ttrss_users')->find_one($owner_uid);
-
- if ($user) {
-
- $salt_based_secret = mb_substr(sha1($user->salt), 0, 12);
-
- if (Config::get_schema_version() >= 143) {
- $secret = $user->otp_secret;
-
- if (empty($secret)) {
-
- /* migrate secret if OTP is already enabled, otherwise make a new one */
- if ($user->otp_enabled) {
- $user->otp_secret = $salt_based_secret;
- } else {
- $user->otp_secret = bin2hex(get_random_bytes(10));
- }
-
- $user->save();
-
- $secret = $user->otp_secret;
- }
- } else {
- $secret = $salt_based_secret;
- }
-
- if (!$user->otp_enabled || $show_if_enabled) {
- return \ParagonIE\ConstantTime\Base32::encodeUpperUnpadded($secret);
- }
- }
-
- return null;
- }
-
- /**
- * @param null|int $owner_uid if null, checks current user via session-specific auth module, if set works on internal database only
- * @return bool
- * @throws PDOException
- * @throws Exception
- */
- static function is_default_password(?int $owner_uid = null): bool {
- return self::user_has_password($owner_uid, 'password');
- }
-
- /**
- * @param string $algo should be one of UserHelper::HASH_ALGO_*
- *
- * @return false|string False if the password couldn't be hashed, otherwise the hash string.
- */
- static function hash_password(string $pass, string $salt, string $algo = self::HASH_ALGOS[0]) {
- $pass_hash = "";
-
- switch ($algo) {
- case self::HASH_ALGO_SHA1:
- $pass_hash = sha1($pass);
- break;
- case self::HASH_ALGO_SHA1X:
- $pass_hash = sha1("$salt:$pass");
- break;
- case self::HASH_ALGO_MODE2:
- case self::HASH_ALGO_SSHA256:
- $pass_hash = hash('sha256', $salt . $pass);
- break;
- case self::HASH_ALGO_SSHA512:
- $pass_hash = hash('sha512', $salt . $pass);
- break;
- default:
- user_error("hash_password: unknown hash algo: $algo", E_USER_ERROR);
- }
-
- if ($pass_hash)
- return "$algo:$pass_hash";
- else
- return false;
- }
-
- /**
- * @param string $login Login for new user (case-insensitive)
- * @param string $password Password for new user (may not be blank)
- * @param UserHelper::ACCESS_LEVEL_* $access_level Access level for new user
- * @return bool true if user has been created
- */
- static function user_add(string $login, string $password, int $access_level) : bool {
- $login = clean($login);
-
- if ($login &&
- $password &&
- !self::find_user_by_login($login) &&
- self::map_access_level((int)$access_level) != self::ACCESS_LEVEL_KEEP_CURRENT) {
-
- $user = ORM::for_table('ttrss_users')->create();
-
- $user->salt = self::get_salt();
- $user->login = mb_strtolower($login);
- $user->pwd_hash = self::hash_password($password, $user->salt);
- $user->access_level = $access_level;
- $user->created = Db::NOW();
-
- return $user->save();
- }
-
- return false;
- }
-
- /**
- * @param int $uid User ID to modify
- * @param string $new_password set password to this value if its not blank
- * @param UserHelper::ACCESS_LEVEL_* $access_level set user access level to this value if it is set (default ACCESS_LEVEL_KEEP_CURRENT)
- * @return bool true if user record has been saved
- *
- * NOTE: $access_level is of mixed type because of intellephense
- */
- static function user_modify(int $uid, string $new_password = '', $access_level = self::ACCESS_LEVEL_KEEP_CURRENT) : bool {
- $user = ORM::for_table('ttrss_users')->find_one($uid);
-
- if ($user) {
- if ($new_password != '') {
- $new_salt = self::get_salt();
- $pwd_hash = self::hash_password($new_password, $new_salt, self::HASH_ALGOS[0]);
-
- $user->pwd_hash = $pwd_hash;
- $user->salt = $new_salt;
- }
-
- if ($access_level != self::ACCESS_LEVEL_KEEP_CURRENT) {
- $user->access_level = (int)$access_level;
- }
-
- return $user->save();
- }
-
- return false;
- }
-
- /**
- * @param int $uid user ID to delete (this won't delete built-in admin user with UID 1)
- * @return bool true if user has been deleted
- */
- static function user_delete(int $uid) : bool {
- if ($uid != 1) {
-
- $user = ORM::for_table('ttrss_users')->find_one($uid);
-
- if ($user) {
- // TODO: is it still necessary to split those queries?
-
- ORM::for_table('ttrss_tags')
- ->where('owner_uid', $uid)
- ->delete_many();
-
- ORM::for_table('ttrss_feeds')
- ->where('owner_uid', $uid)
- ->delete_many();
-
- return $user->delete();
- }
- }
-
- return false;
- }
-
- /**
- * @param null|int $owner_uid if null, checks current user via session-specific auth module, if set works on internal database only
- * @param string $password password to compare hash against
- * @return bool
- */
- static function user_has_password(?int $owner_uid, string $password) : bool {
- if ($owner_uid) {
- $authenticator = new Auth_Internal();
-
- return $authenticator->check_password($owner_uid, $password);
- } else {
- /** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */
- $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]);
-
- if ($authenticator &&
- method_exists($authenticator, "check_password") &&
- $authenticator->check_password($_SESSION["uid"], $password)) {
-
- return true;
- }
- }
-
- return false;
- }
-
-}