From adc1d735393e146f89c40cc4ae9c2a330085b556 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 24 Mar 2018 18:04:06 +0100 Subject: [PATCH] Store and manage 'force_logout' value from 'users' table in session --- src/Administration.php | 2 +- src/Auth.php | 17 +++++++++-------- src/UserManager.php | 6 +++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Administration.php b/src/Administration.php index 66e086d..08f1fe9 100644 --- a/src/Administration.php +++ b/src/Administration.php @@ -558,7 +558,7 @@ final class Administration extends UserManager { $user = $users[0]; if ((int) $user['verified'] === 1) { - $this->onLoginSuccessful($user['id'], $user['email'], $user['username'], $user['status'], $user['roles_mask'], false); + $this->onLoginSuccessful($user['id'], $user['email'], $user['username'], $user['status'], $user['roles_mask'], \PHP_INT_MAX, false); } else { throw new EmailNotVerifiedException(); diff --git a/src/Auth.php b/src/Auth.php index 1e93b3c..331b00b 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -114,7 +114,7 @@ final class Auth extends UserManager { if (!empty($parts[0]) && !empty($parts[1])) { try { $rememberData = $this->db->selectRow( - 'SELECT a.user, a.token, a.expires, b.email, b.username, b.status, b.roles_mask FROM ' . $this->dbTablePrefix . 'users_remembered AS a JOIN ' . $this->dbTablePrefix . 'users AS b ON a.user = b.id WHERE a.selector = ?', + 'SELECT a.user, a.token, a.expires, b.email, b.username, b.status, b.roles_mask, b.force_logout FROM ' . $this->dbTablePrefix . 'users_remembered AS a JOIN ' . $this->dbTablePrefix . 'users AS b ON a.user = b.id WHERE a.selector = ?', [ $parts[0] ] ); } @@ -128,7 +128,7 @@ final class Auth extends UserManager { // the cookie and its contents have now been proven to be valid $valid = true; - $this->onLoginSuccessful($rememberData['user'], $rememberData['email'], $rememberData['username'], $rememberData['status'], $rememberData['roles_mask'], true); + $this->onLoginSuccessful($rememberData['user'], $rememberData['email'], $rememberData['username'], $rememberData['status'], $rememberData['roles_mask'], $rememberData['force_logout'], true); } } } @@ -392,6 +392,7 @@ final class Auth extends UserManager { unset($_SESSION[self::SESSION_FIELD_ROLES]); unset($_SESSION[self::SESSION_FIELD_REMEMBERED]); unset($_SESSION[self::SESSION_FIELD_LAST_RESYNC]); + unset($_SESSION[self::SESSION_FIELD_FORCE_LOGOUT]); } } @@ -490,7 +491,7 @@ final class Auth extends UserManager { } } - protected function onLoginSuccessful($userId, $email, $username, $status, $roles, $remembered) { + protected function onLoginSuccessful($userId, $email, $username, $status, $roles, $forceLogout, $remembered) { // update the timestamp of the user's last login try { $this->db->update( @@ -503,7 +504,7 @@ final class Auth extends UserManager { throw new DatabaseError(); } - parent::onLoginSuccessful($userId, $email, $username, $status, $roles, $remembered); + parent::onLoginSuccessful($userId, $email, $username, $status, $roles, $forceLogout, $remembered); } /** @@ -659,10 +660,10 @@ final class Auth extends UserManager { $userData = $this->getUserDataByEmailAddress( $verifiedEmail, - [ 'id', 'email', 'username', 'status', 'roles_mask' ] + [ 'id', 'email', 'username', 'status', 'roles_mask', 'force_logout' ] ); - $this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], $userData['roles_mask'], true); + $this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], $userData['roles_mask'], $userData['force_logout'], true); if ($rememberDuration !== null) { $this->createRememberDirective($userData['id'], $rememberDuration); @@ -958,7 +959,7 @@ final class Auth extends UserManager { $this->throttle([ 'enumerateUsers', $this->getIpAddress() ], 1, (60 * 60), 75); $this->throttle([ 'attemptToLogin', $this->getIpAddress() ], 4, (60 * 60), 5, true); - $columnsToFetch = [ 'id', 'email', 'password', 'verified', 'username', 'status', 'roles_mask' ]; + $columnsToFetch = [ 'id', 'email', 'password', 'verified', 'username', 'status', 'roles_mask', 'force_logout' ]; if ($email !== null) { $email = self::validateEmailAddress($email); @@ -995,7 +996,7 @@ final class Auth extends UserManager { if ((int) $userData['verified'] === 1) { if (!isset($onBeforeSuccess) || (\is_callable($onBeforeSuccess) && $onBeforeSuccess($userData['id']) === true)) { - $this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], $userData['roles_mask'], false); + $this->onLoginSuccessful($userData['id'], $userData['email'], $userData['username'], $userData['status'], $userData['roles_mask'], $userData['force_logout'], false); // continue to support the old parameter format if ($rememberDuration === true) { diff --git a/src/UserManager.php b/src/UserManager.php index 50d65de..4dcfd7c 100644 --- a/src/UserManager.php +++ b/src/UserManager.php @@ -40,6 +40,8 @@ abstract class UserManager { 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 string session field for the counter that keeps track of forced logouts that need to be performed in the current session */ + const SESSION_FIELD_FORCE_LOGOUT = 'auth_force_logout'; /** @var PdoDatabase the database connection to operate on */ protected $db; @@ -219,10 +221,11 @@ abstract class UserManager { * @param string $username the display name (if any) of the user * @param int $status the status of the user as one of the constants from the {@see Status} class * @param int $roles the roles of the user as a bitmask using constants from the {@see Role} class + * @param int $forceLogout the counter that keeps track of forced logouts that need to be performed in the current session * @param bool $remembered whether the user has been remembered (instead of them having authenticated actively) * @throws AuthError if an internal problem occurred (do *not* catch) */ - protected function onLoginSuccessful($userId, $email, $username, $status, $roles, $remembered) { + protected function onLoginSuccessful($userId, $email, $username, $status, $roles, $forceLogout, $remembered) { // re-generate the session ID to prevent session fixation attacks (requests a cookie to be written on the client) Session::regenerate(true); @@ -235,6 +238,7 @@ abstract class UserManager { $_SESSION[self::SESSION_FIELD_ROLES] = (int) $roles; $_SESSION[self::SESSION_FIELD_REMEMBERED] = $remembered; $_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time(); + $_SESSION[self::SESSION_FIELD_FORCE_LOGOUT] = (int) $forceLogout; } /**