docker: gd extension + 2FA Bugfix
This commit is contained in:
parent
26a22f5299
commit
c9a7da688f
@ -3,12 +3,11 @@
|
|||||||
namespace Core\API {
|
namespace Core\API {
|
||||||
|
|
||||||
use Core\API\Routes\GenerateCache;
|
use Core\API\Routes\GenerateCache;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Route;
|
||||||
|
|
||||||
abstract class RoutesAPI extends Request {
|
abstract class RoutesAPI extends Request {
|
||||||
|
|
||||||
const ACTIONS = array("redirect_temporary", "redirect_permanently", "static", "dynamic");
|
|
||||||
const ROUTER_CACHE_CLASS = "\\Core\\Cache\\RouterCache";
|
const ROUTER_CACHE_CLASS = "\\Core\\Cache\\RouterCache";
|
||||||
|
|
||||||
protected string $routerCachePath;
|
protected string $routerCachePath;
|
||||||
@ -18,38 +17,19 @@ namespace Core\API {
|
|||||||
$this->routerCachePath = getClassPath(self::ROUTER_CACHE_CLASS);
|
$this->routerCachePath = getClassPath(self::ROUTER_CACHE_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function routeExists($uid): bool {
|
protected function toggleRoute(int $id, bool $active): bool {
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$res = $sql->select($sql->count())
|
$route = Route::find($sql, $id);
|
||||||
->from("Route")
|
if ($route === false) {
|
||||||
->whereEq("id", $uid)
|
return false;
|
||||||
->execute();
|
} else if ($route === null) {
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if ($this->success) {
|
|
||||||
if ($res[0]["count"] === 0) {
|
|
||||||
return $this->createError("Route not found");
|
return $this->createError("Route not found");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function toggleRoute($uid, $active): bool {
|
|
||||||
if (!$this->routeExists($uid)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$this->success = $sql->update("Route")
|
|
||||||
->set("active", $active)
|
|
||||||
->whereEq("id", $uid)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
|
$route->setActive($active);
|
||||||
|
$this->success = $route->save($sql);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = $this->success && $this->regenerateCache();
|
return $this->success && $this->regenerateCache();
|
||||||
return $this->success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function regenerateCache(): bool {
|
protected function regenerateCache(): bool {
|
||||||
@ -58,6 +38,27 @@ namespace Core\API {
|
|||||||
$this->lastError = $req->getLastError();
|
$this->lastError = $req->getLastError();
|
||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createRoute(string $type, string $pattern, string $target,
|
||||||
|
?string $extra, bool $exact, bool $active = true): ?Route {
|
||||||
|
|
||||||
|
$routeClass = Route::ROUTE_TYPES[$type] ?? null;
|
||||||
|
if (!$routeClass) {
|
||||||
|
$this->createError("Invalid type: $type");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$routeClass = new \ReflectionClass($routeClass);
|
||||||
|
$routeObj = $routeClass->newInstance($pattern, $exact, $target);
|
||||||
|
$routeObj->setExtra($extra);
|
||||||
|
$routeObj->setActive($active);
|
||||||
|
return $routeObj;
|
||||||
|
} catch (\ReflectionException $exception) {
|
||||||
|
$this->createError("Error instantiating route class: " . $exception->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ namespace Core\API\Routes {
|
|||||||
use Core\API\RoutesAPI;
|
use Core\API\RoutesAPI;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
use Core\Driver\SQL\Condition\CondBool;
|
use Core\Driver\SQL\Condition\CondBool;
|
||||||
|
use Core\Driver\SQL\Query\StartTransaction;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\Route;
|
use Core\Objects\DatabaseEntity\Route;
|
||||||
use Core\Objects\Router\DocumentRoute;
|
use Core\Objects\Router\DocumentRoute;
|
||||||
@ -86,31 +88,15 @@ namespace Core\API\Routes {
|
|||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
|
|
||||||
$res = $sql
|
$routes = Route::findAll($sql);
|
||||||
->select("id", "request", "action", "target", "extra", "active", "exact")
|
|
||||||
->from("Route")
|
|
||||||
->orderBy("id")
|
|
||||||
->ascending()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = ($res !== FALSE);
|
$this->success = ($routes !== FALSE);
|
||||||
|
|
||||||
if ($this->success) {
|
if ($this->success) {
|
||||||
$routes = array();
|
$this->result["routes"] = [];
|
||||||
foreach ($res as $row) {
|
foreach ($routes as $route) {
|
||||||
$routes[] = array(
|
$this->result["routes"][$route->getId()] = $route->jsonSerialize();
|
||||||
"id" => intval($row["id"]),
|
|
||||||
"request" => $row["request"],
|
|
||||||
"action" => $row["action"],
|
|
||||||
"target" => $row["target"],
|
|
||||||
"extra" => $row["extra"] ?? "",
|
|
||||||
"active" => intval($sql->parseBool($row["active"])),
|
|
||||||
"exact" => intval($sql->parseBool($row["exact"])),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result["routes"] = $routes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
@ -133,32 +119,35 @@ namespace Core\API\Routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
|
$sql->startTransaction();
|
||||||
|
|
||||||
// DELETE old rules
|
// DELETE old rules;
|
||||||
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
|
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
// INSERT new routes
|
// INSERT new routes
|
||||||
if ($this->success) {
|
if ($this->success) {
|
||||||
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active", "exact"));
|
$insertStatement = Route::getHandler($sql)->getInsertQuery($this->routes);
|
||||||
|
$this->success = ($insertStatement->execute() !== FALSE);
|
||||||
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->lastError = $sql->getLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success = $this->success && $this->regenerateCache();
|
if ($this->success) {
|
||||||
return $this->success;
|
$sql->commit();
|
||||||
|
return $this->regenerateCache();
|
||||||
|
} else {
|
||||||
|
$sql->rollback();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function validateRoutes(): bool {
|
private function validateRoutes(): bool {
|
||||||
|
|
||||||
$this->routes = array();
|
$this->routes = array();
|
||||||
$keys = array(
|
$keys = array(
|
||||||
"request" => [Parameter::TYPE_STRING, Parameter::TYPE_INT],
|
"id" => Parameter::TYPE_INT,
|
||||||
"action" => Parameter::TYPE_STRING,
|
"pattern" => [Parameter::TYPE_STRING, Parameter::TYPE_INT],
|
||||||
|
"type" => Parameter::TYPE_STRING,
|
||||||
"target" => Parameter::TYPE_STRING,
|
"target" => Parameter::TYPE_STRING,
|
||||||
"extra" => Parameter::TYPE_STRING,
|
"extra" => Parameter::TYPE_STRING,
|
||||||
"active" => Parameter::TYPE_BOOLEAN,
|
"active" => Parameter::TYPE_BOOLEAN,
|
||||||
@ -168,7 +157,11 @@ namespace Core\API\Routes {
|
|||||||
foreach ($this->getParam("routes") as $index => $route) {
|
foreach ($this->getParam("routes") as $index => $route) {
|
||||||
foreach ($keys as $key => $expectedType) {
|
foreach ($keys as $key => $expectedType) {
|
||||||
if (!array_key_exists($key, $route)) {
|
if (!array_key_exists($key, $route)) {
|
||||||
|
if ($key !== "id") { // id is optional
|
||||||
return $this->createError("Route $index missing key: $key");
|
return $this->createError("Route $index missing key: $key");
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $route[$key];
|
$value = $route[$key];
|
||||||
@ -191,13 +184,13 @@ namespace Core\API\Routes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = $route["action"];
|
$type = $route["type"];
|
||||||
if (!in_array($action, self::ACTIONS)) {
|
if (!isset(Route::ROUTE_TYPES[$type])) {
|
||||||
return $this->createError("Invalid action: $action");
|
return $this->createError("Invalid type: $type");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($route["request"])) {
|
if (empty($route["pattern"])) {
|
||||||
return $this->createError("Request cannot be empty.");
|
return $this->createError("Pattern cannot be empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($route["target"])) {
|
if (empty($route["target"])) {
|
||||||
@ -215,33 +208,33 @@ namespace Core\API\Routes {
|
|||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
"request" => new StringType("request", 128),
|
"pattern" => new StringType("pattern", 128),
|
||||||
"action" => new StringType("action"),
|
"type" => new StringType("type"),
|
||||||
"target" => new StringType("target", 128),
|
"target" => new StringType("target", 128),
|
||||||
"extra" => new StringType("extra", 64, true, ""),
|
"extra" => new StringType("extra", 64, true, ""),
|
||||||
|
"exact" => new Parameter("exact", Parameter::TYPE_BOOLEAN),
|
||||||
|
"active" => new Parameter("active", Parameter::TYPE_BOOLEAN, true, true),
|
||||||
));
|
));
|
||||||
$this->isPublic = false;
|
$this->isPublic = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
|
|
||||||
$request = $this->getParam("request");
|
$pattern = $this->getParam("pattern");
|
||||||
$action = $this->getParam("action");
|
$type = $this->getParam("type");
|
||||||
$target = $this->getParam("target");
|
$target = $this->getParam("target");
|
||||||
$extra = $this->getParam("extra");
|
$extra = $this->getParam("extra");
|
||||||
|
$exact = $this->getParam("exact");
|
||||||
if (!in_array($action, self::ACTIONS)) {
|
$active = $this->getParam("active");
|
||||||
return $this->createError("Invalid action: $action");
|
$route = $this->createRoute($type, $pattern, $target, $extra, $exact, $active);
|
||||||
|
if ($route === null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$this->success = $sql->insert("Route", ["request", "action", "target", "extra"])
|
$this->success = $route->save($sql) !== false;
|
||||||
->addRow($request, $action, $target, $extra)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = $this->success && $this->regenerateCache();
|
return $this->success && $this->regenerateCache();
|
||||||
return $this->success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,8 +242,8 @@ namespace Core\API\Routes {
|
|||||||
public function __construct(Context $context, bool $externalCall = false) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||||
"request" => new StringType("request", 128),
|
"pattern" => new StringType("pattern", 128),
|
||||||
"action" => new StringType("action"),
|
"type" => new StringType("type"),
|
||||||
"target" => new StringType("target", 128),
|
"target" => new StringType("target", 128),
|
||||||
"extra" => new StringType("extra", 64, true, ""),
|
"extra" => new StringType("extra", 64, true, ""),
|
||||||
));
|
));
|
||||||
@ -260,30 +253,36 @@ namespace Core\API\Routes {
|
|||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
|
|
||||||
$id = $this->getParam("id");
|
$id = $this->getParam("id");
|
||||||
if (!$this->routeExists($id)) {
|
$sql = $this->context->getSQL();
|
||||||
return false;
|
$route = Route::find($sql, $id);
|
||||||
|
if ($route === false) {
|
||||||
|
return $this->createError("Error fetching route: " . $sql->getLastError());
|
||||||
|
} else if ($route === null) {
|
||||||
|
return $this->createError("Route not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
$request = $this->getParam("request");
|
|
||||||
$action = $this->getParam("action");
|
|
||||||
$target = $this->getParam("target");
|
$target = $this->getParam("target");
|
||||||
$extra = $this->getParam("extra");
|
$extra = $this->getParam("extra");
|
||||||
if (!in_array($action, self::ACTIONS)) {
|
$type = $this->getParam("type");
|
||||||
return $this->createError("Invalid action: $action");
|
$pattern = $this->getParam("pattern");
|
||||||
|
$exact = $this->getParam("exact");
|
||||||
|
$active = $this->getParam("active");
|
||||||
|
if ($route->getType() !== $type) {
|
||||||
|
$route = $this->createRoute($type, $pattern, $target, $extra, $exact, $active);
|
||||||
|
if ($route === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$route->setPattern($pattern);
|
||||||
|
$route->setActive($active);
|
||||||
|
$route->setExtra($extra);
|
||||||
|
$route->setTarget($target);
|
||||||
|
$route->setExact($exact);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$this->success = $route->save($sql) !== false;
|
||||||
$this->success = $sql->update("Route")
|
|
||||||
->set("request", $request)
|
|
||||||
->set("action", $action)
|
|
||||||
->set("target", $target)
|
|
||||||
->set("extra", $extra)
|
|
||||||
->whereEq("id", $id)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = $this->success && $this->regenerateCache();
|
return $this->success && $this->regenerateCache();
|
||||||
return $this->success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,19 +296,18 @@ namespace Core\API\Routes {
|
|||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
|
|
||||||
|
$sql = $this->context->getSQL();
|
||||||
$id = $this->getParam("id");
|
$id = $this->getParam("id");
|
||||||
if (!$this->routeExists($id)) {
|
$route = Route::find($sql, $id);
|
||||||
return false;
|
if ($route === false) {
|
||||||
|
return $this->createError("Error fetching route: " . $sql->getLastError());
|
||||||
|
} else if ($route === null) {
|
||||||
|
return $this->createError("Route not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$this->success = $route->delete($sql) !== false;
|
||||||
$this->success = $sql->delete("Route")
|
|
||||||
->where("id", $id)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = $this->success && $this->regenerateCache();
|
return $this->success && $this->regenerateCache();
|
||||||
return $this->success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,8 +320,8 @@ namespace Core\API\Routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
$uid = $this->getParam("id");
|
$id = $this->getParam("id");
|
||||||
return $this->toggleRoute($uid, true);
|
return $this->toggleRoute($id, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,8 +334,8 @@ namespace Core\API\Routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
$uid = $this->getParam("id");
|
$id = $this->getParam("id");
|
||||||
return $this->toggleRoute($uid, false);
|
return $this->toggleRoute($id, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,18 +50,21 @@ namespace Core\API\Template {
|
|||||||
$valid = false;
|
$valid = false;
|
||||||
|
|
||||||
foreach ($baseDirs as $baseDir) {
|
foreach ($baseDirs as $baseDir) {
|
||||||
$path = realpath(implode("/", [WEBROOT, $baseDir, "Templates", $templateFile]));
|
$templateDir = realpath(implode("/", [WEBROOT, $baseDir, "Templates"]));
|
||||||
|
if ($templateDir) {
|
||||||
|
$path = realpath(implode("/", [$templateDir, $templateFile]));
|
||||||
if ($path && is_file($path)) {
|
if ($path && is_file($path)) {
|
||||||
$valid = true;
|
$valid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$valid) {
|
if (!$valid) {
|
||||||
return $this->createError("Template file not found or not inside template directory");
|
return $this->createError("Template file not found or not inside template directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
$twigLoader = new FilesystemLoader(dirname($path));
|
$twigLoader = new FilesystemLoader($templateDir);
|
||||||
$twigEnvironment = new Environment($twigLoader, [
|
$twigEnvironment = new Environment($twigLoader, [
|
||||||
'cache' => $templateCache,
|
'cache' => $templateCache,
|
||||||
'auto_reload' => true
|
'auto_reload' => true
|
||||||
|
@ -84,27 +84,14 @@ namespace Core\API\TFA {
|
|||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$password = $this->getParam("password");
|
$password = $this->getParam("password");
|
||||||
if ($password) {
|
if ($password) {
|
||||||
$res = $sql->select("password")
|
if (!password_verify($password, $currentUser->password)) {
|
||||||
->from("User")
|
|
||||||
->whereEq("id", $currentUser->getId())
|
|
||||||
->execute();
|
|
||||||
$this->success = !empty($res);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
} else if (!password_verify($password, $res[0]["password"])) {
|
|
||||||
return $this->createError("Wrong password");
|
return $this->createError("Wrong password");
|
||||||
}
|
}
|
||||||
} else if ($token->isConfirmed()) {
|
} else if ($token->isConfirmed()) {
|
||||||
// if the token is fully confirmed, require a password to remove it
|
// if the token is fully confirmed, require a password to remove it
|
||||||
return $this->createError("Missing parameter: password");
|
return $this->createError("Missing parameter: password");
|
||||||
}
|
}
|
||||||
|
$this->success = $token->delete($sql) !== false;
|
||||||
$res = $sql->delete("2FA")
|
|
||||||
->whereEq("id", $token->getId())
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = $res !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
if ($this->success && $token->isConfirmed()) {
|
if ($this->success && $token->isConfirmed()) {
|
||||||
@ -157,16 +144,11 @@ namespace Core\API\TFA {
|
|||||||
} else if (!($twoFactorToken instanceof TimeBasedTwoFactorToken)) {
|
} else if (!($twoFactorToken instanceof TimeBasedTwoFactorToken)) {
|
||||||
$twoFactorToken = new TimeBasedTwoFactorToken(generateRandomString(32, "base32"));
|
$twoFactorToken = new TimeBasedTwoFactorToken(generateRandomString(32, "base32"));
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$this->success = $sql->insert("2FA", ["type", "data"])
|
$this->success = $twoFactorToken->save($sql) !== false;
|
||||||
->addRow("totp", $twoFactorToken->getData())
|
|
||||||
->returning("id")
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
if ($this->success) {
|
if ($this->success) {
|
||||||
$this->success = $sql->update("User")
|
$currentUser->setTwoFactorToken($twoFactorToken);
|
||||||
->set("2fa_id", $sql->getLastInsertId())
|
$this->success = $currentUser->save($sql);
|
||||||
->whereEq("id", $currentUser->getId())
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,11 +176,12 @@ namespace Core\API\TFA {
|
|||||||
return $this->createError("Your two factor token is already confirmed.");
|
return $this->createError("Your two factor token is already confirmed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parent::_execute()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$this->success = $sql->update("2FA")
|
$this->success = $twoFactorToken->confirm($sql) !== false;
|
||||||
->set("confirmed", true)
|
|
||||||
->whereEq("id", $twoFactorToken->getId())
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
@ -271,20 +254,15 @@ namespace Core\API\TFA {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$challenge = base64_encode(generateRandomString(32, "raw"));
|
$challenge = base64_encode(generateRandomString(32, "raw"));
|
||||||
$res = $sql->insert("2FA", ["type", "data"])
|
$twoFactorToken = new KeyBasedTwoFactorToken($challenge);
|
||||||
->addRow("fido", $challenge)
|
$this->success = ($twoFactorToken->save($sql) !== false);
|
||||||
->returning("id")
|
|
||||||
->execute();
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
if (!$this->success) {
|
if (!$this->success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success = $sql->update("User")
|
$currentUser->setTwoFactorToken($twoFactorToken);
|
||||||
->set("2fa_id", $sql->getLastInsertId())
|
$this->success = $currentUser->save($sql) !== false;
|
||||||
->whereEq("id", $currentUser->getId())
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
if (!$this->success) {
|
if (!$this->success) {
|
||||||
return false;
|
return false;
|
||||||
@ -321,16 +299,7 @@ namespace Core\API\TFA {
|
|||||||
return $this->createError("Unsupported key type. Expected: -7");
|
return $this->createError("Unsupported key type. Expected: -7");
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$this->success = $twoFactorToken->confirmKeyBased($sql, base64_encode($authData->getCredentialID()), $publicKey) !== false;
|
||||||
"credentialID" => base64_encode($authData->getCredentialID()),
|
|
||||||
"publicKey" => $publicKey->jsonSerialize()
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->success = $sql->update("2FA")
|
|
||||||
->set("data", json_encode($data))
|
|
||||||
->set("confirmed", true)
|
|
||||||
->whereEq("id", $twoFactorToken->getId())
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ namespace Documents\Install {
|
|||||||
use Core\Configuration\Configuration;
|
use Core\Configuration\Configuration;
|
||||||
use Core\Configuration\CreateDatabase;
|
use Core\Configuration\CreateDatabase;
|
||||||
use Core\Driver\SQL\Query\Commit;
|
use Core\Driver\SQL\Query\Commit;
|
||||||
use Core\Driver\SQL\Query\RollBack;
|
|
||||||
use Core\Driver\SQL\Query\StartTransaction;
|
use Core\Driver\SQL\Query\StartTransaction;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Elements\Body;
|
use Core\Elements\Body;
|
||||||
@ -349,7 +348,7 @@ namespace Documents\Install {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
(new RollBack($sql))->execute();
|
$sql->rollback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
|||||||
use Core\Driver\SQL\Expression\Expression;
|
use Core\Driver\SQL\Expression\Expression;
|
||||||
use Core\Driver\SQL\Expression\Sum;
|
use Core\Driver\SQL\Expression\Sum;
|
||||||
use Core\Driver\SQL\Query\AlterTable;
|
use Core\Driver\SQL\Query\AlterTable;
|
||||||
|
use Core\Driver\SQL\Query\Commit;
|
||||||
use Core\Driver\SQL\Query\CreateProcedure;
|
use Core\Driver\SQL\Query\CreateProcedure;
|
||||||
use Core\Driver\SQL\Query\CreateTable;
|
use Core\Driver\SQL\Query\CreateTable;
|
||||||
use Core\Driver\SQL\Query\CreateTrigger;
|
use Core\Driver\SQL\Query\CreateTrigger;
|
||||||
@ -30,7 +31,9 @@ use Core\Driver\SQL\Query\Delete;
|
|||||||
use Core\Driver\SQL\Query\Drop;
|
use Core\Driver\SQL\Query\Drop;
|
||||||
use Core\Driver\SQL\Query\Insert;
|
use Core\Driver\SQL\Query\Insert;
|
||||||
use Core\Driver\SQL\Query\Query;
|
use Core\Driver\SQL\Query\Query;
|
||||||
|
use Core\Driver\SQL\Query\RollBack;
|
||||||
use Core\Driver\SQL\Query\Select;
|
use Core\Driver\SQL\Query\Select;
|
||||||
|
use Core\Driver\SQL\Query\StartTransaction;
|
||||||
use Core\Driver\SQL\Query\Truncate;
|
use Core\Driver\SQL\Query\Truncate;
|
||||||
use Core\Driver\SQL\Query\Update;
|
use Core\Driver\SQL\Query\Update;
|
||||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||||
@ -99,6 +102,18 @@ abstract class SQL {
|
|||||||
return new Drop($this, $table);
|
return new Drop($this, $table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function startTransaction(): bool {
|
||||||
|
return (new StartTransaction($this))->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit(): bool {
|
||||||
|
return (new Commit($this))->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rollback(): bool {
|
||||||
|
return (new RollBack($this))->execute();
|
||||||
|
}
|
||||||
|
|
||||||
public function alterTable($tableName): AlterTable {
|
public function alterTable($tableName): AlterTable {
|
||||||
return new AlterTable($this, $tableName);
|
return new AlterTable($this, $tableName);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity;
|
||||||
|
|
||||||
use Core\API\Parameter\Parameter;
|
use Core\API\Parameter\Parameter;
|
||||||
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum;
|
use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
@ -16,11 +17,16 @@ use Core\Objects\Router\StaticFileRoute;
|
|||||||
abstract class Route extends DatabaseEntity {
|
abstract class Route extends DatabaseEntity {
|
||||||
|
|
||||||
const PARAMETER_PATTERN = "/^{([^:]+)(:(.*?)(\?)?)?}$/";
|
const PARAMETER_PATTERN = "/^{([^:]+)(:(.*?)(\?)?)?}$/";
|
||||||
|
|
||||||
|
const TYPE_DYNAMIC = "dynamic";
|
||||||
|
const TYPE_STATIC = "static";
|
||||||
|
const TYPE_REDIRECT_PERMANENTLY = "redirect_permanently";
|
||||||
|
const TYPE_REDIRECT_TEMPORARY = "redirect_temporary";
|
||||||
const ROUTE_TYPES = [
|
const ROUTE_TYPES = [
|
||||||
"redirect_temporary" => RedirectRoute::class,
|
self::TYPE_REDIRECT_TEMPORARY => RedirectRoute::class,
|
||||||
"redirect_permanently" => RedirectRoute::class,
|
self::TYPE_REDIRECT_PERMANENTLY => RedirectRoute::class,
|
||||||
"static" => StaticFileRoute::class,
|
self::TYPE_STATIC => StaticFileRoute::class,
|
||||||
"dynamic" => DocumentRoute::class
|
self::TYPE_DYNAMIC => DocumentRoute::class
|
||||||
];
|
];
|
||||||
|
|
||||||
#[MaxLength(128)]
|
#[MaxLength(128)]
|
||||||
@ -77,6 +83,13 @@ abstract class Route extends DatabaseEntity {
|
|||||||
|
|
||||||
public abstract function call(Router $router, array $params): string;
|
public abstract function call(Router $router, array $params): string;
|
||||||
|
|
||||||
|
protected function readExtra() { }
|
||||||
|
|
||||||
|
public function postFetch(SQL $sql, array $row) {
|
||||||
|
parent::postFetch($sql, $row);
|
||||||
|
$this->readExtra();
|
||||||
|
}
|
||||||
|
|
||||||
protected function getArgs(): array {
|
protected function getArgs(): array {
|
||||||
return [$this->pattern, $this->exact];
|
return [$this->pattern, $this->exact];
|
||||||
}
|
}
|
||||||
@ -204,4 +217,28 @@ abstract class Route extends DatabaseEntity {
|
|||||||
"active" => $this->active,
|
"active" => $this->active,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setActive(bool $active) {
|
||||||
|
$this->active = $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPattern(string $pattern) {
|
||||||
|
$this->pattern = $pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExtra(string $extra) {
|
||||||
|
$this->extra = $extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTarget(string $target) {
|
||||||
|
$this->target = $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExact(bool $exact) {
|
||||||
|
$this->exact = $exact;
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
|||||||
#[ExtendingEnum(self::TWO_FACTOR_TOKEN_TYPES)] private string $type;
|
#[ExtendingEnum(self::TWO_FACTOR_TOKEN_TYPES)] private string $type;
|
||||||
private bool $confirmed;
|
private bool $confirmed;
|
||||||
private bool $authenticated;
|
private bool $authenticated;
|
||||||
#[MaxLength(512)] private string $data;
|
#[MaxLength(512)] private ?string $data;
|
||||||
|
|
||||||
public function __construct(string $type, ?int $id = null, bool $confirmed = false) {
|
public function __construct(string $type, ?int $id = null, bool $confirmed = false) {
|
||||||
parent::__construct($id);
|
parent::__construct($id);
|
||||||
@ -27,6 +27,7 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
|||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
$this->confirmed = $confirmed;
|
$this->confirmed = $confirmed;
|
||||||
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
|
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
|
||||||
|
$this->data = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
public function jsonSerialize(): array {
|
||||||
@ -63,11 +64,12 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
|||||||
return $this->confirmed;
|
return $this->confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticated(): bool {
|
public function isAuthenticated(): bool {
|
||||||
return $this->authenticated;
|
return $this->authenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function confirm(SQL $sql): bool {
|
||||||
|
$this->confirmed = true;
|
||||||
|
return $this->save($sql) !== false;
|
||||||
|
}
|
||||||
}
|
}
|
@ -93,4 +93,8 @@ class User extends DatabaseEntity {
|
|||||||
$this->lastOnline = new \DateTime();
|
$this->lastOnline = new \DateTime();
|
||||||
return $this->save($sql, ["last_online", "language_id"]);
|
return $this->save($sql, ["last_online", "language_id"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setTwoFactorToken(TwoFactorToken $twoFactorToken) {
|
||||||
|
$this->twoFactorToken = $twoFactorToken;
|
||||||
|
}
|
||||||
}
|
}
|
@ -24,11 +24,16 @@ class DocumentRoute extends Route {
|
|||||||
$this->extra = json_encode($args);
|
$this->extra = json_encode($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postFetch(SQL $sql, array $row) {
|
protected function readExtra() {
|
||||||
parent::postFetch($sql, $row);
|
parent::readExtra();
|
||||||
$this->args = json_decode($this->extra);
|
$this->args = json_decode($this->extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function preInsert(array &$row) {
|
||||||
|
parent::preInsert($row);
|
||||||
|
$this->extra = json_encode($this->args);
|
||||||
|
}
|
||||||
|
|
||||||
#[Pure] private function getClassName(): string {
|
#[Pure] private function getClassName(): string {
|
||||||
return $this->getTarget();
|
return $this->getTarget();
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,16 @@ class StaticFileRoute extends Route {
|
|||||||
$this->extra = json_encode($this->code);
|
$this->extra = json_encode($this->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postFetch(SQL $sql, array $row) {
|
protected function readExtra() {
|
||||||
parent::postFetch($sql, $row);
|
parent::readExtra();
|
||||||
$this->code = json_decode($this->extra);
|
$this->code = json_decode($this->extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function preInsert(array &$row) {
|
||||||
|
parent::preInsert($row);
|
||||||
|
$this->extra = json_encode($this->code);
|
||||||
|
}
|
||||||
|
|
||||||
public function call(Router $router, array $params): string {
|
public function call(Router $router, array $params): string {
|
||||||
http_response_code($this->code);
|
http_response_code($this->code);
|
||||||
$this->serveStatic($this->getAbsolutePath(), $router);
|
$this->serveStatic($this->getAbsolutePath(), $router);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Core\Objects\TwoFactor;
|
namespace Core\Objects\TwoFactor;
|
||||||
|
|
||||||
|
use Core\Driver\SQL\SQL;
|
||||||
use Cose\Algorithm\Signature\ECDSA\ECSignature;
|
use Cose\Algorithm\Signature\ECDSA\ECSignature;
|
||||||
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
||||||
|
|
||||||
@ -13,8 +14,13 @@ class KeyBasedTwoFactorToken extends TwoFactorToken {
|
|||||||
private ?string $credentialId;
|
private ?string $credentialId;
|
||||||
private ?PublicKey $publicKey;
|
private ?PublicKey $publicKey;
|
||||||
|
|
||||||
|
public function __construct(string $challenge) {
|
||||||
|
parent::__construct(self::TYPE);
|
||||||
|
$this->challenge = $challenge;
|
||||||
|
}
|
||||||
|
|
||||||
protected function readData(string $data) {
|
protected function readData(string $data) {
|
||||||
if ($this->isConfirmed()) {
|
if (!$this->isConfirmed()) {
|
||||||
$this->challenge = base64_decode($data);
|
$this->challenge = base64_decode($data);
|
||||||
$this->credentialId = null;
|
$this->credentialId = null;
|
||||||
$this->publicKey = null;
|
$this->publicKey = null;
|
||||||
@ -27,8 +33,22 @@ class KeyBasedTwoFactorToken extends TwoFactorToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getData(): string {
|
public function getData(): string {
|
||||||
return $this->challenge;
|
if ($this->isConfirmed()) {
|
||||||
|
return base64_encode($this->challenge);
|
||||||
|
} else {
|
||||||
|
return json_encode([
|
||||||
|
"credentialId" => $this->credentialId,
|
||||||
|
"publicKey" => $this->publicKey->jsonSerialize()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function confirmKeyBased(SQL $sql, string $credentialId, PublicKey $publicKey): bool {
|
||||||
|
$this->credentialId = $credentialId;
|
||||||
|
$this->publicKey = $publicKey;
|
||||||
|
return parent::confirm($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getPublicKey(): ?PublicKey {
|
public function getPublicKey(): ?PublicKey {
|
||||||
return $this->publicKey;
|
return $this->publicKey;
|
||||||
|
@ -5,6 +5,7 @@ namespace Core\Objects\TwoFactor;
|
|||||||
use Base32\Base32;
|
use Base32\Base32;
|
||||||
use chillerlan\QRCode\QRCode;
|
use chillerlan\QRCode\QRCode;
|
||||||
use chillerlan\QRCode\QROptions;
|
use chillerlan\QRCode\QROptions;
|
||||||
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
||||||
|
|
||||||
|
@ -7,9 +7,10 @@ RUN mkdir -p /application/core/Configuration /var/www/.gnupg && \
|
|||||||
|
|
||||||
# YAML + dev dependencies
|
# YAML + dev dependencies
|
||||||
RUN apt-get update -y && \
|
RUN apt-get update -y && \
|
||||||
apt-get install -y libyaml-dev libzip-dev libgmp-dev gnupg2 && \
|
apt-get install -y libyaml-dev libzip-dev libgmp-dev libpng-dev gnupg2d && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
pecl install yaml && docker-php-ext-enable yaml
|
pecl install yaml && docker-php-ext-enable yaml && \
|
||||||
|
docker-php-ext-install gd
|
||||||
|
|
||||||
# Runkit (no stable release available)
|
# Runkit (no stable release available)
|
||||||
RUN pecl install runkit7-4.0.0a3 && docker-php-ext-enable runkit7 && \
|
RUN pecl install runkit7-4.0.0a3 && docker-php-ext-enable runkit7 && \
|
||||||
|
Loading…
Reference in New Issue
Block a user