| <?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 (ctype_digit($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; |
| } |
| |
| } |