This commit is contained in:
Roman 2024-03-29 16:37:42 +01:00
parent 0e3d27fa10
commit 755da257f8
5 changed files with 81 additions and 64 deletions

@ -9,7 +9,6 @@ namespace Core\API {
parent::__construct($context, $externalCall, $params); parent::__construct($context, $externalCall, $params);
} }
} }
} }
namespace Core\API\Logs { namespace Core\API\Logs {
@ -34,33 +33,35 @@ namespace Core\API\Logs {
use Pagination; use Pagination;
protected array $shownLogLevels;
protected ?\DateTime $since;
public function __construct(Context $context, bool $externalCall = false) { public function __construct(Context $context, bool $externalCall = false) {
$params = self::getPaginationParameters(['id', 'timestamp', "module", "severity"], $params = self::getPaginationParameters(['id', 'timestamp', "module", "severity"],
'timestamp', 'desc'); 'timestamp', 'desc');
$params["since"] = new Parameter("since", Parameter::TYPE_DATE_TIME, true); $params["since"] = new Parameter("since", Parameter::TYPE_DATE_TIME, true);
$params["severity"] = new StringType("severity", 32, true, "debug"); $params["severity"] = new StringType("severity", 32, true, "debug", array_values(Logger::LOG_LEVELS));
$params["query"] = new StringType("query", 64, true, null); $params["query"] = new StringType("query", 64, true, null);
parent::__construct($context, $externalCall, $params); parent::__construct($context, $externalCall, $params);
$this->csrfTokenRequired = false; $this->shownLogLevels = Logger::LOG_LEVELS;
$this->since = null;
} }
protected function _execute(): bool { protected function getFilter(): CondIn|CondAnd|bool {
$since = $this->getParam("since"); $this->since = $this->getParam("since");
$sql = $this->context->getSQL();
$severity = strtolower(trim($this->getParam("severity"))); $severity = strtolower(trim($this->getParam("severity")));
$query = $this->getParam("query"); $query = $this->getParam("query");
$shownLogLevels = Logger::LOG_LEVELS;
$logLevel = array_search($severity, Logger::LOG_LEVELS, true); $logLevel = array_search($severity, Logger::LOG_LEVELS, true);
if ($logLevel === false) { if ($logLevel === false) {
return $this->createError("Invalid severity. Allowed values: " . implode(",", Logger::LOG_LEVELS)); return $this->createError("Invalid severity. Allowed values: " . implode(",", Logger::LOG_LEVELS));
} else if ($logLevel > 0) { } else if ($logLevel > 0) {
$shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel); $this->shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel);
} }
$condition = new CondIn(new Column("severity"), $shownLogLevels); $condition = new CondIn(new Column("severity"), $this->shownLogLevels);
if ($since !== null) { if ($this->since !== null) {
$condition = new CondAnd($condition, new Compare("timestamp", $since, ">=")); $condition = new CondAnd($condition, new Compare("timestamp", $this->since, ">="));
} }
if ($query) { if ($query) {
@ -70,6 +71,48 @@ namespace Core\API\Logs {
)); ));
} }
return $condition;
}
protected function loadFromFileSystem(array &$logs): void {
// get all log entries from filesystem (if database failed)
$logPath = realpath(implode(DIRECTORY_SEPARATOR, [WEBROOT, "Site", "Logs"]));
if ($logPath) {
$index = 1;
foreach (scandir($logPath) as $fileName) {
$logFile = $logPath . DIRECTORY_SEPARATOR . $fileName;
// {module}_{severity}_{date}_{time}_{ms}.log
if (preg_match("/^(\w+)_(\w+)_((\d+-\d+-\d+_){2}\d+)\.log$/", $fileName, $matches) && is_file($logFile)) {
$content = @file_get_contents($logFile);
$date = \DateTime::createFromFormat(Logger::LOG_FILE_DATE_FORMAT, $matches[3]);
if ($content && $date) {
// filter log date
if ($this->since !== null && datetimeDiff($date, $this->since) > 0) {
continue;
}
// filter log level
if (!in_array(trim(strtolower($matches[2])), $this->shownLogLevels)) {
continue;
}
$logs[] = [
"id" => "file-" . ($index++),
"module" => $matches[1],
"severity" => $matches[2],
"message" => $content,
"timestamp" => $date->format(Parameter::DATE_TIME_FORMAT)
];
}
}
}
}
}
protected function _execute(): bool {
$sql = $this->context->getSQL();
$condition = $this->getFilter();
if (!$this->initPagination($sql, SystemLog::class, $condition)) { if (!$this->initPagination($sql, SystemLog::class, $condition)) {
return false; return false;
} }
@ -97,40 +140,7 @@ namespace Core\API\Logs {
]; ];
} }
// get all log entries from filesystem (if database failed) $this->loadFromFileSystem($this->result["logs"]);
$logPath = realpath(implode(DIRECTORY_SEPARATOR, [WEBROOT, "Site", "Logs"]));
if ($logPath) {
$index = 1;
foreach (scandir($logPath) as $fileName) {
$logFile = $logPath . DIRECTORY_SEPARATOR . $fileName;
// {module}_{severity}_{date}_{time}_{ms}.log
if (preg_match("/^(\w+)_(\w+)_((\d+-\d+-\d+_){2}\d+)\.log$/", $fileName, $matches) && is_file($logFile)) {
$content = @file_get_contents($logFile);
$date = \DateTime::createFromFormat(Logger::LOG_FILE_DATE_FORMAT, $matches[3]);
if ($content && $date) {
// filter log date
if ($since !== null && datetimeDiff($date, $since) > 0) {
continue;
}
// filter log level
if (!in_array(trim(strtolower($matches[2])), $shownLogLevels)) {
continue;
}
$this->result["logs"][] = [
"id" => "file-" . ($index++),
"module" => $matches[1],
"severity" => $matches[2],
"message" => $content,
"timestamp" => $date->format(Parameter::DATE_TIME_FORMAT)
];
}
}
}
}
return true; return true;
} }
@ -138,5 +148,4 @@ namespace Core\API\Logs {
$insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to fetch system logs", true); $insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to fetch system logs", true);
} }
} }
} }

