* @link https://TheTempusProject.com * @license https://opensource.org/licenses/MIT [MIT LICENSE] */ namespace TheTempusProject\Models; use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Code; use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Bedrock\Functions\Session; use TheTempusProject\Bedrock\Functions\Cookie; use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\Config; use TheTempusProject\TheTempusProject as App; class Sessions extends DatabaseModel { public $tableName = 'sessions'; public $modelVersion = '1.0'; public $databaseMatrix = [ [ 'userID', 'int', '5' ], [ 'userGroup', 'int', '5' ], [ 'expire', 'int', '10' ], [ 'ip', 'varchar', '15' ], [ 'hash', 'varchar', '80' ], [ 'lastPage', 'varchar', '64' ], [ 'username', 'varchar', '20' ], [ 'token', 'varchar', '120' ], ]; public $searchFields = [ 'username', ]; public static $activeSession = false; /** * The model constructor. */ public function __construct() { parent::__construct(); self::$session = $this; } /** * Check if a session exists, verifies the username, * password, and IP address to prevent forgeries. * * @param {int} [$id] - The id of the session being checked. * @return {bool} */ public function checkSession( $sessionID ) { $user = new User; // @todo lets put this on some sort of realistic checking regime other than check everything every time if ( $sessionID == false ) { Debug::log( 'sessionID false' ); return false; } if ( !Check::id( $sessionID ) ) { Debug::log( 'sessionID not id' ); return false; } $data = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] ); if ( $data->count() == 0 ) { Debug::info( 'Session token not found.' ); return false; } $session = $data->first(); $user = $user->findById( $session->userID ); if ( $user === false ) { Debug::info( 'User not found in DB.' ); $this->destroy( $session->ID ); return false; } if ( $user->username != $session->username ) { Debug::info( 'Usernames do not match.' ); $this->destroy( $session->ID ); return false; } if ( $user->password != $session->hash ) { Debug::info( 'Session Password does not match.' ); $this->destroy( $session->ID ); return false; } if ( time() > $session->expire ) { Debug::info( 'Session Expired.' ); $this->destroy( $session->ID ); return false; } if ( $user->userGroup !== $session->userGroup ) { Debug::info( 'Groups do not match.' ); $this->destroy( $session->ID ); return false; } if ( $_SERVER['REMOTE_ADDR'] != $session->ip ) { Debug::info( 'IP addresses do not match.' ); $this->destroy( $session->ID ); return false; } self::$activeSession = $session; return true; } /** * Checks the "remember me" cookie we use to identify * unique sessions across multiple visits. Checks that * the tokens match, checks the username as well as the * password from the database to ensure it hasn't been * modified elsewhere between visits. * * @param {string} [$token] - The unique token saved as a cookie that is being checked. * @return {bool} */ public function checkCookie( $cookieToken, $create = false ) { $user = new User; if ( $cookieToken === false ) { Debug::info( 'cookieToken false' ); return false; } $data = self::$db->get( $this->tableName, [ 'token', '=', $cookieToken ] ); if ( !$data->count() ) { Debug::info( 'sessions->checkCookie - Session token not found.' ); return false; } $session = $data->first(); $user = self::$user->findById( $session->userID ); if ( $user === false ) { Debug::info( 'sessions->checkCookie - could not find user by ID.' ); return false; } if ( $user->username != $session->username ) { Debug::info( 'sessions->checkCookie - Usernames do not match.' ); $this->destroy( $session->ID ); return false; } if ( $user->password != $session->hash ) { Debug::info( 'sessions->checkCookie - Session Password does not match.' ); $this->destroy( $session->ID ); return false; } if ( $create ) { return $this->newSession( null, false, false, $session->userID ); } return true; } /** * Creates a new session from the data provided. The * expiration time is optional and will be set to the * system default if not provided. * * @param {int} [$ID] - The User ID of the new session holder. * @param {int} [$expire] - The expiration time (in seconds). * @return {bool} */ public function newSession( $expire = null, $override = false, $remember = false, $userID = null ) { if ( empty( $expire ) ) { // default Session Expiration is 24 hours $expireLimit = Config::getValue( 'main/loginTimer' ); $expire = ( time() + $expireLimit ); Debug::log( 'Using default expiration time' ); } $lastPage = App::getUrl(); if ( self::$activeSession != false ) { // there is already an active session if ( $override === false ) { Debug::error( 'No need for a new session.' ); return false; } // We can override the active session $data = self::$db->get( $this->tableName, [ 'userID', '=', self::$activeSession->ID ] ); if ( $data->count() ) { Debug::log( 'Deleting old session from db' ); $session = self::$db->first(); $this->destroy( $session->ID ); } } if ( empty( $userID ) ) { if ( App::$activeUser === null ) { Debug::info( 'Must provide user details to create a new session.' ); return false; } $userID = App::$activeUser->ID; } $userObject = self::$user->findById( $userID ); if ( $userObject === false ) { Debug::info( 'User not found.' ); return false; } $token = Code::genToken(); $result = self::$db->insert( $this->tableName, [ 'username' => $userObject->username, 'hash' => $userObject->password, 'userGroup' => $userObject->userGroup, 'userID' => $userObject->ID, 'lastPage' => $lastPage, 'expire' => $expire, 'ip' => $_SERVER['REMOTE_ADDR'], 'token' => $token, ] ); $sessionID = self::$db->lastId(); $sessionData = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] )->first(); Session::put( 'SessionID', $sessionID ); if ( $remember ) { Cookie::put( 'RememberToken', $token, ( time() + ( 3600 * 24 * 30 ) ) ); } self::$activeSession = $sessionData; return true; } /** * Function to update the users' current active page. * NOTE: Current session assumed if no $id is provided. * * @param {string} [$page] - The name of the page you are updating to. * @param {int|null} [$id] - The ID of the session you are updating. * @return {bool} */ public function updatePage( $page, $id = null ) { if ( empty( $id ) ) { if ( self::$activeSession === false ) { Debug::info( 'Session::updatePage - Must provide session ID or have active session' ); return false; } $id = self::$activeSession->ID; } if ( !Check::id( $id ) ) { Debug::info( 'Session::updatePage - Invalid ID' ); return false; } if ( !self::$db->update( $this->tableName, $id, [ 'lastPage' => $page ] ) ) { Debug::info( 'Session::updatePage - Failed to update database' ); return false; } return true; } /** * Destroy a session. * * @param {int} [$id] - The ID of the session you wish to destroy. * @return {bool} */ public function destroy( $id ) { Session::delete( 'SessionID' ); Cookie::delete( 'RememberToken' ); if ( !Check::id( $id ) ) { Debug::info( 'Session::destroy - Invalid ID' ); return false; } $data = self::$db->get( $this->tableName, [ 'ID', '=', $id ] ); if ( !$data->count() ) { Debug::info( 'Session::destroy - Session not found in DB' ); return false; } self::$db->delete( $this->tableName, [ 'ID', '=', $id ] ); self::$activeSession = false; return true; } }