docker: gd extension + 2FA Bugfix

This commit is contained in:
Roman Hergenreder 2022-11-27 15:58:44 +01:00
parent 26a22f5299
commit c9a7da688f
13 changed files with 241 additions and 182 deletions

@ -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)
->execute();
$this->success = ($res !== false);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] === 0) {
return $this->createError("Route not found");
}
}
return $this->success;
}
protected function toggleRoute($uid, $active): bool {
if (!$this->routeExists($uid)) {
return false; return false;
} else if ($route === null) {
return $this->createError("Route not found");
} }
$sql = $this->context->getSQL(); $route->setActive($active);
$this->success = $sql->update("Route") $this->success = $route->save($sql);
->set("active", $active)
->whereEq("id", $uid)
->execute();
$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)) {
return $this->createError("Route $index missing key: $key"); if ($key !== "id") { // id is optional
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,10 +50,13 @@ 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 ($path && is_file($path)) { if ($templateDir) {
$valid = true; $path = realpath(implode("/", [$templateDir, $templateFile]));
break; if ($path && is_file($path)) {
$valid = true;
break;
}
} }
} }
@ -61,7 +64,7 @@ namespace Core\API\Template {
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,9 +33,23 @@ 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 && \