Router, Logger, Bump v1.5

This commit is contained in:
2022-05-31 16:14:49 +02:00
parent 5bb0d1419f
commit 658157167e
33 changed files with 968 additions and 267 deletions

View File

@@ -101,7 +101,7 @@ namespace Api\News {
$this->result["newsId"] = $sql->getLastInsertId();
}
return true;
return $this->success;
}
}

View File

@@ -2,13 +2,14 @@
namespace Api;
use Api\Parameter\Parameter;
use Driver\Logger\Logger;
use Objects\User;
use PhpMqtt\Client\MqttClient;
abstract class Request {
protected User $user;
protected Logger $logger;
protected array $params;
protected string $lastError;
protected array $result;
@@ -26,6 +27,7 @@ abstract class Request {
public function __construct(User $user, bool $externalCall = false, array $params = array()) {
$this->user = $user;
$this->logger = new Logger($this->getAPIName(), $this->user->getSQL());
$this->defaultParams = $params;
$this->success = false;
@@ -41,6 +43,19 @@ abstract class Request {
$this->csrfTokenRequired = true;
}
public function getAPIName(): string {
if (get_class($this) === Request::class) {
return "API";
}
$reflection = new \ReflectionClass($this);
if ($reflection->getParentClass()->isAbstract() && $reflection->getParentClass()->isSubclassOf(Request::class)) {
return $reflection->getParentClass()->getShortName() . "/" . $reflection->getShortName();
} else {
return $reflection->getShortName();
}
}
protected function forbidMethod($method) {
if (($key = array_search($method, $this->allowedMethods)) !== false) {
unset($this->allowedMethods[$key]);
@@ -225,6 +240,7 @@ abstract class Request {
return false;
}
$this->success = true;
$success = $this->_execute();
if ($this->success !== $success) {
// _execute returns a different value then it set for $this->success

View File

@@ -2,24 +2,20 @@
namespace Api {
use Api\Routes\GenerateCache;
use Driver\SQL\Condition\Compare;
use Objects\User;
abstract class RoutesAPI extends Request {
const ACTIONS = array("redirect_temporary", "redirect_permanently", "static", "dynamic");
const ROUTER_CACHE_CLASS = "\\Cache\\RouterCache";
protected function formatRegex(string $input, bool $append) : string {
$start = startsWith($input, "^");
$end = endsWith($input, "$");
if ($append) {
if (!$start) $input = "^$input";
if (!$end) $input = "$input$";
} else {
if ($start) $input = substr($input, 1);
if ($end) $input = substr($input, 0, strlen($input)-1);
}
protected string $routerCachePath;
return $input;
public function __construct(User $user, bool $externalCall, array $params) {
parent::__construct($user, $externalCall, $params);
$this->routerCachePath = getClassPath(self::ROUTER_CACHE_CLASS);
}
protected function routeExists($uid): bool {
@@ -52,6 +48,14 @@ namespace Api {
->execute();
$this->lastError = $sql->getLastError();
$this->success = $this->success && $this->regenerateCache();
return $this->success;
}
protected function regenerateCache(): bool {
$req = new GenerateCache($this->user);
$this->success = $req->execute();
$this->lastError = $req->getLastError();
return $this->success;
}
}
@@ -62,94 +66,45 @@ namespace Api\Routes {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\RoutesAPI;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\CondRegex;
use Objects\Router;
use Objects\User;
class Fetch extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
public function _execute(): bool {
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra", "active")
->from("Route")
->orderBy("uid")
->ascending()
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
$routes = array();
foreach($res as $row) {
$routes[] = array(
"uid" => intval($row["uid"]),
"request" => $this->formatRegex($row["request"], false),
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"] ?? "",
"active" => intval($sql->parseBool($row["active"])),
);
}
$this->result["routes"] = $routes;
}
return $this->success;
}
}
class Find extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'request' => new StringType('request', 128, true, '/')
));
$this->isPublic = false;
parent::__construct($user, $externalCall, array());
}
public function _execute(): bool {
$request = $this->getParam('request');
if (!startsWith($request, '/')) {
$request = "/$request";
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra")
->select("uid", "request", "action", "target", "extra", "active", "exact")
->from("Route")
->where(new CondBool("active"))
->where(new CondRegex($request, new Column("request")))
->orderBy("uid")->ascending()
->limit(1)
->orderBy("uid")
->ascending()
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
if (!empty($res)) {
$row = $res[0];
$this->result["route"] = array(
"uid" => intval($row["uid"]),
$routes = array();
foreach ($res as $row) {
$routes[] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"]
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"] ?? "",
"active" => intval($sql->parseBool($row["active"])),
"exact" => intval($sql->parseBool($row["exact"])),
);
} else {
$this->result["route"] = NULL;
}
$this->result["routes"] = $routes;
}
return $this->success;
@@ -162,7 +117,7 @@ namespace Api\Routes {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'routes' => new Parameter('routes',Parameter::TYPE_ARRAY, false)
'routes' => new Parameter('routes', Parameter::TYPE_ARRAY, false)
));
}
@@ -179,15 +134,16 @@ namespace Api\Routes {
// INSERT new routes
if ($this->success) {
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active"));
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active", "exact"));
foreach($this->routes as $route) {
$stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"]);
foreach ($this->routes as $route) {
$stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"], $route["exact"]);
}
$this->success = ($stmt->execute() !== FALSE);
$this->lastError = $sql->getLastError();
}
$this->success = $this->success && $this->regenerateCache();
return $this->success;
}
@@ -195,25 +151,37 @@ namespace Api\Routes {
$this->routes = array();
$keys = array(
"request" => Parameter::TYPE_STRING,
"request" => [Parameter::TYPE_STRING, Parameter::TYPE_INT],
"action" => Parameter::TYPE_STRING,
"target" => Parameter::TYPE_STRING,
"extra" => Parameter::TYPE_STRING,
"active" => Parameter::TYPE_BOOLEAN
"extra" => Parameter::TYPE_STRING,
"active" => Parameter::TYPE_BOOLEAN,
"exact" => Parameter::TYPE_BOOLEAN,
);
foreach($this->getParam("routes") as $index => $route) {
foreach($keys as $key => $expectedType) {
foreach ($this->getParam("routes") as $index => $route) {
foreach ($keys as $key => $expectedType) {
if (!array_key_exists($key, $route)) {
return $this->createError("Route $index missing key: $key");
}
$value = $route[$key];
$type = Parameter::parseType($value);
if ($type !== $expectedType) {
$expectedTypeName = Parameter::names[$expectedType];
if (!is_array($expectedType)) {
$expectedType = [$expectedType];
}
if (!in_array($type, $expectedType)) {
if (count($expectedType) > 0) {
$expectedTypeName = "expected: " . Parameter::names[$expectedType];
} else {
$expectedTypeName = "expected one of: " . implode(",", array_map(
function ($type) {
return Parameter::names[$type];
}, $expectedType));
}
$gotTypeName = Parameter::names[$type];
return $this->createError("Route $index has invalid value for key: $key, expected: $expectedTypeName, got: $gotTypeName");
return $this->createError("Route $index has invalid value for key: $key, $expectedTypeName, got: $gotTypeName");
}
}
@@ -222,16 +190,14 @@ namespace Api\Routes {
return $this->createError("Invalid action: $action");
}
if(empty($route["request"])) {
if (empty($route["request"])) {
return $this->createError("Request cannot be empty.");
}
if(empty($route["target"])) {
if (empty($route["target"])) {
return $this->createError("Target cannot be empty.");
}
// add start- and end pattern for database queries
$route["request"] = $this->formatRegex($route["request"], true);
$this->routes[] = $route;
}
@@ -246,14 +212,14 @@ namespace Api\Routes {
"request" => new StringType("request", 128),
"action" => new StringType("action"),
"target" => new StringType("target", 128),
"extra" => new StringType("extra", 64, true, ""),
"extra" => new StringType("extra", 64, true, ""),
));
$this->isPublic = false;
}
public function _execute(): bool {
$request = $this->formatRegex($this->getParam("request"), true);
$request = $this->getParam("request");
$action = $this->getParam("action");
$target = $this->getParam("target");
$extra = $this->getParam("extra");
@@ -268,6 +234,7 @@ namespace Api\Routes {
->execute();
$this->lastError = $sql->getLastError();
$this->success = $this->success && $this->regenerateCache();
return $this->success;
}
}
@@ -279,7 +246,7 @@ namespace Api\Routes {
"request" => new StringType("request", 128),
"action" => new StringType("action"),
"target" => new StringType("target", 128),
"extra" => new StringType("extra", 64, true, ""),
"extra" => new StringType("extra", 64, true, ""),
));
$this->isPublic = false;
}
@@ -291,7 +258,7 @@ namespace Api\Routes {
return false;
}
$request = $this->formatRegex($this->getParam("request"), true);
$request = $this->getParam("request");
$action = $this->getParam("action");
$target = $this->getParam("target");
$extra = $this->getParam("extra");
@@ -309,6 +276,7 @@ namespace Api\Routes {
->execute();
$this->lastError = $sql->getLastError();
$this->success = $this->success && $this->regenerateCache();
return $this->success;
}
}
@@ -334,6 +302,7 @@ namespace Api\Routes {
->execute();
$this->lastError = $sql->getLastError();
$this->success = $this->success && $this->regenerateCache();
return $this->success;
}
}
@@ -347,7 +316,6 @@ namespace Api\Routes {
}
public function _execute(): bool {
$uid = $this->getParam("uid");
return $this->toggleRoute($uid, true);
}
@@ -362,10 +330,71 @@ namespace Api\Routes {
}
public function _execute(): bool {
$uid = $this->getParam("uid");
return $this->toggleRoute($uid, false);
}
}
class GenerateCache extends RoutesAPI {
private ?Router $router;
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
$this->isPublic = false;
$this->router = null;
}
protected function _execute(): bool {
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra", "exact")
->from("Route")
->where(new CondBool("active"))
->orderBy("uid")->ascending()
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
if (!$this->success) {
return false;
}
$this->router = new Router($this->user);
foreach ($res as $row) {
$request = $row["request"];
$target = $row["target"];
$exact = $sql->parseBool($row["exact"]);
switch ($row["action"]) {
case "redirect_temporary":
$this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 307));
break;
case "redirect_permanently":
$this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 308));
break;
case "static":
$this->router->addRoute(new Router\StaticFileRoute($request, $exact, $target));
break;
case "dynamic":
$extra = json_decode($row["extra"]) ?? [];
$this->router->addRoute(new Router\DocumentRoute($request, $exact, $target, ...$extra));
break;
default:
break;
}
}
$this->success = $this->router->writeCache($this->routerCachePath);
if (!$this->success) {
return $this->createError("Error saving router cache file: " . $this->routerCachePath);
}
return $this->success;
}
public function getRouter(): ?Router {
return $this->router;
}
}
}

