From 50f630f7c20bc2f9731c23c2c9b3c338673f8efb Mon Sep 17 00:00:00 2001 From: Mark Friedrich Date: Sat, 22 Dec 2018 11:43:46 +0100 Subject: [PATCH] - fixed some "potential" login issues, #718 - improved error logging in case of failed login attempts - improved ESI "access token" handling --- app/main/controller/accesscontroller.php | 61 ++++------ app/main/controller/api/map.php | 7 +- app/main/controller/api/user.php | 6 +- app/main/controller/ccp/sso.php | 71 ++++++----- app/main/controller/controller.php | 37 +++--- app/main/lib/config.php | 44 +++++-- app/main/lib/web.php | 36 +++--- app/main/model/charactermodel.php | 146 ++++++++++++++--------- app/pathfinder.ini | 8 +- public/templates/ui/info_panel.html | 2 +- 10 files changed, 237 insertions(+), 181 deletions(-) diff --git a/app/main/controller/accesscontroller.php b/app/main/controller/accesscontroller.php index 72322209..866378e5 100644 --- a/app/main/controller/accesscontroller.php +++ b/app/main/controller/accesscontroller.php @@ -26,7 +26,7 @@ class AccessController extends Controller { if($return = parent::beforeroute($f3, $params)){ // Any route/endpoint of a child class of this one, // requires a valid logged in user! - if( !$this->isLoggedIn($f3) ){ + if($this->isLoggedIn($f3) !== 'OK'){ // no character found or login timer expired $this->logoutCharacter($f3); // skip route handler and afterroute() @@ -40,51 +40,40 @@ class AccessController extends Controller { /** * get current character and check if it is a valid character * @param \Base $f3 - * @return bool + * @return string * @throws \Exception */ - protected function isLoggedIn(\Base $f3): bool { - $loginCheck = false; - if( $character = $this->getCharacter() ){ - if($this->checkLogTimer($f3, $character)){ - if($character->isAuthorized() === 'OK'){ - $loginCheck = true; + protected function isLoggedIn(\Base $f3): string { + $loginStatus = 'UNKNOWN'; + if($character = $this->getCharacter()){ + if($character->checkLoginTimer()){ + if(( $authStatus = $character->isAuthorized()) === 'OK'){ + $loginStatus = 'OK'; + }else{ + $loginStatus = $authStatus; } + }else{ + $loginStatus = 'MAX LOGIN TIME EXCEEDED'; } + }else{ + $loginStatus = 'NO SESSION FOUND'; } - return $loginCheck; - } - - /** - * checks whether a user/character is currently logged in - * @param \Base $f3 - * @param Model\CharacterModel $character - * @return bool - */ - private function checkLogTimer(\Base $f3, Model\CharacterModel $character){ - $loginCheck = false; - + // log character access status in debug mode if( - !$character->dry() && - $character->lastLogin + $loginStatus !== 'OK' && + $f3->get('DEBUG') === 3 ){ - // check logIn time - $logInTime = new \DateTime($character->lastLogin); - $now = new \DateTime(); - - $timeDiff = $now->diff($logInTime); - - $minutes = $timeDiff->days * 60 * 24 * 60; - $minutes += $timeDiff->h * 60; - $minutes += $timeDiff->i; - - if($minutes <= Config::getPathfinderData('timer.logged')){ - $loginCheck = true; - } + self::getLogger('CHARACTER_ACCESS')->write( + sprintf(Model\CharacterModel::LOG_ACCESS, + $character->_id , + $loginStatus, + $character->name + ) + ); } - return $loginCheck; + return $loginStatus; } /** diff --git a/app/main/controller/api/map.php b/app/main/controller/api/map.php index 1491ce54..7ba6d18b 100644 --- a/app/main/controller/api/map.php +++ b/app/main/controller/api/map.php @@ -209,6 +209,9 @@ class Map extends Controller\AccessController { $validInitData = $validInitData ? !empty($structureData) : $validInitData; // get available wormhole types --------------------------------------------------------------------------- + /** + * @var $wormhole Model\Universe\WormholeModel + */ $wormhole = Model\Universe\BasicUniverseModel::getNew('WormholeModel'); $wormholesData = []; if($rows = $wormhole->find(null, ['order' => 'name asc'])){ @@ -424,7 +427,7 @@ class Map extends Controller\AccessController { $return->error = []; if( isset($formData['id']) ){ - $activeCharacter = $this->getCharacter(0); + $activeCharacter = $this->getCharacter(); /** * @var $map Model\MapModel @@ -870,7 +873,7 @@ class Map extends Controller\AccessController { $getMapUserData = (bool)$postData['getMapUserData']; $mapTracking = (bool)$postData['mapTracking']; $systemData = (array)$postData['systemData']; - $activeCharacter = $this->getCharacter(0); + $activeCharacter = $this->getCharacter(); $return = (object)[]; diff --git a/app/main/controller/api/user.php b/app/main/controller/api/user.php index 0425d5c3..f4548e0d 100644 --- a/app/main/controller/api/user.php +++ b/app/main/controller/api/user.php @@ -86,7 +86,7 @@ class User extends Controller\Controller{ $character->save(); // write login log -------------------------------------------------------------------- - self::getLogger('LOGIN')->write( + self::getLogger('CHARACTER_LOGIN')->write( sprintf(self::LOG_LOGGED_IN, $user->_id, $user->name, @@ -260,7 +260,7 @@ class User extends Controller\Controller{ $formData = $data['formData']; try{ - if($activeCharacter = $this->getCharacter(0)){ + if($activeCharacter = $this->getCharacter()){ $user = $activeCharacter->getUser(); // captcha is send -> check captcha ------------------------------------------- @@ -371,7 +371,7 @@ class User extends Controller\Controller{ !empty($data['captcha']) && $data['captcha'] === $captcha ){ - $activeCharacter = $this->getCharacter(0); + $activeCharacter = $this->getCharacter(); $user = $activeCharacter->getUser(); if($user){ diff --git a/app/main/controller/ccp/sso.php b/app/main/controller/ccp/sso.php index 36609695..2f1a1352 100644 --- a/app/main/controller/ccp/sso.php +++ b/app/main/controller/ccp/sso.php @@ -24,11 +24,6 @@ class Sso extends Api\User{ */ const SSO_TIMEOUT = 4; - /** - * @var int expire time (seconds) for an valid "accessToken" - */ - const ACCESS_KEY_EXPIRE_TIME = 20 * 60; - // SSO specific session keys const SESSION_KEY_SSO = 'SESSION.SSO'; const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR'; @@ -71,7 +66,7 @@ class Sso extends Api\User{ if( isset($params['characterId']) && - ( $activeCharacter = $this->getCharacter(0) ) + ( $activeCharacter = $this->getCharacter() ) ){ // authentication restricted to a characterId ----------------------------------------------- // restrict login to this characterId e.g. for character switch on map page @@ -187,10 +182,7 @@ class Sso extends Api\User{ $accessData = $this->getSsoAccessData($getParams['code']); - if( - isset($accessData->accessToken) && - isset($accessData->refreshToken) - ){ + if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){ // login succeeded -> get basic character data for current login $verificationCharacterData = $this->verifyCharacterData($accessData->accessToken); @@ -205,10 +197,11 @@ class Sso extends Api\User{ if( isset($characterData->character) ){ // add "ownerHash" and SSO tokens - $characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash; - $characterData->character['crestAccessToken'] = $accessData->accessToken; - $characterData->character['crestRefreshToken'] = $accessData->refreshToken; - $characterData->character['esiScopes'] = Lib\Util::convertScopesString($verificationCharacterData->Scopes); + $characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash; + $characterData->character['esiAccessToken'] = $accessData->accessToken; + $characterData->character['esiAccessTokenExpires'] = $accessData->esiAccessTokenExpires; + $characterData->character['esiRefreshToken'] = $accessData->refreshToken; + $characterData->character['esiScopes'] = Lib\Util::convertScopesString($verificationCharacterData->Scopes); // add/update static character data $characterModel = $this->updateCharacter($characterData); @@ -338,7 +331,7 @@ class Sso extends Api\User{ * @param bool $authCode * @return null|\stdClass */ - public function getSsoAccessData($authCode){ + protected function getSsoAccessData($authCode){ $accessData = null; if( !empty($authCode) ){ @@ -354,10 +347,10 @@ class Sso extends Api\User{ /** * verify authorization code, and get an "access_token" data - * @param $authCode + * @param string $authCode * @return \stdClass */ - protected function verifyAuthorizationCode($authCode){ + protected function verifyAuthorizationCode(string $authCode){ $requestParams = [ 'grant_type' => 'authorization_code', 'code' => $authCode @@ -369,32 +362,35 @@ class Sso extends Api\User{ /** * get new "access_token" by an existing "refresh_token" * -> if "access_token" is expired, this function gets a fresh one - * @param $refreshToken + * @param string $refreshToken + * @param array $additionalOptions * @return \stdClass */ - public function refreshAccessToken($refreshToken){ + public function refreshAccessToken(string $refreshToken, array $additionalOptions = []){ $requestParams = [ 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken ]; - return $this->requestAccessData($requestParams); + return $this->requestAccessData($requestParams, $additionalOptions); } /** * request an "access_token" AND "refresh_token" data * -> this can either be done by sending a valid "authorization code" * OR by providing a valid "refresh_token" - * @param $requestParams + * @param array $requestParams + * @param array $additionalOptions * @return \stdClass */ - protected function requestAccessData($requestParams){ + protected function requestAccessData(array $requestParams, array $additionalOptions = []){ $verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint(); $verifyAuthCodeUrlParts = parse_url($verifyAuthCodeUrl); $accessData = (object) []; $accessData->accessToken = null; $accessData->refreshToken = null; + $accessData->esiAccessTokenExpires = 0; if($verifyAuthCodeUrlParts){ $contentType = 'application/x-www-form-urlencoded'; @@ -412,17 +408,30 @@ class Sso extends Api\User{ // content (parameters to send with) $requestOptions['content'] = http_build_query($requestParams); - $apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions); + $apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions, $additionalOptions); if($apiResponse['body']){ $authCodeRequestData = json_decode($apiResponse['body'], true); if( !empty($authCodeRequestData) ){ if( isset($authCodeRequestData['access_token']) ){ - // this token is required for endpoints that require Auth + // accessToken is required for endpoints that require Auth $accessData->accessToken = $authCodeRequestData['access_token']; } + if(isset($authCodeRequestData['expires_in'])){ + // expire time for accessToken + try{ + $timezone = $this->getF3()->get('getTimeZone')(); + $accessTokenExpires = new \DateTime('now', $timezone); + $accessTokenExpires->add(new \DateInterval('PT' . (int)$authCodeRequestData['expires_in'] . 'S')); + + $accessData->esiAccessTokenExpires = $accessTokenExpires->format('Y-m-d H:i:s'); + }catch(\Exception $e){ + $this->getF3()->error(500, $e->getMessage(), $e->getTrace()); + } + } + if(isset($authCodeRequestData['refresh_token'])){ // this token is used to refresh/get a new access_token when expires $accessData->refreshToken = $authCodeRequestData['refresh_token']; @@ -546,7 +555,7 @@ class Sso extends Api\User{ $character = Model\BasicModel::getNew('CharacterModel'); $character->getById((int)$characterData->character['id'], 0); $character->copyfrom($characterData->character, [ - 'id', 'name', 'ownerHash', 'crestAccessToken', 'crestRefreshToken', 'esiScopes', 'securityStatus' + 'id', 'name', 'ownerHash', 'esiAccessToken', 'esiAccessTokenExpires', 'esiRefreshToken', 'esiScopes', 'securityStatus' ]); $character->corporationId = $characterData->corporation; @@ -562,7 +571,7 @@ class Sso extends Api\User{ * -> This header is required for any Auth-required endpoints! * @return string */ - protected function getAuthorizationHeader(){ + protected function getAuthorizationHeader() : string { return base64_encode( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') . ':' . Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY') @@ -574,7 +583,7 @@ class Sso extends Api\User{ * -> throw error if url is broken/missing * @return string */ - static function getSsoUrlRoot(){ + static function getSsoUrlRoot() : string { $url = ''; if( \Audit::instance()->url(self::getEnvironmentData('CCP_SSO_URL')) ){ $url = self::getEnvironmentData('CCP_SSO_URL'); @@ -587,15 +596,15 @@ class Sso extends Api\User{ return $url; } - static function getAuthorizationEndpoint(){ + static function getAuthorizationEndpoint() : string { return self::getSsoUrlRoot() . '/oauth/authorize'; } - static function getVerifyAuthorizationCodeEndpoint(){ + static function getVerifyAuthorizationCodeEndpoint() : string { return self::getSsoUrlRoot() . '/oauth/token'; } - static function getVerifyUserEndpoint(){ + static function getVerifyUserEndpoint() : string { return self::getSsoUrlRoot() . '/oauth/verify'; } @@ -603,7 +612,7 @@ class Sso extends Api\User{ * get logger for SSO logging * @return \Log */ - static function getSSOLogger(){ + static function getSSOLogger() : \Log { return parent::getLogger('SSO'); } } \ No newline at end of file diff --git a/app/main/controller/controller.php b/app/main/controller/controller.php index 6d051caf..16530631 100644 --- a/app/main/controller/controller.php +++ b/app/main/controller/controller.php @@ -126,30 +126,29 @@ class Controller { protected function initSession(\Base $f3){ $session = null; - /** - * callback() for suspect sessions - * @param $session - * @param $sid - * @return bool - */ - $onSuspect = function($session, $sid){ - self::getLogger('SESSION_SUSPECT')->write( sprintf( - self::ERROR_SESSION_SUSPECT, - $sid, - $session->ip(), - $session->agent() - )); - // .. continue with default onSuspect() handler - // -> destroy session - return false; - }; - if( $f3->get('SESSION_CACHE') === 'mysql' && $this->getDB('PF') instanceof DB\SQL ){ - if(!headers_sent() && session_status()!=PHP_SESSION_ACTIVE){ + /** + * callback() for suspect sessions + * @param $session + * @param $sid + * @return bool + */ + $onSuspect = function($session, $sid){ + self::getLogger('SESSION_SUSPECT')->write( sprintf( + self::ERROR_SESSION_SUSPECT, + $sid, + $session->ip(), + $session->agent() + )); + // .. continue with default onSuspect() handler + // -> destroy session + return false; + }; + new DB\SQL\MySQL\Session($this->getDB('PF'), 'sessions', true, $onSuspect); } } diff --git a/app/main/lib/config.php b/app/main/lib/config.php index 75c4e3c2..26508a92 100644 --- a/app/main/lib/config.php +++ b/app/main/lib/config.php @@ -20,6 +20,16 @@ class Config extends \Prefab { const CACHE_KEY_SOCKET_VALID = 'CACHED_SOCKET_VALID'; const CACHE_TTL_SOCKET_VALID = 60; + /** + * SSO downtime length (estimation), minutes + */ + const DOWNTIME_LENGTH = 8; + + /** + * SSO downtime buffer length extends downtime length, minutes + */ + const DOWNTIME_BUFFER = 1; + const ERROR_CONF_PATHFINDER = 'Config value missing in pathfinder.ini file [%s]'; const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. -> Check installed Composer packages'; @@ -411,10 +421,9 @@ class Config extends \Prefab { * -> can be used for prevent logging errors during downTime * @param \DateTime|null $dateCheck * @return bool - * @throws Exception\DateException - * @throws \Exception */ static function inDownTimeRange(\DateTime $dateCheck = null) : bool { + $inRange = false; // default daily downtime 00:00am $downTimeParts = [0, 0]; if( !empty($downTime = (string)self::getEnvironmentData('CCP_SSO_DOWNTIME')) ){ @@ -425,19 +434,28 @@ class Config extends \Prefab { } } - // downTime Range is 10m - $downtimeInterval = new \DateInterval('PT10M'); - $timezone = \Base::instance()->get('getTimeZone')(); + try{ + // downTime Range is 10m + $downtimeLength = self::DOWNTIME_LENGTH + (2 * self::DOWNTIME_BUFFER); + $timezone = \Base::instance()->get('getTimeZone')(); - // if set -> use current time - $dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck; - $dateDowntimeStart = new \DateTime('now', $timezone); - $dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]); - $dateDowntimeEnd = clone $dateDowntimeStart; - $dateDowntimeEnd->add($downtimeInterval); + // if not set -> use current time + $dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck; + $dateDowntimeStart = new \DateTime('now', $timezone); + $dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]); + $dateDowntimeStart->sub(new \DateInterval('PT' . self::DOWNTIME_BUFFER . 'M')); - $dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd); - return $dateRange->inRange($dateCheck); + $dateDowntimeEnd = clone $dateDowntimeStart; + $dateDowntimeEnd->add(new \DateInterval('PT' . $downtimeLength . 'M')); + + $dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd); + $inRange = $dateRange->inRange($dateCheck); + }catch(\Exception $e){ + $f3 = \Base::instance(); + $f3->error(500, $e->getMessage(), $e->getTrace()); + } + + return $inRange; } } \ No newline at end of file diff --git a/app/main/lib/web.php b/app/main/lib/web.php index 2a665c1d..0ccd6aea 100644 --- a/app/main/lib/web.php +++ b/app/main/lib/web.php @@ -15,10 +15,11 @@ class Web extends \Web { const ERROR_STATUS_LOG = 'HTTP %s: \'%s\' | url: %s \'%s\'%s'; /** - * max number of curls calls for a single resource until giving up... - * this is because SSO API is not very stable + * max retry attempts (cURL requests) for a single resource until giving up... + * -> this is because SSO API is not very stable + * 2 == (initial request + 2 retry == max of 3) */ - const RETRY_COUNT_MAX = 3; + const RETRY_COUNT_MAX = 2; /** * end of line @@ -31,9 +32,8 @@ class Web extends \Web { * @param array $headers * @return int */ - protected function getStatusCodeFromHeaders($headers = []){ + protected function getStatusCodeFromHeaders(array $headers = []) : int { $statusCode = 0; - if( preg_match( '/HTTP\/1\.\d (\d{3}?)/', @@ -51,9 +51,8 @@ class Web extends \Web { * @param array $headers * @return int */ - protected function getCacheTimeFromHeaders($headers = []){ + protected function getCacheTimeFromHeaders(array $headers = []) : int { $cacheTime = 0; - if( preg_match( '/Cache-Control:(.*?)max-age=([0-9]+)/', @@ -80,10 +79,10 @@ class Web extends \Web { * @param null $options * @return string */ - protected function getCacheKey($url, $options = null){ + protected function getCacheKey(string $url, $options = null) : string { $f3 = \Base::instance(); - $headers = isset($options['header']) ? implode($this->eol, (array) $options['header']) : ''; + $headers = isset($options['header']) ? implode($this->eol, (array)$options['header']) : ''; return $f3->hash( $options['method'] . ' ' @@ -100,13 +99,15 @@ class Web extends \Web { * @param array $additionalOptions * @param int $retryCount request counter for failed call * @return array|FALSE|mixed - * @throws \Exception\DateException */ - public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0){ + public function request($url, array $options = null, array $additionalOptions = [], int $retryCount = 0){ $f3 = \Base::instance(); - if( !$f3->exists( $hash = $this->getCacheKey($url, $options) ) ){ - // retry same request until request limit is reached + if( !$f3->exists($hash = $this->getCacheKey($url, $options), $result) ){ + // set max retry count in case of request error + $retryCountMax = isset($additionalOptions['retryCountMax']) ? (int)$additionalOptions['retryCountMax'] : self::RETRY_COUNT_MAX; + + // retry same request until retry limit is reached $retry = false; $result = parent::request($url, $options); @@ -148,7 +149,7 @@ class Web extends \Web { case 505: $retry = true; - if( $retryCount == self::RETRY_COUNT_MAX ){ + if($retryCount == $retryCountMax){ $errorMsg = $this->getErrorMessageFromJsonResponse( $statusCode, $options['method'], @@ -171,7 +172,7 @@ class Web extends \Web { case 0: $retry = true; - if( $retryCount == self::RETRY_COUNT_MAX ){ + if($retryCount == $retryCountMax){ // timeout -> response should not be cached $result['timeout'] = true; @@ -208,14 +209,11 @@ class Web extends \Web { if( $retry && - $retryCount < self::RETRY_COUNT_MAX + $retryCount < $retryCountMax ){ $retryCount++; $this->request($url, $options, $additionalOptions, $retryCount); } - - }else{ - $result = $f3->get($hash); } return $result; diff --git a/app/main/model/charactermodel.php b/app/main/model/charactermodel.php index 6b85e3e6..888875e2 100644 --- a/app/main/model/charactermodel.php +++ b/app/main/model/charactermodel.php @@ -23,7 +23,9 @@ class CharacterModel extends BasicModel { /** * cache key prefix for getData(); result WITH log data */ - const DATA_CACHE_KEY_LOG = 'LOG'; + const DATA_CACHE_KEY_LOG = 'LOG'; + + const LOG_ACCESS = 'charId: [%20s], status: %s, charName: %s'; /** * character authorization status @@ -75,15 +77,15 @@ class CharacterModel extends BasicModel { 'nullable' => false, 'default' => '' ], - 'crestAccessToken' => [ + 'esiAccessToken' => [ 'type' => Schema::DT_VARCHAR256 ], - 'crestAccessTokenUpdated' => [ + 'esiAccessTokenExpires' => [ 'type' => Schema::DT_TIMESTAMP, 'default' => Schema::DF_CURRENT_TIMESTAMP, 'index' => true ], - 'crestRefreshToken' => [ + 'esiRefreshToken' => [ 'type' => Schema::DT_VARCHAR256 ], 'esiScopes' => [ @@ -193,10 +195,6 @@ class CharacterModel extends BasicModel { $characterData->logLocation = $this->logLocation; $characterData->selectLocation = $this->selectLocation; - if($this->authStatus){ - $characterData->authStatus = $this->authStatus; - } - if($addCharacterLogData){ if($logModel = $this->getLog()){ $characterData->log = $logModel->getData(); @@ -219,6 +217,11 @@ class CharacterModel extends BasicModel { $this->updateCacheData($characterData, $cacheKeyModifier); } + // temp "authStatus" should not be cached + if($this->authStatus){ + $characterData->authStatus = $this->authStatus; + } + return $characterData; } @@ -261,20 +264,6 @@ class CharacterModel extends BasicModel { return $ownerHash; } - /** - * set API accessToken for current session - * -> update "tokenUpdated" column on change - * -> this is required for expire checking! - * @param string $accessToken - * @return string - */ - public function set_crestAccessToken($accessToken){ - if($this->crestAccessToken !== $accessToken){ - $this->touch('crestAccessTokenUpdated'); - } - return $accessToken; - } - /** * setter for "kicked" until time * @param $minutes @@ -484,47 +473,61 @@ class CharacterModel extends BasicModel { */ public function getAccessToken(){ $accessToken = false; + $refreshToken = true; + + $timezone = self::getF3()->get('getTimeZone')(); + $now = new \DateTime('now', $timezone); - // check if there is already an "accessToken" for this user - // check expire timer for stored "accessToken" if( - !empty($this->crestAccessToken) && - !empty($this->crestAccessTokenUpdated) + !empty($this->esiAccessToken) && + !empty($this->esiAccessTokenExpires) ){ - $timezone = self::getF3()->get('getTimeZone')(); - $tokenTime = \DateTime::createFromFormat( + $expireTime = \DateTime::createFromFormat( 'Y-m-d H:i:s', - $this->crestAccessTokenUpdated, + $this->esiAccessTokenExpires, $timezone ); - // add expire time buffer for this "accessToken" - // token should be marked as "deprecated" BEFORE it actually expires. - $timeBuffer = 2 * 60; - $tokenTime->add(new \DateInterval('PT' . (Sso::ACCESS_KEY_EXPIRE_TIME - $timeBuffer) . 'S')); - $now = new \DateTime('now', $timezone); - if($tokenTime->getTimestamp() > $now->getTimestamp()){ - $accessToken = $this->crestAccessToken; + // check if token is not expired + if($expireTime->getTimestamp() > $now->getTimestamp()){ + // token still valid + $accessToken = $this->esiAccessToken; + + // check if token should be renewed (close to expire) + $timeBuffer = 2 * 60; + $expireTime->sub(new \DateInterval('PT' . $timeBuffer . 'S')); + + if($expireTime->getTimestamp() > $now->getTimestamp()){ + // token NOT close to expire + $refreshToken = false; + } } } - // if no "accessToken" was found -> get a fresh one by an existing "refreshToken" + // no valid "accessToken" found OR + // existing token is close to expire + // -> get a fresh one by an existing "refreshToken" + // -> in case request for new token fails (e.g. timeout) and old token is still valid -> keep old token if( - !$accessToken && - !empty($this->crestRefreshToken) + $refreshToken && + !empty($this->esiRefreshToken) ){ - // no accessToken found OR token is deprecated - $ssoController = new Sso(); - $accessData = $ssoController->refreshAccessToken($this->crestRefreshToken); + $additionalOptions = []; + if($accessToken){ + // ... close to expire token exists -> moderate failover settings + $additionalOptions['suppressHTTPErrors'] = true; + $additionalOptions['retryCountMax'] = 0; + } - if( - isset($accessData->accessToken) && - isset($accessData->refreshToken) - ){ - $this->crestAccessToken = $accessData->accessToken; + $ssoController = new Sso(); + $accessData = $ssoController->refreshAccessToken($this->esiRefreshToken, $additionalOptions); + + if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){ + $this->esiAccessToken = $accessData->accessToken; + $this->esiAccessTokenExpires = $accessData->esiAccessTokenExpires; $this->save(); - $accessToken = $this->crestAccessToken; + $accessToken = $this->esiAccessToken; } } @@ -535,18 +538,53 @@ class CharacterModel extends BasicModel { * check if character is currently kicked * @return bool */ - public function isKicked(){ + public function isKicked() : bool { $kicked = false; if( !is_null($this->kicked) ){ - $kickedUntil = new \DateTime(); - $kickedUntil->setTimestamp( (int)strtotime($this->kicked) ); - $now = new \DateTime(); - $kicked = ($kickedUntil > $now); + try{ + $kickedUntil = new \DateTime(); + $kickedUntil->setTimestamp( (int)strtotime($this->kicked) ); + $now = new \DateTime(); + $kicked = ($kickedUntil > $now); + }catch(\Exception $e){ + self::getF3()->error(500, $e->getMessage(), $e->getTrace()); + } } return $kicked; } + /** + * checks whether this character is currently logged in + * @return bool + */ + public function checkLoginTimer() : bool { + $loginCheck = false; + + if( !$this->dry() && $this->lastLogin ){ + // get max login time (minutes) from config + $maxLoginMinutes = (int)Config::getPathfinderData('timer.logged'); + if($maxLoginMinutes){ + $timezone = self::getF3()->get('getTimeZone')(); + try{ + $now = new \DateTime('now', $timezone); + $logoutTime = new \DateTime($this->lastLogin, $timezone); + $logoutTime->add(new \DateInterval('PT' . $maxLoginMinutes . 'M')); + if($logoutTime->getTimestamp() > $now->getTimestamp()){ + $loginCheck = true; + } + }catch(\Exception $e){ + self::getF3()->error(500, $e->getMessage(), $e->getTrace()); + } + }else{ + // no "max login" timer configured -> character still logged in + $loginCheck = true; + } + } + + return $loginCheck; + } + /** * checks whether this character is authorized to log in * -> check corp/ally whitelist config (pathfinder.ini) @@ -1111,7 +1149,7 @@ class CharacterModel extends BasicModel { } // delete auth cookie data ------------------------------------------------------------------------------------ - if($deleteCookie ){ + if($deleteCookie){ $this->deleteAuthentications(); } } diff --git a/app/pathfinder.ini b/app/pathfinder.ini index 7a2bf640..75058276 100644 --- a/app/pathfinder.ini +++ b/app/pathfinder.ini @@ -189,13 +189,15 @@ EXPIRE_SIGNATURES = 259200 ERROR = error ; SSO error log SSO = sso -; login information -LOGIN = login +; login info +CHARACTER_LOGIN = character_login +; character access info +CHARACTER_ACCESS = character_access ; session warnings (suspect) SESSION_SUSPECT = session_suspect ; account deleted DELETE_ACCOUNT = account_delete -; admin action (e.g. kick, bann) log +; admin action (e.g. kick, ban) log ADMIN = admin ; TCP socket errors SOCKET_ERROR = socket_error diff --git a/public/templates/ui/info_panel.html b/public/templates/ui/info_panel.html index 4d580e16..8389d198 100644 --- a/public/templates/ui/info_panel.html +++ b/public/templates/ui/info_panel.html @@ -1,4 +1,4 @@
-

Scheduled maintenance: Update v1.4.2 v1.4.3; Shutdown: ~19:00 (UTC). ETA ~19:20 (UTC)

+

Scheduled maintenance: Database upgrade MariaDB v10.1 v10.3; Shutdown: ~14:00 (UTC). ETA ~14:45 (UTC)