- fixed some "potential" login issues, #718
- improved error logging in case of failed login attempts - improved ESI "access token" handling
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)[];
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="{{id}}" class="alert alert-warning">
|
||||
<div class="ui-pnotify-icon"><span class="fas fa-exclamation fa-fw fa-lg"></span></div>
|
||||
<h4 class="ui-pnotify-title">Scheduled maintenance: Update v1.4.2 <i class="fas fa-long-arrow-alt-right"></i> v1.4.3; Shutdown: ~19:00 (UTC). ETA ~19:20 (UTC)</h4>
|
||||
<h4 class="ui-pnotify-title">Scheduled maintenance: Database upgrade <em>MariaDB</em> v10.1 <i class="fas fa-long-arrow-alt-right"></i> v10.3; Shutdown: ~14:00 (UTC). ETA ~14:45 (UTC)</h4>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user