mirror of
https://git.tt-rss.org/fox/tt-rss.git
synced 2025-08-06 14:17:27 +02:00
157 lines
4.5 KiB
PHP
157 lines
4.5 KiB
PHP
<?php
|
|
require_once 'lib/gettext/gettext.inc.php';
|
|
|
|
/**
|
|
* @todo look into making this behave closer to what SessionHandlerInterface intends
|
|
*/
|
|
class Sessions implements \SessionHandlerInterface {
|
|
private int $session_expire;
|
|
private string $session_name;
|
|
|
|
public function __construct() {
|
|
$this->session_expire = min(2147483647 - time() - 1, Config::get(Config::SESSION_COOKIE_LIFETIME));
|
|
$this->session_name = Config::get(Config::SESSION_NAME);
|
|
}
|
|
|
|
/**
|
|
* Adjusts session-related PHP configuration options
|
|
*/
|
|
public function configure(): void {
|
|
if (Config::is_server_https()) {
|
|
ini_set('session.cookie_secure', 'true');
|
|
}
|
|
|
|
ini_set('session.gc_probability', '75');
|
|
ini_set('session.name', $this->session_name);
|
|
ini_set('session.use_only_cookies', 'true');
|
|
ini_set('session.gc_maxlifetime', $this->session_expire);
|
|
ini_set('session.cookie_lifetime', '0');
|
|
}
|
|
|
|
/**
|
|
* Extend the validity of the PHP session cookie (if it exists) and is persistent (expire > 0)
|
|
* @return bool Whether the new cookie was set successfully
|
|
*/
|
|
public function extend_session(): bool {
|
|
if (isset($_COOKIE[$this->session_name]) && $this->session_expire > 0) {
|
|
return setcookie($this->session_name,
|
|
$_COOKIE[$this->session_name],
|
|
time() + $this->session_expire,
|
|
ini_get('session.cookie_path'),
|
|
ini_get('session.cookie_domain'),
|
|
ini_get('session.cookie_secure'),
|
|
ini_get('session.cookie_httponly'));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function open(string $path, string $name): bool {
|
|
return true;
|
|
}
|
|
|
|
public function close(): bool {
|
|
return true;
|
|
}
|
|
|
|
public function read(string $id): false|string {
|
|
$sth = Db::pdo()->prepare('SELECT data FROM ttrss_sessions WHERE id=?');
|
|
$sth->execute([$id]);
|
|
|
|
if ($row = $sth->fetch()) {
|
|
$data = base64_decode($row['data']);
|
|
|
|
if (Config::get(Config::ENCRYPTION_KEY)) {
|
|
$unserialized_data = @unserialize($data); // avoid leaking plaintext session via error message
|
|
|
|
if ($unserialized_data !== false)
|
|
return Crypt::decrypt_string($unserialized_data);
|
|
}
|
|
|
|
// if encryption key is missing or session data is not in serialized format, assume plaintext data and return as-is
|
|
return $data;
|
|
}
|
|
|
|
$expire = time() + $this->session_expire;
|
|
|
|
$sth = Db::pdo()->prepare("INSERT INTO ttrss_sessions (id, data, expire)
|
|
VALUES (?, '', ?)");
|
|
return $sth->execute([$id, $expire]) ? '' : false;
|
|
}
|
|
|
|
public function write(string $id, string $data): bool {
|
|
|
|
if (Config::get(Config::ENCRYPTION_KEY))
|
|
$data = serialize(Crypt::encrypt_string($data));
|
|
|
|
$data = base64_encode($data);
|
|
|
|
$expire = time() + $this->session_expire;
|
|
|
|
$sth = Db::pdo()->prepare('SELECT id FROM ttrss_sessions WHERE id=?');
|
|
$sth->execute([$id]);
|
|
|
|
if ($sth->fetch()) {
|
|
$sth = Db::pdo()->prepare('UPDATE ttrss_sessions SET data=?, expire=? WHERE id=?');
|
|
return $sth->execute([$data, $expire, $id]);
|
|
}
|
|
|
|
$sth = Db::pdo()->prepare('INSERT INTO ttrss_sessions (id, data, expire) VALUES (?, ?, ?)');
|
|
return $sth->execute([$id, $data, $expire]);
|
|
}
|
|
|
|
public function destroy(string $id): bool {
|
|
$sth = Db::pdo()->prepare('DELETE FROM ttrss_sessions WHERE id = ?');
|
|
return $sth->execute([$id]);
|
|
}
|
|
|
|
/**
|
|
* @return int|false the number of deleted sessions on success, or false on failure
|
|
*/
|
|
public function gc(int $max_lifetime): false|int {
|
|
$result = Db::pdo()->query('DELETE FROM ttrss_sessions WHERE expire < ' . time());
|
|
return $result === false ? false : $result->rowCount();
|
|
}
|
|
|
|
public static function validate_session(): bool {
|
|
if (Config::get(Config::SINGLE_USER_MODE)) return true;
|
|
|
|
$pdo = Db::pdo();
|
|
|
|
if (!empty($_SESSION['uid'])) {
|
|
$user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']);
|
|
|
|
if ($user) {
|
|
if ($user->pwd_hash != $_SESSION['pwd_hash']) {
|
|
$_SESSION['login_error_msg'] = __('Session failed to validate (password changed)');
|
|
return false;
|
|
}
|
|
|
|
if ($user->access_level == UserHelper::ACCESS_LEVEL_DISABLED) {
|
|
$_SESSION['login_error_msg'] = __('Session failed to validate (account is disabled)');
|
|
return false;
|
|
}
|
|
|
|
// default to true because there might not be any hooks and this is our last check
|
|
$hook_result = true;
|
|
|
|
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_VALIDATE_SESSION,
|
|
function ($result) use (&$hook_result) {
|
|
$hook_result = $result;
|
|
|
|
if (!$result) {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
return $hook_result;
|
|
|
|
} else {
|
|
$_SESSION['login_error_msg'] = __('Session failed to validate (user not found)');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|