admin.aprsto/htdocs/includes/session.class.php
Steve White ae9ece5266 *** Initial Commit of Files for APRS.TO Backend Administration Panel ***
This code is non-functional at this point.
2025-02-02 15:53:34 -05:00

558 lines
20 KiB
PHP

<?php
class Session
{
private static $_singletonInstance = null;
private $_localSessionData = [];
protected $_stationViewCounter = [];
public string $_lastError = '';
protected bool $_loggedIn;
protected User $_user;
protected UserSettings $_userSettings;
public function __construct()
{
// PHP Session Support Enabled?
$this->_init();
// Check for a persistent authentication token
if (!$this->_loggedIn) $this->checkPersistentAuthToken();
// Override mobile user setting with the value in the latest request URL
if (isset($_REQUEST['isMobile'])) $this->isMobile = intval($_REQUEST['isMobile']);
}
public function __destruct()
{
session_write_close();
$_SESSION = array();
$this->_localSessionData = array();
}
/**
* Sets the $value for a $key
*
* @param string $key (Name of the paramter to set)
* @param mixed $value (Value of the paramter to set)
*/
public function __set($key, $value)
{
$this->_localSessionData[$key] = $value;
}
/**
* Returns the $value for a $key
*
* @param string $key (Name of the paramter to retreive)
* @return mixed $value (Value associated with $key)
*/
public function __get($key)
{
return $this->_localSessionData[$key] ?? null;
}
/**
* Logs the user out of the current session and resets all session variables.
*/
public function __isset($key)
{
return isset($this->_localSessionData[$key]);
}
/**
* Initialize the current session with defaults
* This is called for every page request
* sets _lastError to an empty string.
* @param boolean $reInit (true = Reinit current session)
*/
private function _init($reInit = false)
{
if ((session_id() == false && session_start()) || $reInit)
{
// Local session storage
if (!isset($_SESSION['store'])) $_SESSION['store'] = [];
$this->_localSessionData = &$_SESSION['store'];
// Session user
if (!isset($_SESSION['user'])) $_SESSION['user'] = new User(0);
$this->_user = &$_SESSION['user'];
// User logged in?
$this->_loggedIn = $this->_user->isExistingObject();
// Session user settings
if (!isset($_SESSION['user_settings'])) $_SESSION['user_settings'] = new UserSettings(0);
$this->_userSettings = &$_SESSION['user_settings'];
// Station view counter deduplication
if (!isset($_SESSION['station_view_counter'])) $_SESSION['station_view_counter'] = [];
$this->_stationViewCounter = &$_SESSION['station_view_counter'];
// Initital session start time (only set once)
if (!isset($this->start_time)) $this->start_time = time();
// Flush session hit counter store thats used to de-dupe station views
if (!isset($this->view_counter_history)) $this->view_counter_history = [];
$expire = time() - (60 * (getWebsiteConfig("view_counter_hysteresis") ?? 60));
foreach($this->view_counter_history AS $k => $v)
{
if ($v < $expire) unset($this->view_counter_history[$k]);
}
// Mobile User? Override setting with the value in the latest request URL
if (isset($_REQUEST['isMobile'])) $this->isMobile = intval($_REQUEST['isMobile']);
if (is_null($this->isMobile)) $this->isMobile = false;
// Set the session defaults to the user preferences (if logged in)
if (is_null($this->imperialUnits) && $this->_loggedIn && $this->_userSettings->isExistingObject())
{
$this->imperialUnits = $this->_userSettings->useImperialUnits;
}
// Set the session defaults to the user preferences (if logged in)
if (is_null($this->useMilTime) && $this->_loggedIn && $this->_userSettings->isExistingObject())
{
$this->useMilTime = $this->_userSettings->useMilTime;
}
// Set defaults if not overridden by user preferences (check browser locale)
if (!isset($this->imperialUnits) || is_null($this->imperialUnits)) $this->isImperialUnitUser();
// Map settings use a default location, if the user has set one, use that, otherwise get from config
if ($this->_userSettings->isExistingObject() && $this->_userSettings->loginApplyMapCenterZoom)
{
$this->defaultMapCenterLatitude = $this->_userSettings->defaultMapCenterLatitude;
$this->defaultMapCenterLongitude = $this->_userSettings->defaultMapCenterLongitude;
$this->defaultMapZoom = $this->_userSettings->defaultMapZoom;
}
else
{
$this->defaultMapCenterLatitude = getWebsiteConfig('default_latitude');
$this->defaultMapCenterLongitude = getWebsiteConfig('default_longitude');
$this->defaultMapZoom = getWebsiteConfig('default_zoom');
}
// This is a failsafe for ensuring that basic map settings are set.
if (!isset($this->showAprsPositions)) $this->showAprsPositions = true;
if (!isset($this->showCwopPositions)) $this->showCwopPositions = true;
if (!isset($this->showOgnPositions)) $this->showOgnPositions = true;
if (!isset($this->showCbAprsPositions)) $this->showCbAprsPositions = true;
if (!isset($this->showInternetMarkers)) $this->showInternetMarkers = true;
if (!isset($this->showStationaryMarkers)) $this->showStationaryMarkers = true;
if (!isset($this->showOgflymMarkers)) $this->showOgflymMarkers = true;
if (!isset($this->showUnknownMarkers)) $this->showUnknownMarkers = true;
if (!isset($this->showMaidenheadGrid)) $this->showMaidenheadGrid = false;
if (!isset($this->showDayNightOverlay)) $this->showDayNightOverlay = false;
if (!isset($this->dayNightRefreshInterval)) $this->dayNightRefreshInterval = 5000;
if (!isset($this->tailTimeLength)) $this->tailTimeLength = 60;
if (!isset($this->showStationMarkers)) $this->showStationMarkers = true;
if (!isset($this->showObjectMarkers)) $this->showObjectMarkers = true;
if (!isset($this->showItemMarkers)) $this->showItemMarkers = true;
}
}
/**
* Returnes an initialized Session
*
* @return Session
*/
public static function getInstance()
{
if (self::$_singletonInstance === null) {
self::$_singletonInstance = new Session();
}
return self::$_singletonInstance;
}
/**
* Returnes the user record associated with the initialized Session
*
* @return User (User object, or an unititlized user object if none loaded)
*/
public function getUser()
{
if ($this->_user) return $this->_user;
return new User(0);
}
/**
* Returns the user settings object associated with the initialized Session
*
* @param boolean $reload (Reload settings from the database)
* @return UserSettings (User settings object, or an unititlized user object if none loaded)
*/
public function getUserSettings($reload = false)
{
if ($reload)
$this->_userSettings = UserSettingsRepository::getInstance()->getObjectByUserId($this->_user->_id);
if (!$this->_userSettings->isExistingObject())
{
$this->_userSettings->mpShowPath = 1;
$this->_userSettings->mpShowCourseSpeed = 1;
$this->_userSettings->mpShowMice = 1;
$this->_userSettings->mpShowComment = 1;
$this->_userSettings->mpShowPhgRng = 1;
$this->_userSettings->mpShowDistance = 1;
$this->_userSettings->mpShowWeather = 1;
$this->_userSettings->mpUseLargeIcon = 1;
$this->_userSettings->useMaxModal = 0;
}
return $this->_userSettings;
}
/**
* Returns all Session Values
* @return array All currently saves session settings
*/
public function getAll()
{
return $this->_localSessionData ?? [];
}
/**
* Process a user login in the active session
* This will attempt to load the user record and validate the password.
*
* @param string $username (Username of the user)
* @param string $password (Password of the user)
* @return boolean (True if the login was successful, false if not)
*/
public function loginUser($username, $password)
{
// Remove whitespace
$username = trim($username);
// Only process login if not already logged in
if (!$this->_loggedIn)
{
// Test the username and set it if passes (no )
if (strlen($username) <= 4 || preg_match('/[^a-z_\-0-9]/i', $username) == 0)
{
// Username is good, attempt to pull the record
$user = UserRepository::getInstance()->getObjectByUsername($username);
if ($user->isExistingObject())
{
if ($user->verifyPassword($password))
{
$this->_loggedIn = true;
$this->_user = $user;
// Get the user User Settings
$this->_userSettings = UserSettingsRepository::getInstance()->getObjectByUserId($this->_user->_id);
return true;
}
else
{
$this->_lastError = "Password specified was not correct.";
}
}
else
{
$this->_lastError = "The username could not found.";
}
}
else
{
$this->_lastError = "Username is not valid for this site.";
}
}
else
{
$this->_lastError = "Already logged in.";
}
}
/**
* Logs the user out of the current session and resets all session variables.
*/
public function logOutUser()
{
unset($this->_user);
unset($this->_userSettings);
$this->_loggedIn = false;
// Delete cookies
$cookie_prefix = getWebsiteConfig('cookie_prefix');
$cookie_domain = getWebsiteConfig('cookie_domain');
$cookie_secure = getWebsiteConfig('cookie_secure') == 1 ? true : false;
$cookie_httponly = getWebsiteConfig('cookie_httponly') == 1 ? true : false;
setcookie("{$cookie_prefix}_login", '', time()-3600, '/', $cookie_domain, $cookie_secure, $cookie_httponly);
setcookie("{$cookie_prefix}_identifier", '', time()-3600, '/', $cookie_domain, $cookie_secure, $cookie_httponly);
setcookie("{$cookie_prefix}_selector", '', time()-3600, '/', $cookie_domain, $cookie_secure, $cookie_httponly);
// Delete the cookie entries before re-initalizing session
unset($_COOKIE['aprs_login']);
unset($_COOKIE['aprs_identifier']);
unset($_COOKIE['aprs_selector']);
$_SESSION = [];
$this->_init(true);
}
/**
* Sets a persistent authentication token and cookies ("rememeber me")
* @return bool (True if the token was set, otherwise false)
*/
public function setPersistentAuthToken()
{
// No point in doing anything if not logged in
if ($this->_loggedIn)
{
$token = $this->_user->generatePersistentAuthToken();
if (is_array($token) && $token['id'])
{
// Set Cookie expiration for 1 month
$cookie_prefix = getWebsiteConfig('cookie_prefix');
$cookie_domain = getWebsiteConfig('cookie_domain');
$cookie_secure = getWebsiteConfig('cookie_secure') == 1 ? true : false;
$cookie_httponly = getWebsiteConfig('cookie_httponly') == 1 ? true : false;
setcookie("{$cookie_prefix}_login", $this->_user->userName, $token['expiration'], '/', $cookie_domain, $cookie_secure, $cookie_httponly);
setcookie("{$cookie_prefix}_identifier", $token['identifier'], $token['expiration'], '/', $cookie_domain, $cookie_secure, $cookie_httponly);
setcookie("{$cookie_prefix}_selector", $token['selector'], $token['expiration'], '/', $cookie_domain, $cookie_secure, $cookie_httponly);
return true;
}
else
{
$this->_lastError = "An error occured creating persistent login token.";
}
}
else
{
$this->_lastError = "Not logged in.";
}
return false;
}
/**
* Checks the persistent authentication token and legs the user in if set
* @return bool (True if the token was set, otherwise false)
*/
public function checkPersistentAuthToken()
{
// No point in doing anything if logged in
if (!$this->_loggedIn && isset($_COOKIE['aprs_login']))
{
// Attempt to find the user record
$username = trim($_COOKIE['aprs_login']);
$user = UserRepository::getInstance()->getObjectByUsername($username);
if ($user->isExistingObject())
{
if ($user->validatePersistentAuthToken($_COOKIE['aprs_selector'] ?? '', $_COOKIE['aprs_identifier'] ?? ''))
{
// Get the user User Settings
$this->_userSettings = UserSettingsRepository::getInstance()->getObjectByUserId($user->_id);
$this->_loggedIn = true;
$this->_user = $user;
return true;
}
}
}
}
/**
* Checks the station view cache to see if a station has been viewed recenely
*/
public function incrementStationCounter($station)
{
if (getWebsiteConfig("view_counter_increment_enabled") == '0') return;
if ($station->isExistingObject() && !array_key_exists($station->id, $this->_stationViewCounter) && !$this->isBot())
{
// Check the deduplication/spam table
$pdo = PDOMysqlConnection::getInstance();
$expire = time() - (60 * (getWebsiteConfig("view_counter_hysteresis") ?? 60));
$sql = "SELECT * FROM station_view_count_tracker WHERE station_id = ? AND expire_timestamp > ? AND (user_id = ? OR ip_address = INET_ATON(?)) LIMIT 1";
$stmt = $pdo->prepareAndExec($sql, [$station->id, $expire, $this->getUser()->id, $this->getClientIp()]);
if ($record = $stmt->fetch(PDO::FETCH_ASSOC))
{
// Already recorded a hit, skip
return;
}
$this->_stationViewCounter[$station->id] = time();
// Add to the the deduplication/spam table
$expire = time() + (60 * (getWebsiteConfig("view_counter_hysteresis") ?? 60));
$sql = "INSERT INTO station_view_count_tracker (user_id, ip_address, station_id, expire_timestamp) VALUES(?, INET_ATON(?), ?, ?) LIMIT 1";
$stmt = $pdo->prepareAndExec($sql, [$this->getUser()->id ?? 0, $this->getClientIp(), $station->id, $expire]);
}
}
/**
* Records the a station browse (view) event
*/
public function recordStationBrowseHistory($station_id)
{
if (!isInt($station_id) || (isset($this->lastViewedStationId) && $station_id == $this->lastViewedStationId) || !$this->isLoggedIn()) return false;
// Only record history for valid users
if ($this->getUser()->isExistingObject())
{
$ret = $this->getUser()->recordStationBrowseHistory($station_id);
$this->_lastError = $this->getUser()->getLastError();
$this->lastViewedStationId = $station_id;
return $ret;
}
}
/**
* Logs the user out of the current session and resets all session variables.
*/
public function isLoggedIn()
{
return $this->_loggedIn;
}
public function getURL($baseUrl, array $queryArray = [])
{
$path = array_slice(explode('/', $baseUrl), 1);
$section = array_shift($path);
$view = str_replace('.php', '', array_shift($path));
$newUrl = $baseUrl;
if ($section == "views")
{
$newUrl = '/v/' . $view;
}
if ($section == "account")
{
$newUrl = '/a/' . $view;
}
else if (isset($queryArray['id']) && $section == 'station')
{
$newUrl = '/' . $section . '/' . $queryArray['id'] . ($section != $view ? '/' . $view : '');
unset($queryArray['id']);
}
else if (isset($queryArray['s']) && $section == 'station')
{
$newUrl = '/' . $section . '/' . $queryArray['s'] . ($section != $view ? '/' . $view : '');
unset($queryArray['s']);
}
// Make the URL
return $newUrl . $this->getQuerySting($queryArray, '?');
}
public function getQuerySting(array $queryArray = [], $append = '&')
{
// Make the URL
return (sizeof($queryArray) ? $append . http_build_query($queryArray) : '');
}
public function isImperialUnitUser()
{
// See if the current value is being overridden
if (isset($_GET['imperialUnits']) && $_GET['imperialUnits'])
{
$this->imperialUnits = 1;
}
else if (isset($_GET['imperialUnits']) && !$_GET['imperialUnits'])
{
$this->imperialUnits = 0;
}
// Return stored (or just set) session value
if (isset($this->imperialUnits)) return $this->imperialUnits;
// Initialize the default for the users location
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
{
// USA
if (substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5) == 'en-US'
// Myanmar / Burma
|| substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) == 'my'
// Liberia
|| substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5) == 'en-LR')
{
$this->imperialUnits = 1;
}
else
{
$this->imperialUnits = 0;
}
}
}
/**
* Returns the client IP address
*
* @return string IP address of the client
*/
public function getClientIp()
{
static $proxies;
if (is_null($proxies))
{
$proxies = explode(',', getWebsiteConfig("reverse_proxy_ips") ?? '');
}
if (!is_array($proxies) || sizeof($proxies) == 0)
{
return $_SERVER['REMOTE_ADDR'] ?? $_SERVER['HTTP_CLIENT_IP'] ?? '';
}
return (in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'])
?? $_SERVER['REMOTE_ADDR']
?? $_SERVER['HTTP_CLIENT_IP']
?? '';
}
/**
* Detects whether the user agent is believed to be a robot or spider
*
* @return string IP address of the client
*/
public function isBot()
{
if ( !empty($_SERVER['HTTP_USER_AGENT']) and preg_match('/abacho|accona|AddThis|AdsBot|ahoy|AhrefsBot|AISearchBot|alexa|altavista|anthill|appie|applebot|arale|araneo|AraybOt|ariadne|arks|aspseek|ATN_Worldwide|Atomz|baiduspider|baidu|bbot|bingbot|bing|Bjaaland|BlackWidow|BotLink|bot|boxseabot|bspider|calif|CCBot|ChinaClaw|christcrawler|CMC\/0\.01|combine|confuzzledbot|contaxe|CoolBot|cosmos|crawler|crawlpaper|crawl|curl|cusco|cyberspyder|cydralspider|dataprovider|digger|DIIbot|DotBot|downloadexpress|DragonBot|DuckDuckBot|dwcp|EasouSpider|ebiness|ecollector|elfinbot|esculapio|ESI|esther|eStyle|Ezooms|facebookexternalhit|facebook|facebot|fastcrawler|FatBot|FDSE|FELIX IDE|fetch|fido|find|Firefly|fouineur|Freecrawl|froogle|gammaSpider|gazz|gcreep|geona|Getterrobo-Plus|get|girafabot|golem|googlebot|\-google|grabber|GrabNet|griffon|Gromit|gulliver|gulper|hambot|havIndex|hotwired|htdig|HTTrack|ia_archiver|iajabot|IDBot|Informant|InfoSeek|InfoSpiders|INGRID\/0\.1|inktomi|inspectorwww|Internet Cruiser Robot|irobot|Iron33|JBot|jcrawler|Jeeves|jobo|KDD\-Explorer|KIT\-Fireball|ko_yappo_robot|label\-grabber|larbin|legs|libwww-perl|linkedin|Linkidator|linkwalker|Lockon|logo_gif_crawler|Lycos|m2e|majesticsEO|marvin|mattie|mediafox|mediapartners|MerzScope|MindCrawler|MJ12bot|mod_pagespeed|moget|Motor|msnbot|muncher|muninn|MuscatFerret|MwdSearch|NationalDirectory|naverbot|NEC\-MeshExplorer|NetcraftSurveyAgent|NetScoop|NetSeer|newscan\-online|nil|none|Nutch|ObjectsSearch|Occam|openstat.ru\/Bot|packrat|pageboy|ParaSite|patric|pegasus|perlcrawler|phpdig|piltdownman|Pimptrain|pingdom|pinterest|pjspider|PlumtreeWebAccessor|PortalBSpider|psbot|rambler|Raven|RHCS|RixBot|roadrunner|Robbie|robi|RoboCrawl|robofox|Scooter|Scrubby|Search\-AU|searchprocess|search|SemrushBot|Senrigan|seznambot|Shagseeker|sharp\-info\-agent|sift|SimBot|Site Valet|SiteSucker|skymob|SLCrawler\/2\.0|slurp|snooper|solbot|speedy|spider_monkey|SpiderBot\/1\.0|spiderline|spider|suke|tach_bw|TechBOT|TechnoratiSnoop|templeton|teoma|titin|topiclink|twitterbot|twitter|UdmSearch|Ukonline|UnwindFetchor|URL_Spider_SQL|urlck|urlresolver|Valkyrie libwww\-perl|verticrawl|Victoria|void\-bot|Voyager|VWbot_K|wapspider|WebBandit\/1\.0|webcatcher|WebCopier|WebFindBot|WebLeacher|WebMechanic|WebMoose|webquest|webreaper|webspider|webs|WebWalker|WebZip|wget|whowhere|winona|wlm|WOLP|woriobot|WWWC|XGET|xing|yahoo|YandexBot|YandexMobileBot|yandex|yeti|Zeus/i', $_SERVER['HTTP_USER_AGENT']))
{
return true; // 'Above given bots detected'
}
return false;
}
/**
* Returns the last error message set during a session action then
* sets _lastError to an empty string.
*
* @return string (Text of the last recorded error)
*/
public function getLastError()
{
$last_error = $this->_lastError;
$this->_lastError = '';
return $last_error;
}
}