173 lines
6.3 KiB
PHP
173 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace Franzz\Livetrail;
|
|
|
|
use Franzz\Objects\PhpObject;
|
|
use Franzz\Objects\ToolBox;
|
|
|
|
//TODO Keep only local specificities and move bulk to Franzz\Objects\Controller
|
|
class Controller extends PhpObject {
|
|
private const MUTATING_ACTIONS = [
|
|
'add_post',
|
|
'subscribe',
|
|
'unsubscribe',
|
|
'login',
|
|
'logout',
|
|
'update_project',
|
|
'upload',
|
|
'add_comment',
|
|
'add_position',
|
|
'admin_set',
|
|
'admin_create',
|
|
'admin_delete'
|
|
];
|
|
private const SESSION_WRITING_ACTIONS = [
|
|
'login',
|
|
'logout'
|
|
];
|
|
|
|
private Livetrail $oLivetrail;
|
|
private array $asReq;
|
|
private string $sCsrfToken = '';
|
|
|
|
public function __construct() {
|
|
parent::__construct(__CLASS__);
|
|
}
|
|
|
|
private function setReqVal(string $sKey, $oValue, string $sValidation=''): void {
|
|
$this->asReq[$sKey] = $this->validateValue($sValidation, $oValue);
|
|
}
|
|
|
|
public function handle($sProcessPage, array $argv = []): string {
|
|
//Start buffering so warnings/notices can be collected
|
|
ob_start();
|
|
|
|
//Parse variables
|
|
$asReq = ToolBox::getRequest($argv);
|
|
$this->asReq = [];
|
|
$sAction = $asReq['a'] ?? '';
|
|
$this->setReqVal('t', $asReq['t'] ?? '');
|
|
$this->setReqVal('name', $asReq['name'] ?? '');
|
|
$this->setReqVal('content', $asReq['content'] ?? '');
|
|
$this->setReqVal('ref_type', $asReq['ref_type'] ?? '');
|
|
$this->setReqVal('ref_id', $asReq['ref_id'] ?? 0, 'positiveInt');
|
|
$this->setReqVal('id_project', $asReq['id_project'] ?? 0, 'positiveInt');
|
|
$this->setReqVal('id', $asReq['id'] ?? 0);
|
|
$this->setReqVal('id_entity', $asReq['id'] ?? 0, 'positiveInt');
|
|
$this->setReqVal('field', $asReq['field'] ?? '');
|
|
$this->setReqVal('value', $asReq['value'] ?? '');
|
|
$this->setReqVal('type', $asReq['type'] ?? '');
|
|
$this->setReqVal('email', $asReq['email'] ?? '');
|
|
$this->setReqVal('password', $asReq['password'] ?? '');
|
|
$this->setReqVal('latitude', $asReq['latitude'] ?? '');
|
|
$this->setReqVal('longitude', $asReq['longitude'] ?? '');
|
|
$this->setReqVal('timestamp', $asReq['timestamp'] ?? 0, 'positiveInt');
|
|
$this->setReqVal('csrf_token', $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['csrf_token'] ?? ''));
|
|
|
|
//Authentication and CSRF protection share the same server-side session.
|
|
$this->initCsrfToken();
|
|
|
|
//Create Livetrail instance
|
|
$this->oLivetrail = new Livetrail($sProcessPage, $this->asReq['t']);
|
|
|
|
//Validate CSRF, then release the session lock before long-running work.
|
|
$bValidMutationRequest = $this->validateMutationRequest($sAction);
|
|
if(!$bValidMutationRequest || !in_array($sAction, self::SESSION_WRITING_ACTIONS, true)) $this->closeSession();
|
|
|
|
if(!$bValidMutationRequest) $sResult = Livetrail::getJsonResult(false, Livetrail::UNAUTHORIZED);
|
|
else {
|
|
$this->oLivetrail->setProjectId($this->asReq['id_project']);
|
|
$sResult = ($sAction == '')?$this->oLivetrail->getAppMainPage($this->getCsrfToken()):$this->dispatch($sAction);
|
|
}
|
|
|
|
//Clean errors
|
|
$sDebug = ob_get_clean();
|
|
if($sDebug != '') $this->oLivetrail->addUncaughtError($sDebug);
|
|
$this->closeSession();
|
|
|
|
return $sResult;
|
|
}
|
|
|
|
private function validateMutationRequest(string $sAction): bool {
|
|
return
|
|
PHP_SAPI === 'cli'
|
|
||
|
|
!in_array($sAction, self::MUTATING_ACTIONS, true)
|
|
||
|
|
($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && $this->checkCsrfToken($this->asReq['csrf_token'])
|
|
;
|
|
}
|
|
|
|
private function getCsrfToken(): string {
|
|
if($this->sCsrfToken === '') $this->initCsrfToken();
|
|
return $this->sCsrfToken;
|
|
}
|
|
|
|
private function setCsrfToken(): void {
|
|
if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
$this->sCsrfToken = $_SESSION['csrf_token'];
|
|
}
|
|
|
|
private function initCsrfToken(): void {
|
|
if(PHP_SAPI === 'cli') return;
|
|
|
|
if(session_status() !== PHP_SESSION_ACTIVE) {
|
|
$bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
|
|
session_set_cookie_params(['httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax']);
|
|
session_start();
|
|
}
|
|
|
|
$this->setCsrfToken();
|
|
}
|
|
|
|
private function checkCsrfToken(string $sClientToken): bool {
|
|
$sServerToken = $this->getCsrfToken();
|
|
return PHP_SAPI === 'cli' || ($sServerToken !== '' && is_string($sClientToken) && hash_equals($sServerToken, $sClientToken));
|
|
}
|
|
|
|
private function closeSession(): void {
|
|
if(session_status() === PHP_SESSION_ACTIVE) session_write_close();
|
|
}
|
|
|
|
private function dispatch(string $sAction): string {
|
|
return match($sAction) {
|
|
'markers' => $this->oLivetrail->getMarkers(),
|
|
'last_update' => $this->oLivetrail->getLastUpdate(),
|
|
'next_feed' => $this->oLivetrail->getNextFeed($this->asReq['id']),
|
|
'new_feed' => $this->oLivetrail->getNewFeed($this->asReq['id']),
|
|
'add_post' => $this->oLivetrail->addPost($this->asReq['name'], $this->asReq['content'], $this->asReq['ref_type'], $this->asReq['ref_id']),
|
|
'subscribe' => $this->oLivetrail->subscribe(),
|
|
'unsubscribe' => $this->oLivetrail->unsubscribe(),
|
|
'login' => $this->oLivetrail->login($this->asReq['email'], $this->asReq['password'], $this->asReq['name']),
|
|
'logout' => $this->oLivetrail->logout(),
|
|
'update_project' => $this->oLivetrail->updateProject(),
|
|
default => $this->dispatchAdmin($sAction)
|
|
};
|
|
}
|
|
|
|
private function dispatchAdmin(string $sAction): string {
|
|
if(!$this->oLivetrail->checkUserClearance(User::CLEARANCE_ADMIN)) {
|
|
return Livetrail::getJsonResult(false, Livetrail::NOT_FOUND);
|
|
}
|
|
|
|
return match($sAction) {
|
|
'upload' => $this->oLivetrail->upload(),
|
|
'add_comment' => $this->oLivetrail->addComment($this->asReq['id_entity'], $this->asReq['content']),
|
|
'add_position' => $this->oLivetrail->addPosition($this->asReq['latitude'], $this->asReq['longitude'], $this->asReq['timestamp']),
|
|
'admin_get' => $this->oLivetrail->getAdminSettings(),
|
|
'admin_set' => $this->oLivetrail->setAdminSettings($this->asReq['type'], $this->asReq['id_entity'], $this->asReq['field'], $this->asReq['value']),
|
|
'admin_create' => $this->oLivetrail->createAdminSettings($this->asReq['type']),
|
|
'admin_delete' => $this->oLivetrail->deleteAdminSettings($this->asReq['type'], $this->asReq['id_entity']),
|
|
'sql' => $this->oLivetrail->getDbBuildScript(),
|
|
default => Livetrail::getJsonResult(false, Livetrail::NOT_FOUND)
|
|
};
|
|
}
|
|
|
|
private static function validateValue(string $sValidation, $oValue=0) {
|
|
return match($sValidation) {
|
|
'' => $oValue,
|
|
'positiveInt' => filter_var($oValue, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 0]])
|
|
};
|
|
}
|
|
}
|