From 21341d3c185928fe5f92ce04484c9a9b648fc4c8 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 10 Mar 2018 20:53:13 +0100 Subject: [PATCH] Regularly resynchronize session data with authoritative source in DB --- src/Auth.php | 48 ++++++++++++++++++++++++++++++++++++++++++++- src/UserManager.php | 3 +++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Auth.php b/src/Auth.php index 705924b..c7ad7ed 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -28,6 +28,8 @@ final class Auth extends UserManager { private $ipAddress; /** @var bool whether throttling should be enabled (e.g. in production) or disabled (e.g. during development) */ private $throttling; + /** @var int the interval in seconds after which to resynchronize the session data with its authoritative source in the database */ + private $sessionResyncInterval; /** @var string the name of the cookie used for the 'remember me' feature */ private $rememberCookieName; @@ -36,18 +38,21 @@ final class Auth extends UserManager { * @param string $ipAddress the IP address that should be used instead of the default setting (if any), e.g. when behind a proxy * @param string|null $dbTablePrefix (optional) the prefix for the names of all database tables used by this component * @param bool|null $throttling (optional) whether throttling should be enabled (e.g. in production) or disabled (e.g. during development) + * @param int|null $sessionResyncInterval (optional) the interval in seconds after which to resynchronize the session data with its authoritative source in the database */ - public function __construct($databaseConnection, $ipAddress = null, $dbTablePrefix = null, $throttling = null) { + public function __construct($databaseConnection, $ipAddress = null, $dbTablePrefix = null, $throttling = null, $sessionResyncInterval = null) { parent::__construct($databaseConnection, $dbTablePrefix); $this->ipAddress = !empty($ipAddress) ? $ipAddress : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null); $this->throttling = isset($throttling) ? (bool) $throttling : true; + $this->sessionResyncInterval = isset($sessionResyncInterval) ? ((int) $sessionResyncInterval) : (60 * 5); $this->rememberCookieName = self::createRememberCookieName(); $this->initSession(); $this->enhanceHttpSecurity(); $this->processRememberDirective(); + $this->resyncSessionIfNecessary(); } /** Initializes the session and sets the correct configuration */ @@ -136,6 +141,46 @@ final class Auth extends UserManager { } } + private function resyncSessionIfNecessary() { + // if the user is signed in + if ($this->isLoggedIn()) { + if (!isset($_SESSION[self::SESSION_FIELD_LAST_RESYNC])) { + $_SESSION[self::SESSION_FIELD_LAST_RESYNC] = 0; + } + + // if it's time for resynchronization + if (($_SESSION[self::SESSION_FIELD_LAST_RESYNC] + $this->sessionResyncInterval) <= \time()) { + // fetch the authoritative data from the database again + try { + $authoritativeData = $this->db->selectRow( + 'SELECT email, username, status, roles_mask FROM ' . $this->dbTablePrefix . 'users WHERE id = ?', + [ $this->getUserId() ] + ); + } + catch (Error $e) { + throw new DatabaseError(); + } + + // if the user's data has been found + if (!empty($authoritativeData)) { + // update the session data + $_SESSION[self::SESSION_FIELD_EMAIL] = $authoritativeData['email']; + $_SESSION[self::SESSION_FIELD_USERNAME] = $authoritativeData['username']; + $_SESSION[self::SESSION_FIELD_STATUS] = (int) $authoritativeData['status']; + $_SESSION[self::SESSION_FIELD_ROLES] = (int) $authoritativeData['roles_mask']; + } + // if no data has been found for the user + else { + // their account may have been deleted so they should be signed out + $this->logOut(); + } + + // remember that we've just performed resynchronization + $_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time(); + } + } + } + /** * Attempts to sign up a user * @@ -343,6 +388,7 @@ final class Auth extends UserManager { unset($_SESSION[self::SESSION_FIELD_STATUS]); unset($_SESSION[self::SESSION_FIELD_ROLES]); unset($_SESSION[self::SESSION_FIELD_REMEMBERED]); + unset($_SESSION[self::SESSION_FIELD_LAST_RESYNC]); } } diff --git a/src/UserManager.php b/src/UserManager.php index 2639837..fa56e3e 100644 --- a/src/UserManager.php +++ b/src/UserManager.php @@ -38,6 +38,8 @@ abstract class UserManager { const SESSION_FIELD_ROLES = 'auth_roles'; /** @var string session field for whether the user who is currently signed in (if any) has been remembered (instead of them having authenticated actively) */ const SESSION_FIELD_REMEMBERED = 'auth_remembered'; + /** @var string session field for the UNIX timestamp in seconds of the session data's last resynchronization with its authoritative source in the database */ + const SESSION_FIELD_LAST_RESYNC = 'auth_last_resync'; /** @var PdoDatabase the database connection to operate on */ protected $db; @@ -205,6 +207,7 @@ abstract class UserManager { $_SESSION[self::SESSION_FIELD_STATUS] = (int) $status; $_SESSION[self::SESSION_FIELD_ROLES] = (int) $roles; $_SESSION[self::SESSION_FIELD_REMEMBERED] = $remembered; + $_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time(); } /**