summaryrefslogtreecommitdiffstats
path: root/inc/session.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/session.inc.php')
-rw-r--r--inc/session.inc.php178
1 files changed, 124 insertions, 54 deletions
diff --git a/inc/session.inc.php b/inc/session.inc.php
index cb52cd38..ccb878cd 100644
--- a/inc/session.inc.php
+++ b/inc/session.inc.php
@@ -1,19 +1,21 @@
<?php
-require_once('config.php');
+declare(strict_types=1);
-@mkdir(CONFIG_SESSION_DIR, 0700, true);
-@chmod(CONFIG_SESSION_DIR, 0700);
-if (!is_writable(CONFIG_SESSION_DIR)) die('Config error: Session Path not writable!');
+require_once('config.php');
class Session
{
private static $sid = false;
private static $data = false;
+ private static $dataChanged = false;
+ private static $userId = 0;
+ private static $updateSessionDateline = false;
- private static function generateSessionId($salt)
+ private static function generateSessionId(string $salt): void
{
- if (self::$sid !== false) Util::traceError('Error: Asked to generate session id when already set.');
+ if (self::$sid !== false)
+ ErrorHandler::traceError('Error: Asked to generate session id when already set.');
self::$sid = sha1($salt . ','
. mt_rand(0, 65535)
. $_SERVER['REMOTE_ADDR']
@@ -27,26 +29,42 @@ class Session
);
}
- public static function create($salt = '')
+ public static function create(string $salt, int $userId, bool $fixedAddress): void
{
self::generateSessionId($salt);
- self::$data = array();
+ self::$data = [];
+ self::$userId = $userId;
+ Database::exec("INSERT INTO session (sid, userid, dateline, lastip, fixedip, data)
+ VALUES (:sid, :userid, 0, '', :fixedip, '')", [
+ 'sid' => self::$sid,
+ 'userid' => $userId,
+ 'fixedip' => $fixedAddress ? 1 : 0,
+ ]);
+ self::setupSessionAccounting(true);
}
- public static function load()
+ public static function load(): bool
{
// Try to load session id from cookie
- if (!self::loadSessionId()) return false;
+ if (!self::loadSessionId())
+ return false;
// Succeeded, now try to load session data. If successful, job is done
- if (self::readSessionData()) return true;
+ if (self::readSessionData())
+ return true;
// Loading session data failed
- self::delete();
+ self::$sid = false;
return false;
}
- public static function get($key)
+ public static function getUserId(): int
{
- if (!isset(self::$data[$key]) || !is_array(self::$data[$key])) return false;
+ return self::$userId;
+ }
+
+ public static function get(string $key)
+ {
+ if (!isset(self::$data[$key]) || !is_array(self::$data[$key]))
+ return false;
return self::$data[$key][0];
}
@@ -55,80 +73,132 @@ class Session
* @param mixed $value data to store for key, false = delete
* @param int|false $validMinutes validity in minutes, or false = forever
*/
- public static function set($key, $value, $validMinutes = false)
+ public static function set(string $key, $value, $validMinutes = 60): void
{
- if (self::$data === false) Util::traceError('Tried to set session data with no active session');
+ if (self::$data === false)
+ ErrorHandler::traceError('Tried to set session data with no active session');
if ($value === false) {
unset(self::$data[$key]);
} else {
self::$data[$key] = [$value, $validMinutes === false ? false : time() + $validMinutes * 60];
}
+ self::$dataChanged = true;
}
- private static function loadSessionId()
+ private static function loadSessionId(): bool
{
- if (self::$sid !== false) die('Error: Asked to load session id when already set.');
- if (empty($_COOKIE['sid'])) return false;
+ if (self::$sid !== false)
+ ErrorHandler::traceError('Error: Asked to load session id when already set.');
+ if (empty($_COOKIE['sid']))
+ return false;
$id = preg_replace('/[^a-zA-Z0-9]/', '', $_COOKIE['sid']);
- if (empty($id)) return false;
+ if (empty($id))
+ return false;
self::$sid = $id;
return true;
}
- public static function delete()
+ public static function delete(): void
{
- if (self::$sid === false) return;
- @unlink(self::getSessionFile());
+ if (self::$sid === false)
+ return;
+ Database::exec("DELETE FROM session WHERE sid = :sid",
+ ['sid' => self::$sid]);
self::deleteCookie();
self::$sid = false;
self::$data = false;
}
- public static function deleteCookie()
+ /**
+ * Kill all sessions of currently logged-in user. This can be used as
+ * a security measure if the user suspects that a session left open on
+ * another device could be/is being abused.
+ */
+ public static function deleteAllButCurrent(): void
{
- Util::clearCookie('sid');
+ if (self::$sid === false)
+ return;
+ Database::exec("DELETE FROM session WHERE sid <> :sid AND userid = :uid",
+ ['sid' => self::$sid, 'uid' => self::$userId]);
}
-
- private static function getSessionFile()
+
+ public static function deleteCookie(): void
{
- if (self::$sid === false) Util::traceError('Error: Tried to access session file when no session id was set.');
- return CONFIG_SESSION_DIR . '/' . self::$sid;
+ Util::clearCookie('sid');
}
- private static function readSessionData()
+ private static function readSessionData(): bool
{
- if (self::$data !== false) Util::traceError('Tried to call read session data twice');
- $sessionfile = self::getSessionFile();
- if (!is_readable($sessionfile) || filemtime($sessionfile) + CONFIG_SESSION_TIMEOUT < time()) {
- @unlink($sessionfile);
- return false;
- }
- self::$data = @unserialize(@file_get_contents($sessionfile));
- if (self::$data === false)
- return false;
+ if (self::$data !== false)
+ ErrorHandler::traceError('Tried to call read session data twice');
+ $row = Database::queryFirst("SELECT userid, dateline, lastip, fixedip, data FROM session WHERE sid = :sid",
+ ['sid' => self::$sid]);
$now = time();
- $save = false;
+ if ($row === false || $row['dateline'] < $now) {
+ self::delete();
+ return false;
+ }
+ if ($row['fixedip'] && $row['lastip'] !== $_SERVER['REMOTE_ADDR']) {
+ return false; // Ignore but don't invalidate
+ }
+ // Refresh cookie if appropriate
+ self::setupSessionAccounting(Request::isGet() && $row['dateline'] + 86400 < $now + CONFIG_SESSION_TIMEOUT);
+ self::$userId = (int)$row['userid'];
+ self::$data = @json_decode($row['data'], true);
+ if (!is_array(self::$data)) {
+ self::$data = [];
+ }
foreach (array_keys(self::$data) as $key) {
if (self::$data[$key][1] !== false && self::$data[$key][1] < $now) {
unset(self::$data[$key]);
- $save = true;
+ self::$dataChanged = true;
}
}
- if ($save) {
- self::save();
- }
return true;
}
-
- public static function save()
+
+ private static function setupSessionAccounting(bool $cookie): void
{
- if (self::$sid === false || self::$data === false) return; //Util::traceError('Called saveSession with no active session');
- $sessionfile = self::getSessionFile();
- $ret = @file_put_contents($sessionfile, @serialize(self::$data));
- if (!$ret) Util::traceError('Storing session data in ' . $sessionfile . ' failed.');
- Util::clearCookie('sid');
- $ret = setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
- if (!$ret) Util::traceError('Error: Could not set Cookie for Client (headers already sent)');
+ if ($cookie) {
+ self::$updateSessionDateline = true;
+ $ret = setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT,
+ '', '', !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
+ if (!$ret)
+ ErrorHandler::traceError('Error: Could not set Cookie for Client (headers already sent)');
+ }
+ register_shutdown_function(function () {
+ self::saveOnShutdown();
+ });
}
-}
+ private static function saveOnShutdown(): void
+ {
+ $now = time();
+ $args = ['lastip' => $_SERVER['REMOTE_ADDR']];
+ if (self::$updateSessionDateline) {
+ $args['dateline'] = $now + CONFIG_SESSION_TIMEOUT;
+ }
+ if (self::$dataChanged) {
+ $args['data'] = json_encode(self::$data);
+ }
+ self::saveData($args);
+ }
+
+ public static function saveExtraData(): void
+ {
+ if (!self::$dataChanged)
+ return;
+ self::saveData(['data' => json_encode(self::$data)]);
+ self::$dataChanged = false;
+ }
+
+ private static function saveData(array $args): void
+ {
+ $query = "UPDATE session SET " . implode(', ', array_map(function ($key) {
+ return "$key = :$key";
+ }, array_keys($args))) . " WHERE sid = :sid";
+ $args['sid'] = self::$sid;
+ Database::exec($query, $args);
+ }
+
+}