Index: includes/bootstrap.inc =================================================================== RCS file: /srv/cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.249 diff -u -u -p -r1.249 bootstrap.inc --- includes/bootstrap.inc 16 Nov 2008 19:41:14 -0000 1.249 +++ includes/bootstrap.inc 21 Nov 2008 08:14:47 -0000 @@ -1,5 +1,5 @@ user(); + } + return drupal_session()->user(); +} - // Write and Close handlers are called after destructing objects - // since PHP 5.0.5. - // Thus destructors can use sessions but session handler can't use objects. - // So we are moving session closure before destructing objects. - register_shutdown_function('session_write_close'); - - // Handle the case of first time visitors and clients that don't store - // cookies (eg. web crawlers). - if (!isset($_COOKIE[session_name()])) { - $user = drupal_anonymous_user(); - return ''; - } - - // Otherwise, if the session is still active, we have a record of the - // client's session in the database. - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $key))->fetchObject(); - - // We found the client's session record and they are an authenticated user. - if ($user && $user->uid > 0) { - // This is done to unserialize the data member of $user. - $user = drupal_unpack($user); - - // Add roles element to $user. - $user->roles = array(); - $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1); - } - // We didn't find the client's record (session has expired), or they - // are an anonymous user. - else { - $session = isset($user->session) ? $user->session : ''; - $user = drupal_anonymous_user($session); +class Session { + private static $_instance; + // Protect the original uid discovered in Session::read() from external writes + // so we can be sure it is still the proper one later. + private $uid; + // The currently logged in user. + public $user; + + private function __construct() { + session_set_save_handler( + array(&$this, "open"), + array(&$this, "close"), + array(&$this, "read"), + array(&$this, "write"), + array(&$this, "destroy"), + array(&$this, "gc") + ); + session_start(); } - return $user->session; -} + /** + * Retrieve the object instance containing all the current session data. + * + * @return Session + */ + public static function get() { + if (empty(self::$_instance)) { + self::$_instance = new Session(); + } + return self::$_instance; + } -/** - * Session handler assigned by session_set_save_handler(). - * - * This function will be called by PHP to store the current user's - * session, which Drupal saves to the database. - * - * This function should not be called directly. Session data should - * instead be accessed via the $_SESSION superglobal. - * - * @param $key - * Session ID. - * @param $value - * Serialized array of the session data. - * @return - * This function will always return TRUE. - */ -function _sess_write($key, $value) { - global $user; + public function user() { + return $this->user; + } + + /** + * Finalize the login process. Must be called when logging in a user. + * + * The method checks the account object that is to replace the current user, + * records a watchdog message about the new session, saves the login + * timestamp, calls hook_user op 'login' and generates a new session. + * + * @param $account + * The new user object intended to replace the currently logged in user. + * @param $edit + * This array is passed to hook_user op login. + * @return + * Returns the new current user object. + */ + public function authenticate($account, &$edit) { + $this->user = $account; + // Reset the protected uid record to the new authorized uid. + $this->uid = $this->user->uid; + + watchdog('user', 'Session opened for %name.', array('%name' => $this->user->name)); + // Update the user table timestamp noting user has logged in. + // This is also used to invalidate one-time login links. + $this->user->login = REQUEST_TIME; + db_update('users') + ->fields(array( + 'login' => $this->user->login + )) + ->condition('uid', $this->user->uid) + ->execute(); + user_module_invoke('login', $edit, $this->user); + drupal_session_regenerate(); + return $this->user; + } - // If saving of session data is disabled or if the client doesn't have a session, - // and one isn't being created ($value), do nothing. This keeps crawlers out of - // the session table. This reduces memory and server load, and gives more useful - // statistics. We can't eliminate anonymous session table rows without breaking - // the "Who's Online" block. - if (!drupal_save_session() || ($user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) { + /** + * 'Open' session handler assigned by session_set_save_handler(). + * + * This method is used to handle any initialization, such as file paths or + * database connections, that is needed before accessing session data. Drupal + * does not need to initialize anything in this function. + * + * @return + * This method will always return TRUE. + */ + private function open() { return TRUE; } - db_merge('sessions') - ->key(array('sid' => $key)) - ->fields(array( - 'uid' => $user->uid, - 'cache' => isset($user->cache) ? $user->cache : 0, - 'hostname' => ip_address(), - 'session' => $value, - 'timestamp' => REQUEST_TIME, - )) - ->execute(); + /** + * 'Close' session handler assigned by session_set_save_handler(). + * + * This method is used to close the current session. Because Drupal stores + * session data in the database immediately on write, this function does + * not need to do anything. + * + * + * @return + * This method will always return TRUE. + */ + private function close() { + return TRUE; + } - // Last access time is updated no more frequently than once every 180 seconds. - // This reduces contention in the users table. - if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) { - db_update('users') + /** + * 'Read' session handler assigned by session_set_save_handler(). + * + * This method will be called internally by PHP to retrieve the current user's + * session data, which is stored in the database. It also loads the + * current user's appropriate roles into the user object. + * + * @param $key + * Session ID. + * @return + * Either an array of the session data, or an empty string, if no data + * was found or the user is anonymous. + */ + private function read($sid) { + // Handle the case of first time visitors and clients that don't store + // cookies (eg. web crawlers). + if (!isset($_COOKIE[session_name()])) { + $this->user = drupal_anonymous_user(); + return ''; + } + + // Otherwise, if the session is still active, we have a record of the + // client's session in the database. + $this->user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); + + // We found the client's session record and they are an authenticated user. + if ($this->user && $this->user->uid > 0) { + // This is done to unserialize the data member of $user. + $this->user = drupal_unpack($this->user); + + // Add roles element to $this->user. + $this->user->roles = array(); + $this->user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + $this->user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $this->user->uid))->fetchAllKeyed(0, 1); + } + // We didn't find the client's record (session has expired), or they + // are an anonymous user. + else { + $session = isset($this->user->session) ? $this->user->session : ''; + $this->user = drupal_anonymous_user($session); + } + + return $this->user->session; + } + + /** + * 'Write' session handler assigned by session_set_save_handler(). + * + * This method will be called by PHP to store the current user's + * session, which Drupal saves to the database. + * + * @param $sid + * Session ID. + * @param $value + * Serialized array of the session data. + * @return + * This function will always return TRUE. + */ + private function write($sid, $value) { + // Ensure we are safe to proceed with writing session data. + if ($this->write_check($value)) { + return TRUE; + } + + db_merge('sessions') + ->key(array('sid' => $sid)) ->fields(array( - 'access' => REQUEST_TIME + 'uid' => $this->user->uid, + 'cache' => isset($this->user->cache) ? $this->user->cache : 0, + 'hostname' => ip_address(), + 'session' => $value, + 'timestamp' => REQUEST_TIME, )) - ->condition('uid', $user->uid) ->execute(); + + // Last access time is updated no more frequently than once every 180 seconds. + // This reduces contention in the users table. + if ($this->user->uid && REQUEST_TIME - $this->user->access > variable_get('session_write_interval', 180)) { + db_update('users') + ->fields(array( + 'access' => REQUEST_TIME + )) + ->condition('uid', $this->user->uid) + ->execute(); + } + + return TRUE; + } + + /** + * Perform validation and security checks to ensure it is safe and appropriate + * to write data to the session table. + * + * First, if there is a mismatch between the user uid that was originally + * loaded into session data and Session::user->uid, then something has gone + * wrong, so we bail out without updating session data. + * + * Then, if saving of session data is disabled or if the client doesn't have a + * session, and one isn't being created ($value), do nothing. This keeps + * crawlers out of the session table, which reduces memory and server load, + * and results in more useful statistics. We can't eliminate anonymous session + * table rows without breaking the "Who's Online" block. + * + * @param $value + * Serialized array containing session data. + * @return bool + * Returns a boolean indicating whether the session write should be aborted. + */ + private function write_check($value) { + if ($this->uid != $this->user->uid) { + return TRUE; + } + + if ($this->user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value)) { + return TRUE; + } + return FALSE; + } + + /** + * 'Destroy' session handler assigned by session_set_save_handler(). + * + * Cleanup a specific session. + * + * @param string $sid + * Session ID. + */ + public function destroy($sid) { + db_delete('sessions') + ->condition('sid', $sid) + ->execute(); + } + + /** + * Session handler assigned by session_set_save_handler(). + * + * Cleanup stalled sessions. + * + * @param int $lifetime + * The value of session.gc_maxlifetime, passed by PHP. + * Sessions not updated for more than $lifetime seconds will be removed. + */ + public function gc($lifetime) { + // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough + // value. For example, if you want user sessions to stay in your database + // for three weeks before deleting them, you need to set gc_maxlifetime + // to '1814400'. At that value, only after a user doesn't log in after + // three weeks (1814400 seconds) will his/her session be removed. + db_delete('sessions') + ->condition('timestamp', REQUEST_TIME - $lifetime, '<') + ->execute(); + return TRUE; } - return TRUE; + /** + * Destructor magic method, called internally during PHP's shutdown sequence. + * + * By calling session_write_close() here, we are able to + * it attempts to clean up the Session singleton. + * @return unknown_type + */ + public function __destruct() { + session_write_close(); + } } /** @@ -197,20 +334,6 @@ function drupal_session_count($timestamp } /** - * Session handler assigned by session_set_save_handler(). - * - * Cleanup a specific session. - * - * @param string $sid - * Session ID. - */ -function _sess_destroy_sid($sid) { - db_delete('sessions') - ->condition('sid', $sid) - ->execute(); -} - -/** * End a specific user's session(s). * * @param string $uid @@ -221,46 +344,3 @@ function drupal_session_destroy_uid($uid ->condition('uid', $uid) ->execute(); } - -/** - * Session handler assigned by session_set_save_handler(). - * - * Cleanup stalled sessions. - * - * @param int $lifetime - * The value of session.gc_maxlifetime, passed by PHP. - * Sessions not updated for more than $lifetime seconds will be removed. - */ -function _sess_gc($lifetime) { - // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough - // value. For example, if you want user sessions to stay in your database - // for three weeks before deleting them, you need to set gc_maxlifetime - // to '1814400'. At that value, only after a user doesn't log in after - // three weeks (1814400 seconds) will his/her session be removed. - db_delete('sessions') - ->condition('timestamp', REQUEST_TIME - $lifetime, '<') - ->execute(); - return TRUE; -} - -/** - * Determine whether to save session data of the current request. - * - * This function allows the caller to temporarily disable writing of - * session data, should the request end while performing potentially - * dangerous operations, such as manipulating the global $user object. - * See http://drupal.org/node/218104 for usage. - * - * @param $status - * Disables writing of session data when FALSE, (re-)enables - * writing when TRUE. - * @return - * FALSE if writing session data has been disabled. Otherwise, TRUE. - */ -function drupal_save_session($status = NULL) { - static $save_session = TRUE; - if (isset($status)) { - $save_session = $status; - } - return $save_session; -} Index: modules/user/user.module =================================================================== RCS file: /srv/cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.938 diff -u -u -p -r1.938 user.module --- modules/user/user.module 20 Nov 2008 06:56:17 -0000 1.938 +++ modules/user/user.module 21 Nov 2008 08:14:41 -0000 @@ -1,5 +1,5 @@ $account->uid, 'status' => 1)); - $user = $account; - user_authenticate_finalize($form_values); - return $user; + return drupal_session()->authenticate($account, $form_values);; } } } } /** - * Finalize the login process. Must be called when logging in a user. - * - * The function records a watchdog message about the new session, saves the - * login timestamp, calls hook_user op 'login' and generates a new session. - * - * $param $edit - * This array is passed to hook_user op login. - */ -function user_authenticate_finalize(&$edit) { - global $user; - watchdog('user', 'Session opened for %name.', array('%name' => $user->name)); - // Update the user table timestamp noting user has logged in. - // This is also used to invalidate one-time login links. - $user->login = REQUEST_TIME; - db_query("UPDATE {users} SET login = %d WHERE uid = %d", $user->login, $user->uid); - user_module_invoke('login', $edit, $user); - drupal_session_regenerate(); -} - -/** * Submit handler for the login form. Redirects the user to a page. * * The user is redirected to the My Account page. Setting the destination in