summaryrefslogtreecommitdiff
path: root/classes/Sessions.php
diff options
context:
space:
mode:
Diffstat (limited to 'classes/Sessions.php')
-rw-r--r--classes/Sessions.php148
1 files changed, 148 insertions, 0 deletions
diff --git a/classes/Sessions.php b/classes/Sessions.php
new file mode 100644
index 000000000..08c6b5855
--- /dev/null
+++ b/classes/Sessions.php
@@ -0,0 +1,148 @@
+<?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, max(Config::get(Config::SESSION_COOKIE_LIFETIME), 86400));
+ $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)
+ * @return bool Whether the new cookie was set successfully
+ */
+ public function extend_session(): bool {
+ if (isset($_COOKIE[$this->session_name])) {
+ 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;
+ }
+
+ /**
+ * @todo set return type to string|false, and remove ReturnTypeWillChange, when min supported is PHP 8
+ * @return string|false
+ */
+ #[\ReturnTypeWillChange]
+ public function read(string $id) {
+ $sth = Db::pdo()->prepare('SELECT data FROM ttrss_sessions WHERE id=?');
+ $sth->execute([$id]);
+
+ if ($row = $sth->fetch()) {
+ return base64_decode($row['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 {
+ $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]);
+ }
+
+ /**
+ * @todo set return type to int|false, and remove ReturnTypeWillChange, when min supported is PHP 8
+ * @return int|false the number of deleted sessions on success, or false on failure
+ */
+ #[\ReturnTypeWillChange]
+ public function gc(int $max_lifetime) {
+ $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;
+ }
+}