blob: d2922e3f2b47acf0835b3f7059b7b356070ea4dc [file] [log] [blame]
<?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;
}
}