Commit 723427bf authored by Marc Egger's avatar Marc Egger
Browse files

Config.php refactor to read config before store initialized

parent c97c1b9c
Pipeline #3851 failed with stages
in 2 minutes and 12 seconds
...@@ -13,6 +13,7 @@ require_once(__DIR__ . '/../../vendor/autoload.php'); ...@@ -13,6 +13,7 @@ require_once(__DIR__ . '/../../vendor/autoload.php');
use IMATHUZH\Qfq\Core\Helper\Path; use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Report\Html2Pdf; use IMATHUZH\Qfq\Core\Report\Html2Pdf;
use IMATHUZH\Qfq\Core\Store\Config;
/** /**
...@@ -20,7 +21,8 @@ use IMATHUZH\Qfq\Core\Report\Html2Pdf; ...@@ -20,7 +21,8 @@ use IMATHUZH\Qfq\Core\Report\Html2Pdf;
*/ */
try { try {
Path::setMainPaths(Path::API_TO_APP); Path::setMainPaths(Path::API_TO_APP);
$html2pdf = new Html2Pdf(); Config::readConfig('');
$html2pdf = new Html2Pdf(Config::getConfigArray());
$html2pdf->outputHtml2Pdf(); $html2pdf->outputHtml2Pdf();
......
...@@ -496,8 +496,6 @@ const SYSTEM_DB_NAME_DATA = 'dbNameData'; ...@@ -496,8 +496,6 @@ const SYSTEM_DB_NAME_DATA = 'dbNameData';
const SYSTEM_DB_NAME_QFQ = 'dbNameQfq'; const SYSTEM_DB_NAME_QFQ = 'dbNameQfq';
const SYSTEM_DB_NAME_T3 = 'dbNameT3'; const SYSTEM_DB_NAME_T3 = 'dbNameT3';
const SYSTEM_QFQ_LOG_PATH = 'logPath'; // absolute or relative to app
const SYSTEM_QFQ_LOG_PATHFILENAME = 'qfqLog'; // absolute or relative to app const SYSTEM_QFQ_LOG_PATHFILENAME = 'qfqLog'; // absolute or relative to app
const SYSTEM_MAIL_LOG_PATHFILENAME = 'mailLog'; // absolute or relative to app const SYSTEM_MAIL_LOG_PATHFILENAME = 'mailLog'; // absolute or relative to app
......
...@@ -104,6 +104,10 @@ class Path ...@@ -104,6 +104,10 @@ class Path
const EXT_TO_SEND_EMAIL_FILE = '/Classes/External/sendEmail'; const EXT_TO_SEND_EMAIL_FILE = '/Classes/External/sendEmail';
/** /**
* Manually set the paths which are not constant nor can be inferred by other paths.
* This function must be called at the beginning of every entry point, to tell the Path class, where things are
* relative to the CWD.
*
* @param string $cwdToApp * @param string $cwdToApp
* @throws \CodeException * @throws \CodeException
* @throws \UserFormException * @throws \UserFormException
...@@ -121,7 +125,7 @@ class Path ...@@ -121,7 +125,7 @@ class Path
*/ */
public static function absoluteApp(/* path parts to append */): string public static function absoluteApp(/* path parts to append */): string
{ {
return realpath(self::cwdToApp(func_get_args())); return self::realpath(self::cwdToApp(func_get_args()));
} }
/** /**
...@@ -130,7 +134,7 @@ class Path ...@@ -130,7 +134,7 @@ class Path
*/ */
public static function absoluteExt(/* path parts to append */): string public static function absoluteExt(/* path parts to append */): string
{ {
return realpath(self::cwdToExt(func_get_args())); return self::realpath(self::cwdToExt(func_get_args()));
} }
/** /**
...@@ -139,7 +143,7 @@ class Path ...@@ -139,7 +143,7 @@ class Path
*/ */
public static function absoluteLog(/* path parts to append */): string public static function absoluteLog(/* path parts to append */): string
{ {
return realpath(self::cwdToLog(func_get_args())); return self::realpath(self::cwdToLog(func_get_args()));
} }
/** /**
...@@ -274,6 +278,48 @@ class Path ...@@ -274,6 +278,48 @@ class Path
self::$overloadAbsoluteMailLogFile = $newPath; self::$overloadAbsoluteMailLogFile = $newPath;
} }
/**
* Override the paths of sql.log, qfq.log, mail.log using the values from the config file or Typo3.
*
* @throws \UserFormException
*/
public static function overrideLogPathsFromConfig()
{
// QFQ log
$absoluteQfqLogFile = Config::get(SYSTEM_QFQ_LOG_PATHFILENAME);
if (!empty($absoluteQfqLogFile)) {
self::setAbsoluteQfqLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteQfqLogFile));
}
// Mail log
$absoluteMailLogFile = Config::get(SYSTEM_MAIL_LOG_PATHFILENAME);
if (!empty($absoluteMailLogFile)) {
self::setAbsoluteMailLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteMailLogFile));
}
// SQL log
$absoluteSqlLogFile = Config::get(SYSTEM_SQL_LOG_PATHFILENAME);
if (!empty($absoluteSqlLogFile)) {
self::setAbsoluteSqlLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteSqlLogFile));
}
}
/**
* Return the second path if it is absolute. Otherwise concatenate and return the two paths.
*
* @param string $path1
* @param string $path2
* @return string
* @throws \UserFormException
*/
public static function joinIfNotAbsolute (string $path1, string $path2): string
{
if ($path2 !== '' && $path2[0] === '/') {
return $path2;
}
return self::join($path1, $path2);
}
/** /**
* Join the arguments together as a path. * Join the arguments together as a path.
* The second argument may be an array of parts. (further arguments are ignored in that case) * The second argument may be an array of parts. (further arguments are ignored in that case)
...@@ -343,6 +389,11 @@ class Path ...@@ -343,6 +389,11 @@ class Path
} }
/** /**
* Searches these places for log directory:
* 1) project-directory/log
* 2) fileadmin/protected/log
* If not found create log dir in: project-directory/log
*
* @throws \UserFormException * @throws \UserFormException
*/ */
private static function findAndSetLogPath() private static function findAndSetLogPath()
...@@ -416,6 +467,8 @@ EOF; ...@@ -416,6 +467,8 @@ EOF;
} }
/** /**
* Throw an exception if the given path is not set (i.e. === null).
*
* @param $path * @param $path
* @throws \UserFormException * @throws \UserFormException
*/ */
...@@ -426,12 +479,31 @@ EOF; ...@@ -426,12 +479,31 @@ EOF;
} }
/** /**
* Throw exception if path does not start with '/'
*
* @param $path * @param $path
* @throws \UserFormException * @throws \UserFormException
*/ */
private static function enforcePathIsAbsolute($path) { private static function enforcePathIsAbsolute(string $path) {
if($path[0] !== '/') { if($path !== '' && $path[0] === '/') {
Thrower::userFormException('Path is not absolute', "Path does not start with '/' : $path"); return;
}
Thrower::userFormException('Path is not absolute', "Path does not start with '/' : $path");
}
/**
* PHP System function: realpath() with QFQ exception
*
* @param string $path
* @return string
* @throws \UserFormException
*/
private static function realpath(string $path): string
{
$result = realpath($path);
if ($result === false) {
Thrower::userFormException('Path not found.', "Either file/dir not exists or access not permitted: $path.");
} }
return $result;
} }
} }
\ No newline at end of file
...@@ -26,6 +26,7 @@ use IMATHUZH\Qfq\Core\Helper\Support; ...@@ -26,6 +26,7 @@ use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Report\Monitor; use IMATHUZH\Qfq\Core\Report\Monitor;
use IMATHUZH\Qfq\Core\Report\Report; use IMATHUZH\Qfq\Core\Report\Report;
use IMATHUZH\Qfq\Core\Report\ReportAsFile; use IMATHUZH\Qfq\Core\Report\ReportAsFile;
use IMATHUZH\Qfq\Core\Store\Config;
use IMATHUZH\Qfq\Core\Store\FillStoreForm; use IMATHUZH\Qfq\Core\Store\FillStoreForm;
use IMATHUZH\Qfq\Core\Store\Session; use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Sip;
...@@ -121,6 +122,8 @@ class QuickFormQuery { ...@@ -121,6 +122,8 @@ class QuickFormQuery {
*/ */
public function __construct(array $t3data = array(), $phpUnit = false, $inlineReport = true) { public function __construct(array $t3data = array(), $phpUnit = false, $inlineReport = true) {
Config::readConfig();
#TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}" #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
$this->phpUnit = $phpUnit; $this->phpUnit = $phpUnit;
$this->inlineReport = $inlineReport; $this->inlineReport = $inlineReport;
......
...@@ -69,9 +69,6 @@ class Html2Pdf { ...@@ -69,9 +69,6 @@ class Html2Pdf {
public function __construct(array $config = array(), $phpUnit = false) { public function __construct(array $config = array(), $phpUnit = false) {
#TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}" #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
if (count($config) == 0) {
$config = Config::readConfig('');
}
$this->config = $config; $this->config = $config;
......
...@@ -193,7 +193,7 @@ class Report { ...@@ -193,7 +193,7 @@ class Report {
$sqlLog = $this->store->getVar(TYPO3_SQL_LOG_ABSOLUTE, STORE_TYPO3); $sqlLog = $this->store->getVar(TYPO3_SQL_LOG_ABSOLUTE, STORE_TYPO3);
if (false !== $sqlLog) { if (false !== $sqlLog) {
$sqlLogAbsolute = HelperFile::joinPathFilename(realpath(Path::cwdToApp()), $sqlLog); $sqlLogAbsolute = HelperFile::joinPathFilename(Path::absoluteApp(), $sqlLog);
Path::setAbsoluteSqlLogFile($sqlLogAbsolute); Path::setAbsoluteSqlLogFile($sqlLogAbsolute);
} }
......
...@@ -21,6 +21,8 @@ use IMATHUZH\Qfq\Core\Helper\OnString; ...@@ -21,6 +21,8 @@ use IMATHUZH\Qfq\Core\Helper\OnString;
*/ */
class Config { class Config {
private static $config = null;
const CONFIG_REQUIRED_TEMPLATE = [ const CONFIG_REQUIRED_TEMPLATE = [
SYSTEM_DB_1_USER => null, SYSTEM_DB_1_USER => null,
SYSTEM_DB_1_PASSWORD => null, SYSTEM_DB_1_PASSWORD => null,
...@@ -28,6 +30,86 @@ class Config { ...@@ -28,6 +30,86 @@ class Config {
SYSTEM_DB_1_NAME=> null, SYSTEM_DB_1_NAME=> null,
]; ];
/**
* Get config value with given key. Throws exception if config has not been read.
*
* @param string $key
* @return mixed
* @throws \UserFormException
*/
public static function get(string $key)
{
if (self::$config === null) {
Thrower::userFormException("Config error.", "Config was accessed before it was read. Execute Config::readConfig() first.");
}
return self::$config[$key];
}
/**
* Returns a copy of the config array. Throws exception if config has not been read.
*
* @return array
* @throws \UserFormException
*/
public static function getConfigArray(): array
{
if (self::$config === null) {
Thrower::userFormException("Config error.", "Config was accessed before it was read. Execute Config::readConfig() first.");
}
return self::$config;
}
/**
* Read qfq.json (merge with Typo3-qfq config if exists)
* Deprecated config file typo3conf/config.qfq.php is translated to JSON in PATH:cwdToProject(..)
*
* @param string $PhpUnitOverloadCwdToConfigFile
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function readConfig($PhpUnitOverloadCwdToConfigFile = '') {
if (self::$config !== null && $PhpUnitOverloadCwdToConfigFile === '') {
return;
}
// read and parse config. Throw exception if not exists.
$cwdToConfigFile = $PhpUnitOverloadCwdToConfigFile === '' ? Path::cwdToProject(CONFIG_QFQ_JSON) : $PhpUnitOverloadCwdToConfigFile;
if (!file_exists($cwdToConfigFile)) {
HelperFile::file_put_contents(Path::cwdToProject(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT));
Thrower::userFormException("Please create qfq config file '" . CONFIG_QFQ_JSON . "' in project directory. Example config file '" . CONFIG_QFQ_JSON_EXAMPLE . "' was created in project directory.", "Project directory: " . realpath(Path::cwdToProject()));
}
$config = HelperFile::json_decode(HelperFile::file_get_contents($cwdToConfigFile));
// check required keys
foreach (self::CONFIG_REQUIRED_TEMPLATE as $key => $value) {
if (!array_key_exists($key, $config) || is_null($config[$key]) || $config[$key] === '') {
Thrower::userFormException("Required key '$key' missing in config file " . CONFIG_QFQ_JSON, "Config file: $cwdToConfigFile");
}
}
if ($PhpUnitOverloadCwdToConfigFile === '') {
// Settings in qfq.json overwrite T3 settings
$configT3qfq = self::readTypo3QfqConfig();
$config = array_merge($configT3qfq, $config);
}
$config = self::renameConfigElements($config);
$config = self::setDefaults($config);
self::checkDeprecated($config);
self::checkForAttack($config);
// Copy values to detect custom settings later
$config[F_FE_DATA_PATTERN_ERROR_SYSTEM] = $config[F_FE_DATA_PATTERN_ERROR];
self::$config = $config;
// Set log paths
Path::overrideLogPathsFromConfig();
}
/** /**
* Iterates over all 30 custom vars, explode them to split between key and value, append to $config. * Iterates over all 30 custom vars, explode them to split between key and value, append to $config.
* *
...@@ -58,7 +140,7 @@ class Config { ...@@ -58,7 +140,7 @@ class Config {
} }
/** /**
* Overwrite the config file with data from given array. * Overwrite the qfq config file with data from given array.
* *
* @param array $config * @param array $config
* @throws \UserFormException * @throws \UserFormException
...@@ -91,51 +173,6 @@ class Config { ...@@ -91,51 +173,6 @@ class Config {
HelperFile::unlink($cwdToOldConfigFile); HelperFile::unlink($cwdToOldConfigFile);
} }
/**
* Read qfq.json (merge with Typo3-qfq config if exists)
* Deprecated config file typo3conf/config.qfq.php is translated to JSON in PATH:cwdToProject(..)
*
* @param string $PhpUnitOverloadCwdToConfigFile
* @return array
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function readConfig($PhpUnitOverloadCwdToConfigFile = '') {
// read and parse config
$cwdToConfigFile = $PhpUnitOverloadCwdToConfigFile === '' ? Path::cwdToProject(CONFIG_QFQ_JSON) : $PhpUnitOverloadCwdToConfigFile;
if (!file_exists($cwdToConfigFile)) {
HelperFile::file_put_contents(Path::cwdToProject(CONFIG_QFQ_JSON_EXAMPLE), json_encode(self::CONFIG_REQUIRED_TEMPLATE, JSON_PRETTY_PRINT));
Thrower::userFormException("Please create qfq config file '" . CONFIG_QFQ_JSON . "' in project directory. Example config file '" . CONFIG_QFQ_JSON_EXAMPLE . "' was created in project directory.", "Project directory: " . realpath(Path::cwdToProject()));
}
$config = HelperFile::json_decode(HelperFile::file_get_contents($cwdToConfigFile));
// check required keys
foreach (self::CONFIG_REQUIRED_TEMPLATE as $key => $value) {
if (!array_key_exists($key, $config) || is_null($config[$key]) || $config[$key] === '') {
Thrower::userFormException("Required key '$key' missing in config file " . CONFIG_QFQ_JSON, "Config file: $cwdToConfigFile");
}
}
if ($PhpUnitOverloadCwdToConfigFile === '') {
// Settings in qfq.json overwrite T3 settings
$configT3qfq = self::readTypo3QfqConfig();
$config = array_merge($configT3qfq, $config);
}
$config = self::renameConfigElements($config);
$config = self::setDefaults($config);
self::checkDeprecated($config);
self::checkForAttack($config);
// Copy values to detect custom settings later
$config[F_FE_DATA_PATTERN_ERROR_SYSTEM] = $config[F_FE_DATA_PATTERN_ERROR];
return $config;
}
/** /**
* Read Typo3-QFQ config first from global variable, then from typo3conf/Localconfig.php. * Read Typo3-QFQ config first from global variable, then from typo3conf/Localconfig.php.
* If both not exist, return empty array. * If both not exist, return empty array.
...@@ -271,7 +308,8 @@ class Config { ...@@ -271,7 +308,8 @@ class Config {
public static function attackDetectedExitNow(array $config = array(), $reason = '') { public static function attackDetectedExitNow(array $config = array(), $reason = '') {
if (count($config) == 0) { if (count($config) == 0) {
$config = self::readConfig(); self::readConfig();
$config = self::getConfigArray();
} }
Logger::logMessage(Logger::linePre() . 'Security: attack detected' . PHP_EOL . $reason, Path::absoluteQfqLogFile()); Logger::logMessage(Logger::linePre() . 'Security: attack detected' . PHP_EOL . $reason, Path::absoluteQfqLogFile());
...@@ -322,7 +360,6 @@ class Config { ...@@ -322,7 +360,6 @@ class Config {
SYSTEM_RENDER => SYSTEM_RENDER_SINGLE, SYSTEM_RENDER => SYSTEM_RENDER_SINGLE,
SYSTEM_DATE_FORMAT => 'yyyy-mm-dd', SYSTEM_DATE_FORMAT => 'yyyy-mm-dd',
SYSTEM_SHOW_DEBUG_INFO => SYSTEM_SHOW_DEBUG_INFO_AUTO, SYSTEM_SHOW_DEBUG_INFO => SYSTEM_SHOW_DEBUG_INFO_AUTO,
SYSTEM_QFQ_LOG_PATH => '',
SYSTEM_MAIL_LOG_PATHFILENAME => '', SYSTEM_MAIL_LOG_PATHFILENAME => '',
SYSTEM_QFQ_LOG_PATHFILENAME => '', SYSTEM_QFQ_LOG_PATHFILENAME => '',
SYSTEM_SQL_LOG_PATHFILENAME => '', SYSTEM_SQL_LOG_PATHFILENAME => '',
...@@ -375,7 +412,7 @@ class Config { ...@@ -375,7 +412,7 @@ class Config {
SYSTEM_DB_UPDATE => SYSTEM_DB_UPDATE_AUTO, SYSTEM_DB_UPDATE => SYSTEM_DB_UPDATE_AUTO,
SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS => SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS_DEFAULT, SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS => SYSTEM_RECORD_LOCK_TIMEOUT_SECONDS_DEFAULT,
SYSTEM_SESSION_TIMEOUT_SECONDS => self::getPhpSessionTimeout(), SYSTEM_SESSION_TIMEOUT_SECONDS => Session::getPhpSessionTimeout(),
SYSTEM_DOCUMENTATION_QFQ => SYSTEM_DOCUMENTATION_QFQ_URL, SYSTEM_DOCUMENTATION_QFQ => SYSTEM_DOCUMENTATION_QFQ_URL,
SYSTEM_ENTER_AS_SUBMIT => 1, SYSTEM_ENTER_AS_SUBMIT => 1,
...@@ -464,45 +501,4 @@ class Config { ...@@ -464,45 +501,4 @@ class Config {
return $config; return $config;
} }
/**
* Check Session Timeout
*
* @param $timeout
* @throws \UserFormException
*/
public static function checkSessionTimeout($timeout) {
if (self::getPhpSessionTimeout() < $timeout) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the PHP config 'session.gc_maxlifetime' ("
. ini_get('session.gc_maxlifetime') . ") and/or 'session.cookie_lifetime' ("
. ini_get('session.cookie_lifetime') . ")");
}
if ($timeout > SYSTEM_COOKIE_LIFETIME) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the hardcoded cookie lifetime (" . SYSTEM_COOKIE_LIFETIME . ")");
}
}
/**
* Get the minimum of 'session.cookie_lifetime' and 'session.gc_maxlifetime'
* A zero means unlimited and will be limited to one day.
*
* @return int|string
*/
private static function getPhpSessionTimeout() {
$timeout = ini_get('session.cookie_lifetime');
$gc_maxlifetime = ini_get('session.gc_maxlifetime');
if ($timeout == 0) {
$timeout = 86400;
}
if ($gc_maxlifetime == 0) {
// Just to set a limit
$timeout = 86400;
}
return $timeout > $gc_maxlifetime ? $gc_maxlifetime : $timeout;
}
} }
\ No newline at end of file
...@@ -314,7 +314,7 @@ class Session ...@@ -314,7 +314,7 @@ class Session
public static function checkSessionExpired($timeout) { public static function checkSessionExpired($timeout) {
// Just to be sure that the given $timeout is supported by the current php.ini setup // Just to be sure that the given $timeout is supported by the current php.ini setup
config::checkSessionTimeout($timeout); self::checkSessionTimeout($timeout);
if (self::$lastActivity === false || $timeout === false || $timeout == 0) { if (self::$lastActivity === false || $timeout === false || $timeout == 0) {
return; return;
...@@ -327,6 +327,47 @@ class Session ...@@ -327,6 +327,47 @@ class Session
} }
} }
/**
* Check Session Timeout
*
* @param $timeout
* @throws \UserFormException
*/
public static function checkSessionTimeout($timeout) {
if (self::getPhpSessionTimeout() < $timeout) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the PHP config 'session.gc_maxlifetime' ("
. ini_get('session.gc_maxlifetime') . ") and/or 'session.cookie_lifetime' ("
. ini_get('session.cookie_lifetime') . ")");
}
if ($timeout > SYSTEM_COOKIE_LIFETIME) {
throw new \UserFormException ("The specified timeout of $timeout seconds is higher than the hardcoded cookie lifetime (" . SYSTEM_COOKIE_LIFETIME . ")");
}
}
/**
* Get the minimum of 'session.cookie_lifetime' and 'session.gc_maxlifetime'
* A zero means unlimited and will be limited to one day.
*
* @return int|string
*/
public static function getPhpSessionTimeout() {
$timeout = ini_get('session.cookie_lifetime');
$gc_maxlifetime = ini_get('session.gc_maxlifetime');
if ($timeout == 0) {
$timeout = 86400;
}
if ($gc_maxlifetime == 0) {
// Just to set a limit
$timeout = 86400;
}
return $timeout > $gc_maxlifetime ? $gc_maxlifetime : $timeout;
}
/** /**
* Returns $flagFeUserChanged. In case it's true, set it to false. * Returns $flagFeUserChanged. In case it's true, set it to false.
* *
......
...@@ -303,7 +303,7 @@ class Store { ...@@ -303,7 +303,7 @@ class Store {
private static function checkMandatoryParameter(array $config) { private static function checkMandatoryParameter(array $config) {