| <?php | |
| /** | |
| * Copyright (c) 2006, 2023 Eclipse Foundation and others. | |
| * | |
| * This program and the accompanying materials are made | |
| * available under the terms of the Eclipse Public License 2.0 | |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ | |
| * | |
| * Contributors: | |
| * Denis Roy (Eclipse Foundation)- initial API and implementation | |
| * Christopher Guindon (Eclipse Foundation) - Bug 440590 - Improve the flexibility of session.class.php | |
| * Christopher Guindon (Eclipse Foundation) - Refactoring to avoid Friend() seriazilation in database | |
| * | |
| * SPDX-License-Identifier: EPL-2.0 | |
| */ | |
| require_once realpath(dirname(__FILE__) . "/../classes/friends/friend.class.php"); | |
| require_once "app.class.php"; | |
| if (!class_exists("EvtLog")) { | |
| require_once "evt_log.class.php"; | |
| } | |
| /** | |
| * Session class. | |
| */ | |
| class Session { | |
| /** | |
| * Cookie name for flag that enforce HTTPS redirects. | |
| * | |
| * @var string | |
| */ | |
| const ENV = "ECLIPSE_ENV"; | |
| /** | |
| * The session name. | |
| * | |
| * @var string | |
| */ | |
| const SESSION_NAME = "ECLIPSESESSION"; | |
| /** | |
| * An instance of App() from eclipse.org-common. | |
| * | |
| * @var App | |
| */ | |
| private $App = NULL; | |
| /** | |
| * Bugzilla ID linked to the session. | |
| * | |
| * @var int | |
| */ | |
| private $bugzilla_id = 0; | |
| /** | |
| * Flag to check if cookies were already sent. | |
| * | |
| * @var bool | |
| */ | |
| private $cookies_sent = FALSE; | |
| /** | |
| * Session cookie's domain. | |
| * | |
| * To make cookies visible on all subdomains then the | |
| * domain must be prefixed with a dot. | |
| * | |
| * @var string | |
| */ | |
| private $domain = ".eclipse.org"; | |
| /** | |
| * Eclipse session email. | |
| * | |
| * @var string | |
| */ | |
| private $email = ""; | |
| /** | |
| * Instance of EvtLog from eclipse.org-common. | |
| * | |
| * @var EvtLog | |
| */ | |
| private $EvtLog = NULL; | |
| /** | |
| * Instance of Friend from eclipse.org-common. | |
| * | |
| * @var Friend | |
| */ | |
| private $Friend = NULL; | |
| /** | |
| * Unique Session Identifier. | |
| * | |
| * @var string | |
| */ | |
| private $gid = ""; | |
| /** | |
| * URL for redirection to login page. | |
| * | |
| * @var string | |
| */ | |
| private $login_page = "https://accounts.eclipse.org/user/login"; | |
| /** | |
| * Subnet IP range for current session. | |
| * | |
| * @var string | |
| */ | |
| private $subnet = ""; | |
| /** | |
| * Update time used for expiring stale session. | |
| * | |
| * @var string | |
| */ | |
| private $updated_at = ""; | |
| /** | |
| * Eclipse session username. | |
| * | |
| * @var string | |
| */ | |
| private $username = ""; | |
| /** | |
| * Default constructor. | |
| * | |
| * @param int $persistent | |
| * Determine if the session is persistent (default is 0). | |
| * @param array $configs | |
| * Configuration values to overwrite defaults. | |
| * | |
| * @return NULL | |
| */ | |
| public function __construct($persistent = 0, $configs = array()) { | |
| $this->App = new App(); | |
| $this->EvtLog = new EvtLog(); | |
| $this->initializeConfigurations($configs); | |
| $this->validate(); | |
| } | |
| /** | |
| * Initialize default configurations. | |
| * | |
| * @param array $configs | |
| * Overwriting configurations. | |
| */ | |
| private function initializeConfigurations($configs) { | |
| // Update defaults based on the environment. | |
| $domainData = $this->App->getEclipseDomain(); | |
| $defaults = array( | |
| 'domain' => $domainData['cookie'], | |
| 'login_page' => 'https://' . $domainData['accounts'] . '/user/login' | |
| ); | |
| foreach ($defaults as $key => $value) { | |
| if (!empty($configs[$key]) && is_string($configs[$key])) { | |
| ${$key} = $configs[$key]; | |
| } | |
| else { | |
| ${$key} = $value; | |
| } | |
| } | |
| $this->setDomain($domain); | |
| $this->setLoginPage($login_page); | |
| } | |
| /** | |
| * Retrieve the stored Bugzilla ID. | |
| * | |
| * @return int | |
| * Returns the Bugzilla ID. | |
| */ | |
| public function getBugzillaID() { | |
| return $this->bugzilla_id; | |
| } | |
| /** | |
| * Set the Bugzilla ID if it's a valid digit. | |
| * | |
| * @param mixed $bugzilla_id | |
| * The Bugzilla ID to be set. | |
| * | |
| * @return void | |
| */ | |
| public function setBugzillaID($bugzilla_id) { | |
| if (is_numeric($bugzilla_id)) { | |
| $this->bugzilla_id = $bugzilla_id; | |
| } | |
| } | |
| /** | |
| * Retrieve the status of whether cookies were sent or not. | |
| * | |
| * @return bool | |
| * Returns true if cookies were sent, false otherwise. | |
| */ | |
| public function getCookiesSent() { | |
| return $this->cookies_sent; | |
| } | |
| /** | |
| * Set the status for the `cookies_sent` property. | |
| * | |
| * @param bool $cookies_sent | |
| * Status to indicate if cookies were sent. | |
| * | |
| * @return void | |
| */ | |
| public function setCookiesSent($cookies_sent) { | |
| $this->cookies_sent = (bool) $cookies_sent; | |
| } | |
| /** | |
| * Retrieves an instance of the Friend() class. | |
| * | |
| * This method used to unserialized data from the | |
| * eclipse.session data column. This was changed to better support | |
| * different versions of PHP. | |
| * | |
| * @return mixed | |
| * The unserialized data. | |
| * | |
| * @deprecated | |
| * Call getFriend() instead. | |
| */ | |
| public function getData() { | |
| trigger_error("Deprecated function called.", E_USER_NOTICE); | |
| return $this->getFriend(); | |
| } | |
| /** | |
| * Serializes and sets the data. | |
| * | |
| * @param mixed $data | |
| * The data to be serialized and stored. | |
| * | |
| * @deprecated | |
| */ | |
| public function setData($data) { | |
| trigger_error("Deprecated function called.", E_USER_NOTICE); | |
| } | |
| /** | |
| * Retrieve the current cookie domain. | |
| * | |
| * @return string | |
| * Returns the cookie domain. | |
| */ | |
| public function getDomain() { | |
| return $this->domain; | |
| } | |
| /** | |
| * Set the value for the `$domain` property. | |
| * | |
| * @param string $domain | |
| * The domain value to be set for cookies. | |
| * | |
| * @return void | |
| */ | |
| public function setDomain($domain) { | |
| if (is_string($domain)) { | |
| $this->domain = $domain; | |
| } | |
| } | |
| /** | |
| * Sets the Session email. | |
| * | |
| * We need this set when creating a session to fetch | |
| * the bugzilla id of the user. If this is not set, | |
| * the user bugzilla_id will always be zero. | |
| * | |
| * @param string $email | |
| * The email address to set. | |
| * | |
| * @return void | |
| */ | |
| public function setEmail($email) { | |
| $this->email = $email; | |
| } | |
| /** | |
| * Retrives the Session email. | |
| * | |
| * @return string Returns the email address. | |
| */ | |
| public function getEmail() { | |
| return $this->email; | |
| } | |
| /** | |
| * Retrieves the Friend instance. | |
| * | |
| * If the Friend instance hasn't been set or isn't of the correct type, | |
| * it initializes a new one. | |
| * | |
| * @return Friend | |
| * The Friend instance. | |
| */ | |
| public function getFriend() { | |
| $username = $this->getUsername(); | |
| if (!is_null($this->Friend)) { | |
| return $this->Friend; | |
| } | |
| else if (is_null($this->Friend) && !empty($username)) { | |
| $this->Friend = new Friend(); | |
| $this->Friend->setUID($username); | |
| $this->Friend->selectFriend($this->Friend->selectFriendID("uid", $username)); | |
| $this->Friend->updateFriendFromLdap(); | |
| $this->Friend->setBugzillaID($this->getBugzillaID()); | |
| return $this->Friend; | |
| } | |
| return new Friend(); | |
| } | |
| /** | |
| * Sets the Friend instance. | |
| * | |
| * @param Friend $friend | |
| * The Friend instance to set. | |
| * | |
| * @return void | |
| * | |
| * @deprecated | |
| */ | |
| public function setFriend($friend) { | |
| trigger_error("Deprecated function called.", E_USER_NOTICE); | |
| } | |
| /** | |
| * Retrieves the Generated Session Id. | |
| * | |
| * @return string | |
| * The generated session id. | |
| */ | |
| public function getGID() { | |
| return $this->gid; | |
| } | |
| /** | |
| * Sets the Generated Session Id. | |
| * | |
| * @param string $gid | |
| * The generated session id to set. | |
| */ | |
| public function setGID($gid) { | |
| $this->gid = $gid; | |
| } | |
| /** | |
| * Generates a unique Session ID. | |
| * | |
| * The method combines a unique ID and a random number to generate | |
| * a hash which serves as a session ID. | |
| * | |
| * @return string | |
| * The generated session ID. | |
| * | |
| * @todo Consider using a more secure hashing algorithm than md5 | |
| * in future revisions. | |
| */ | |
| public function generateGID() { | |
| return md5(uniqid(mt_rand(), TRUE)); | |
| } | |
| /** | |
| * Retrieve the login page URL. | |
| * | |
| * @return string | |
| * The URL of the login page. | |
| */ | |
| public function getLoginPageURL() { | |
| return $this->login_page; | |
| } | |
| /** | |
| * Set the value for the `login_page` property. | |
| * | |
| * @param string $login_page | |
| * The URL to be set for the login page. | |
| * | |
| * @return void | |
| */ | |
| public function setLoginPage($login_page) { | |
| if (is_string($login_page) && filter_var($login_page, FILTER_VALIDATE_URL)) { | |
| $this->login_page = $login_page; | |
| } | |
| } | |
| /** | |
| * Redirect the client to the login page. | |
| * | |
| * This method prevents caching and sends a 303 See Other | |
| * HTTP status code to indicate a non-permanent redirect. | |
| * | |
| * @return void | |
| */ | |
| public function redirectToLogin() { | |
| $this->App->preventCaching(); | |
| header("Location: " . $this->login_page, TRUE, 303); | |
| exit; | |
| } | |
| /** | |
| * Retrieves the subnet associated with the session. | |
| * | |
| * @return string | |
| * The associated subnet. | |
| */ | |
| public function getSubnet() { | |
| return $this->subnet; | |
| } | |
| /** | |
| * Sets the subnet associated with the session. | |
| * | |
| * @param string $subnet | |
| * The subnet to associate with the session. | |
| */ | |
| public function setSubnet($subnet) { | |
| $this->subnet = $subnet; | |
| } | |
| /** | |
| * Retrieve the Class-C subnet of the client's IP address. | |
| * | |
| * Class-C subnet masks the last octet of the IP address to .0. | |
| * | |
| * @return string | |
| * Class-C subnet of the client's IP address. | |
| */ | |
| public function getClientSubnet() { | |
| $ipAddress = $this->App->getRemoteIPAddress(); | |
| return substr($ipAddress, 0, strrpos($ipAddress, ".")) . ".0"; | |
| } | |
| /** | |
| * Retrieves the last update time for the session. | |
| * | |
| * @return string | |
| * The date/time of the last update on the session. | |
| * Format is "Y-m-d H:i:s.u". | |
| */ | |
| public function getUpdatedAt() { | |
| return $this->updated_at; | |
| } | |
| /** | |
| * Sets the last update time for the session. | |
| * | |
| * @param string $updated_at | |
| * The date/time to set as the last update time. | |
| * Expected format is "Y-m-d H:i:s.u". | |
| */ | |
| public function setUpdatedAt($updated_at) { | |
| $this->updated_at = $updated_at; | |
| } | |
| /** | |
| * Retrieve the Eclipse username. | |
| * | |
| * @return string | |
| * The Eclipse username, or empty string if not set. | |
| */ | |
| public function getUsername() { | |
| return $this->username; | |
| } | |
| /** | |
| * Set the Eclipse username. | |
| * | |
| * @param string $username | |
| * The username to be set. | |
| * | |
| * @return string | |
| * The Eclipse username, or empty string if not set. | |
| */ | |
| public function setUsername($username) { | |
| if (is_string($username)) { | |
| $this->username = $username; | |
| } | |
| return $this->username; | |
| } | |
| /** | |
| * Check if user has given consent to use cookies. | |
| * | |
| * @return bool | |
| * True if the user has given consent, false otherwise. | |
| */ | |
| public function hasCookieConsent() { | |
| return $this->App->hasCookieConsent(); | |
| } | |
| /** | |
| * Determine if the user is logged in. | |
| * | |
| * @return bool | |
| * True if the user is logged in, false otherwise. | |
| * | |
| * @deprecated use $this->isLoggedIn() instead. | |
| */ | |
| public function getIsLoggedIn() { | |
| trigger_error("Deprecated function called.", E_USER_NOTICE); | |
| return $this->isLoggedIn(); | |
| } | |
| /** | |
| * Determine if this session is logged in. | |
| * | |
| * @author droy | |
| * @since 2014-07-03 | |
| * | |
| * @return bool | |
| */ | |
| public function isLoggedIn() { | |
| return $this->getGID() != ""; | |
| } | |
| /** | |
| * Check if session is persistent. | |
| * | |
| * Assumes session is persistent only if the user has provided cookie consent. | |
| * | |
| * @return int | |
| * Returns 1 if session is persistent, otherwise 0. | |
| */ | |
| public function getIsPersistent() { | |
| return $this->hasCookieConsent() ? 1 : 0; | |
| } | |
| /** | |
| * Set session persistence. | |
| * | |
| * @param mixed $_is_persistent | |
| * Deprecated parameter. | |
| * | |
| * @deprecated This method is deprecated and should not be used. | |
| */ | |
| public function setIsPersistent($_is_persistent) { | |
| trigger_error("Deprecated function called.", E_USER_NOTICE); | |
| } | |
| /** | |
| * Validate session based on browser cookie. | |
| * | |
| * @return bool | |
| * Returns TRUE if the session is valid, FALSE otherwise. | |
| */ | |
| public function validate() { | |
| if (!isset($_COOKIE[self::SESSION_NAME])) { | |
| return FALSE; | |
| } | |
| $cookie = $_COOKIE[self::SESSION_NAME]; | |
| if ($this->load($cookie)) { | |
| $this->maintenance(); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Load session based on GID. | |
| * The user must be in the same subnet for session to be valid. | |
| * | |
| * @param string $_gid | |
| * The GID to use for session retrieval. | |
| * | |
| * @return bool | |
| * Returns TRUE if session is successfully loaded, FALSE otherwise. | |
| */ | |
| public function load($_gid) { | |
| if (empty($_gid)) { | |
| return FALSE; | |
| } | |
| // Sanitize the inputs. | |
| $gid = $this->App->quoteAndSanitize($_gid); | |
| $subnet = $this->App->quoteAndSanitize($this->getClientSubnet()); | |
| $sql = "SELECT /* USE MASTER */ gid, bugzilla_id, subnet, updated_at, is_persistent, username | |
| FROM sessions | |
| WHERE gid = $gid | |
| AND subnet = $subnet"; | |
| $result = $this->App->eclipse_sql($sql); | |
| if ($result && mysql_num_rows($result) > 0) { | |
| $myrow = mysql_fetch_assoc($result); | |
| $this->setGID($_gid); | |
| $this->setBugzillaID($myrow['bugzilla_id']); | |
| $this->setSubnet($myrow['subnet']); | |
| $this->setUpdatedAt($myrow['updated_at']); | |
| $this->setUsername($myrow['username']); | |
| $is_persistent = $this->App->quoteAndSanitize($this->getIsPersistent()); | |
| $sql = "UPDATE sessions SET updated_at = NOW(), is_persistent = $is_persistent WHERE gid = $gid"; | |
| $this->App->eclipse_sql($sql); | |
| $this->setEclipseSessionCookies(); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Destroys the session. | |
| * | |
| * @param @deprecated bool $flush_all_sessions | |
| * Whether to flush all sessions associated with the current user. | |
| */ | |
| public function destroy($flush_all_sessions = TRUE) { | |
| $Friend = $this->getFriend(); | |
| $username = $this->getUsername(); | |
| $gid = $this->getGID(); | |
| // Delete session by username as the user may have more than 1 session. | |
| if (!empty($username)) { | |
| $sql = "DELETE FROM sessions WHERE username = " . $this->App->quoteAndSanitize($username); | |
| $this->App->eclipse_sql($sql); | |
| } | |
| // Delete session by guid as fallback for compability with older versions. | |
| if (!empty($gid)) { | |
| $sql = "DELETE FROM sessions WHERE gid = " . $this->App->quoteAndSanitize($this->getGID()) . " LIMIT 1"; | |
| $this->App->eclipse_sql($sql); | |
| } | |
| // Clear cookies. | |
| setcookie("TAKEMEBACK", "", 0, "/", ".eclipse.org"); | |
| setcookie("fud_session_2015", "", 0, "/forums/", ".eclipse.org"); | |
| setcookie(self::SESSION_NAME, "", 0, "/", $this->getDomain(), 1, TRUE); | |
| setcookie(self::ENV, "", 0, "/", $this->getDomain(), 0, TRUE); | |
| } | |
| /** | |
| * Create an Eclipse Session. | |
| * | |
| * @param string $mail | |
| * | |
| * @return void | |
| */ | |
| public function create() { | |
| // Initializing session attributes. | |
| $this->setGID($this->generateGID()); | |
| $this->setSubnet($this->getClientSubnet()); | |
| $this->setUpdatedAt($this->App->getCURDATE()); | |
| $Friend = new Friend(); | |
| $this->setBugzillaID($Friend->getBugzillaIDFromEmail($this->getEmail())); | |
| // Construct SQL query. | |
| $sql = "INSERT INTO sessions ( | |
| gid, bugzilla_id, subnet, updated_at, is_persistent, username) | |
| VALUES ( | |
| {$this->App->quoteAndSanitize($this->getGID())}, | |
| {$this->App->quoteAndSanitize($this->getBugzillaID())}, | |
| {$this->App->quoteAndSanitize($this->getSubnet())}, | |
| NOW(), | |
| {$this->App->quoteAndSanitize($this->getIsPersistent())}, | |
| {$this->App->quoteAndSanitize($this->getUsername())})"; | |
| $this->App->eclipse_sql($sql); | |
| // Log event if not in development mode. | |
| if (!$this->App->devmode) { | |
| $this->EvtLog->setLogTable("sessions"); | |
| $this->EvtLog->setPK1($Friend->getBugzillaID()); | |
| $this->EvtLog->setPK2($this->App->getRemoteIPAddress()); | |
| $this->EvtLog->setLogAction("INSERT"); | |
| $this->EvtLog->insertModLog("apache"); | |
| } | |
| $this->setEclipseSessionCookies(); | |
| } | |
| /** | |
| * Set Eclipse Session Cookies. | |
| * | |
| * @return bool | |
| * Returns TRUE if the cookies were successfully set, FALSE otherwise. | |
| */ | |
| public function setEclipseSessionCookies() { | |
| $gid = $this->getGID(); | |
| // If GID is not set or cookies have already been sent, return FALSE. | |
| if (empty($gid) || $this->getCookiesSent()) { | |
| return FALSE; | |
| } | |
| $this->setCookiesSent(TRUE); | |
| // By default, cookies expire at the end of the session. | |
| $cookie_expiry = 0; | |
| // If the session is persistent, set the cookie to expire in a week. | |
| if ($this->getIsPersistent()) { | |
| // 7 days in seconds. | |
| $cookie_expiry = time() + (3600 * 24 * 7); | |
| } | |
| // Set the session cookie. | |
| setcookie(self::SESSION_NAME, $gid, $cookie_expiry, "/", $this->getDomain(), TRUE, TRUE); | |
| // Set an environment cookie. This ensures the session remains consistent between HTTP and HTTPS. | |
| // Using "S" for Secure. Further environment data can be appended as needed. | |
| setcookie(self::ENV, "S", $cookie_expiry, "/", $this->getDomain(), FALSE, TRUE); | |
| return TRUE; | |
| } | |
| /** | |
| * Performs maintenance tasks for sessions. | |
| * | |
| * Removes stale sessions that haven't been updated for more than 8 days. | |
| * This ensures that the sessions database table does not grow indefinitely | |
| * with old unused sessions. | |
| */ | |
| public function maintenance() { | |
| $App = new App(); | |
| // Delete sessions that haven't been updated for more than 8 days. | |
| // Users can regenerate sessions by visiting accounts.eclipse.org. | |
| $sql = "DELETE FROM sessions | |
| WHERE updated_at < DATE_SUB(NOW(), INTERVAL 8 DAY)"; | |
| $App->eclipse_sql($sql); | |
| } | |
| /** | |
| * Update Friend object in Sessions table. | |
| * | |
| * @param Friend|null $Friend | |
| * @deprecated The Friend object to update. | |
| * We don't serialize Friend() instances anymore. | |
| * | |
| * @return bool | |
| * Returns TRUE if the update is successful, FALSE otherwise. | |
| */ | |
| public function updateSessionData($Friend = NULL) { | |
| $session_gid = $this->getGID(); | |
| if ($session_gid) { | |
| $gid = $this->App->quoteAndSanitize($session_gid); | |
| // Update session timestamp. | |
| $sql = "UPDATE sessions SET updated_at = NOW(), | |
| WHERE gid = {$gid}"; | |
| $this->App->eclipse_sql($sql); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| } |