View File

@@ -46,7 +46,7 @@ namespace Api\Template {
}
$templateDir = WEBROOT . "/core/Templates/";
$templateCache = WEBROOT . "/core/TemplateCache/";
$templateCache = WEBROOT . "/core/Cache/Templates/";
$path = realpath($templateDir . $templateFile);
if (!startsWith($path, realpath($templateDir))) {
return $this->createError("Template file not in template directory");

View File

@@ -525,11 +525,13 @@ namespace Api\User {
}
if (!$this->success) {
$this->logger->error("Could not deliver email to=$email type=invite reason=" . $this->lastError);
$this->lastError = "The invitation was created but the confirmation email could not be sent. " .
"Please contact the server administration. Reason: " . $this->lastError;
"Please contact the server administration. This issue has been automatically logged. Reason: " . $this->lastError;
}
}
$this->logger->info("Created new user with uid=$id");
return $this->success;
}
}
@@ -844,17 +846,20 @@ namespace Api\User {
$this->success = $request->execute(array(
"to" => $email,
"subject" => "[$siteName] E-Mail Confirmation",
"body" => $messageBody
"body" => $messageBody,
"async" => true,
));
$this->lastError = $request->getLastError();
}
}
if (!$this->success) {
$this->logger->error("Could not deliver email to=$email type=register reason=" . $this->lastError);
$this->lastError = "Your account was registered but the confirmation email could not be sent. " .
"Please contact the server administration. Reason: " . $this->lastError;
"Please contact the server administration. This issue has been automatically logged. Reason: " . $this->lastError;
}
$this->logger->info("Registered new user with uid=" . $this->userId);
return $this->success;
}
}
@@ -1124,6 +1129,7 @@ namespace Api\User {
"gpgFingerprint" => $gpgFingerprint
));
$this->lastError = $request->getLastError();
$this->logger->info("Requested password reset for user uid=" . $user["uid"] . " by ip_address=" . $_SERVER["REMOTE_ADDR"]);
}
}
@@ -1304,6 +1310,7 @@ namespace Api\User {
} else if (!$this->updateUser($result["user"]["uid"], $password)) {
return false;
} else {
$this->logger->info("Issued password reset for user uid=" . $result["user"]["uid"]);
$this->invalidateToken($token);
return true;
}
@@ -1475,11 +1482,12 @@ namespace Api\User {
$settings = $this->user->getConfiguration()->getSettings();
$baseUrl = htmlspecialchars($settings->getBaseUrl());
$token = htmlspecialchars(urlencode($token));
$url = "$baseUrl/settings?confirmGPG&token=$token";
$mailBody = "Hello $name,<br><br>" .
"you imported a GPG public key for end-to-end encrypted mail communication. " .
"To confirm the key and verify, you own the corresponding private key, please click on the following link. " .
"The link is active for one hour.<br><br>" .
"<a href='$baseUrl/confirmGPG?token=$token'>$baseUrl/settings?confirmGPG&token=$token</a><br>
"<a href='$url'>$url</a><br>
Best Regards<br>
ilum:e Security Lab";