@ -114,7 +114,7 @@ namespace Core\API\Permission {
} }
$sql = $this->context->getSQL(); $sql = $this->context->getSQL();
$res = $sql->select("method", "groups", "description", "isCore") $res = $sql->select("method", "groups", "description", "is_core")
->from("ApiPermission") ->from("ApiPermission")
->execute(); ->execute();
@ -127,12 +127,12 @@ namespace Core\API\Permission {
$method = $row["method"]; $method = $row["method"];
$description = $row["description"]; $description = $row["description"];
$groups = json_decode($row["groups"]); $groups = json_decode($row["groups"]);
$isCore = $row["isCore"]; $isCore = $row["is_core"];
$permissions[] = [ $permissions[] = [
"method" => $method, "method" => $method,
"groups" => $groups, "groups" => $groups,
"description" => $description, "description" => $description,
"isCore" => $isCore "is_core" => $isCore
]; ];
} }
$this->result["permissions"] = $permissions; $this->result["permissions"] = $permissions;
@ -181,11 +181,11 @@ namespace Core\API\Permission {
} }
if ($description === null) { if ($description === null) {
$updateQuery = $sql->insert("ApiPermission", ["method", "groups", "isCore"]) $updateQuery = $sql->insert("ApiPermission", ["method", "groups", "is_core"])
->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groupIds])) ->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groupIds]))
->addRow($method, $groupIds, false); ->addRow($method, $groupIds, false);
} else { } else {
$updateQuery = $sql->insert("ApiPermission", ["method", "groups", "isCore", "description"]) $updateQuery = $sql->insert("ApiPermission", ["method", "groups", "is_core", "description"])
->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groupIds, "description" => $description])) ->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groupIds, "description" => $description]))
->addRow($method, $groupIds, false, $description); ->addRow($method, $groupIds, false, $description);
} }

@ -32,17 +32,20 @@ abstract class Request {
$this->context = $context; $this->context = $context;
$this->logger = new Logger($this->getAPIName(), $this->context->getSQL()); $this->logger = new Logger($this->getAPIName(), $this->context->getSQL());
$this->defaultParams = $params; $this->defaultParams = $params;
$this->externalCall = $externalCall;
$this->variableParamCount = false;
// result
$this->lastError = "";
$this->success = false; $this->success = false;
$this->result = array(); $this->result = array();
$this->externalCall = $externalCall;
// restrictions
$this->isPublic = true; $this->isPublic = true;
$this->isDisabled = false; $this->isDisabled = false;
$this->loginRequired = false; $this->loginRequired = false;
$this->variableParamCount = false;
$this->apiKeyAllowed = true; $this->apiKeyAllowed = true;
$this->allowedMethods = array("GET", "POST"); $this->allowedMethods = array("GET", "POST");
$this->lastError = "";
$this->csrfTokenRequired = true; $this->csrfTokenRequired = true;
} }
@ -267,14 +270,19 @@ abstract class Request {
} }
$this->success = true; $this->success = true;
try {
$success = $this->_execute(); $success = $this->_execute();
if ($this->success !== $success) { if ($this->success !== $success) {
// _execute returns a different value then it set for $this->success // _execute might return a different value then it set for $this->success
// this should actually not occur, how to handle this case? // this should actually not occur, how to handle this case?
$this->success = $success; $this->success = $success;
} }
} catch (\Error $err) {
$this->createError($err->getMessage());
$this->logger->error($err->getMessage());
}
$sql->setLastError(''); $sql->setLastError("");
return $this->success; return $this->success;
} }

@ -39,7 +39,7 @@ class CreateDatabase extends DatabaseScript {
->addJson("groups", true, '[]') ->addJson("groups", true, '[]')
->addString("description", 128, false, "") ->addString("description", 128, false, "")
->primaryKey("method") ->primaryKey("method")
->addBool("isCore", false); ->addBool("is_core", false);
self::loadDefaultACL($queries, $sql); self::loadDefaultACL($queries, $sql);
self::loadPatches($queries, $sql); self::loadPatches($queries, $sql);
@ -122,7 +122,7 @@ class CreateDatabase extends DatabaseScript {
} }
public static function loadDefaultACL(array &$queries, SQL $sql) { public static function loadDefaultACL(array &$queries, SQL $sql) {
$query = $sql->insert("ApiPermission", ["method", "groups", "description", "isCore"]); $query = $sql->insert("ApiPermission", ["method", "groups", "description", "is_core"]);
foreach (Request::getApiEndpoints() as $reflectionClass) { foreach (Request::getApiEndpoints() as $reflectionClass) {
$method = $reflectionClass->getName() . "::getDefaultACL"; $method = $reflectionClass->getName() . "::getDefaultACL";

@ -298,7 +298,7 @@ abstract class SQL {
} }
} }
public function setLastError($str) { public function setLastError($str): void {
$this->lastError = $str; $this->lastError = $str;
} }
@ -306,7 +306,7 @@ abstract class SQL {
return $this->lastInsertId; return $this->lastInsertId;
} }
public function close() { public function close(): void {
$this->disconnect(); $this->disconnect();
$this->connection = NULL; $this->connection = NULL;
} }