Namespace and ClassPath rewrites
This commit is contained in:
104
Core/Configuration/Configuration.class.php
Normal file
104
Core/Configuration/Configuration.class.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Configuration;
|
||||
|
||||
use Core\Objects\ConnectionData;
|
||||
|
||||
class Configuration {
|
||||
|
||||
const className = "\Site\Configuration\Database";
|
||||
private ?ConnectionData $database;
|
||||
private Settings $settings;
|
||||
|
||||
function __construct() {
|
||||
$this->database = null;
|
||||
$this->settings = Settings::loadDefaults();
|
||||
|
||||
$className = self::className;
|
||||
$path = getClassPath($className, ".class");
|
||||
if (file_exists($path) && is_readable($path)) {
|
||||
include_once $path;
|
||||
if (class_exists($className)) {
|
||||
$this->database = new $className();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDatabase(): ?ConnectionData {
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function getSettings(): Settings {
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
public static function create(string $className, $data) {
|
||||
$path = getClassPath($className);
|
||||
$classNameShort = explode("\\", $className);
|
||||
$classNameShort = end($classNameShort);
|
||||
|
||||
if ($data) {
|
||||
if (is_string($data)) {
|
||||
$key = var_export($data, true);
|
||||
$code = intendCode(
|
||||
"<?php
|
||||
|
||||
namespace Core\Configuration;
|
||||
|
||||
class $classNameShort extends KeyData {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct($key);
|
||||
}
|
||||
|
||||
}", false
|
||||
);
|
||||
} else if ($data instanceof ConnectionData) {
|
||||
$superClass = get_class($data);
|
||||
$host = var_export($data->getHost(), true);
|
||||
$port = var_export($data->getPort(), true);
|
||||
$login = var_export($data->getLogin(), true);
|
||||
$password = var_export($data->getPassword(), true);
|
||||
|
||||
$properties = "";
|
||||
foreach ($data->getProperties() as $key => $val) {
|
||||
$key = var_export($key, true);
|
||||
$val = var_export($val, true);
|
||||
$properties .= "\n\$this->setProperty($key, $val);";
|
||||
}
|
||||
|
||||
$code = intendCode(
|
||||
"<?php
|
||||
|
||||
namespace Site\Configuration;
|
||||
|
||||
class $classNameShort extends \\$superClass {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct($host, $port, $login, $password);$properties
|
||||
}
|
||||
}", false
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$code = "<?php";
|
||||
}
|
||||
|
||||
return @file_put_contents($path, $code);
|
||||
}
|
||||
|
||||
public function delete(string $className): bool {
|
||||
$path = getClassPath("\\Configuration\\$className");
|
||||
if (file_exists($path)) {
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setDatabase(ConnectionData $connectionData): void {
|
||||
$this->database = $connectionData;
|
||||
}
|
||||
}
|
||||
256
Core/Configuration/CreateDatabase.class.php
Normal file
256
Core/Configuration/CreateDatabase.class.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Configuration;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Driver\SQL\Strategy\SetNullStrategy;
|
||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||
use Core\Objects\DatabaseEntity\DatabaseEntity;
|
||||
use PHPUnit\Util\Exception;
|
||||
|
||||
class CreateDatabase extends DatabaseScript {
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
$queries = array();
|
||||
|
||||
self::loadEntities($queries, $sql);
|
||||
|
||||
$queries[] = $sql->insert("Language", array("code", "name"))
|
||||
->addRow("en_US", 'American English')
|
||||
->addRow("de_DE", 'Deutsch Standard');
|
||||
|
||||
$queries[] = $sql->createTable("UserToken")
|
||||
->addInt("user_id")
|
||||
->addString("token", 36)
|
||||
->addEnum("token_type", array("password_reset", "email_confirm", "invite", "gpg_confirm"))
|
||||
->addDateTime("valid_until")
|
||||
->addBool("used", false)
|
||||
->foreignKey("user_id", "User", "id", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->insert("Group", array("name", "color"))
|
||||
->addRow(USER_GROUP_MODERATOR_NAME, "#007bff")
|
||||
->addRow(USER_GROUP_SUPPORT_NAME, "#28a745")
|
||||
->addRow(USER_GROUP_ADMIN_NAME, "#dc3545");
|
||||
|
||||
$queries[] = $sql->createTable("UserGroup")
|
||||
->addInt("user_id")
|
||||
->addInt("group_id")
|
||||
->unique("user_id", "group_id")
|
||||
->foreignKey("user_id", "User", "id", new CascadeStrategy())
|
||||
->foreignKey("group_id", "Group", "id", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("UserNotification")
|
||||
->addInt("user_id")
|
||||
->addInt("notification_id")
|
||||
->addBool("seen", false)
|
||||
->foreignKey("user_id", "User", "id")
|
||||
->foreignKey("notification_id", "Notification", "id")
|
||||
->unique("user_id", "notification_id");
|
||||
|
||||
$queries[] = $sql->createTable("GroupNotification")
|
||||
->addInt("group_id")
|
||||
->addInt("notification_id")
|
||||
->addBool("seen", false)
|
||||
->foreignKey("group_id", "Group", "id")
|
||||
->foreignKey("notification_id", "Notification", "id")
|
||||
->unique("group_id", "notification_id");
|
||||
|
||||
$queries[] = $sql->createTable("Visitor")
|
||||
->addInt("day")
|
||||
->addInt("count", false, 1)
|
||||
->addString("cookie", 26)
|
||||
->unique("day", "cookie");
|
||||
|
||||
$queries[] = $sql->createTable("Route")
|
||||
->addSerial("id")
|
||||
->addString("request", 128)
|
||||
->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic"))
|
||||
->addString("target", 128)
|
||||
->addString("extra", 64, true)
|
||||
->addBool("active", true)
|
||||
->addBool("exact", true)
|
||||
->primaryKey("id")
|
||||
->unique("request");
|
||||
|
||||
$queries[] = $sql->insert("Route", ["request", "action", "target", "extra", "exact"])
|
||||
->addRow("/admin", "dynamic", "\\Core\\Documents\\Admin", NULL, false)
|
||||
->addRow("/register", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/register.twig"]), true)
|
||||
->addRow("/confirmEmail", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/confirm_email.twig"]), true)
|
||||
->addRow("/acceptInvite", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/accept_invite.twig"]), true)
|
||||
->addRow("/resetPassword", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/reset_password.twig"]), true)
|
||||
->addRow("/login", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/login.twig"]), true)
|
||||
->addRow("/resendConfirmEmail", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/resend_confirm_email.twig"]), true)
|
||||
->addRow("/debug", "dynamic", "\\Core\\Documents\\Info", NULL, true)
|
||||
->addRow("/", "static", "/static/welcome.html", NULL, true);
|
||||
|
||||
$queries[] = $sql->createTable("Settings")
|
||||
->addString("name", 32)
|
||||
->addString("value", 1024, true)
|
||||
->addBool("private", false) // these values are not returned from '/api/settings/get', but can be changed
|
||||
->addBool("readonly", false) // these values are neither returned, nor can be changed from outside
|
||||
->primaryKey("name");
|
||||
|
||||
$settingsQuery = $sql->insert("Settings", array("name", "value", "private", "readonly"))
|
||||
// ->addRow("mail_enabled", "0") # this key will be set during installation
|
||||
->addRow("mail_host", "", false, false)
|
||||
->addRow("mail_port", "", false, false)
|
||||
->addRow("mail_username", "", false, false)
|
||||
->addRow("mail_password", "", true, false)
|
||||
->addRow("mail_from", "", false, false)
|
||||
->addRow("mail_last_sync", "", false, false)
|
||||
->addRow("mail_footer", "", false, false);
|
||||
|
||||
(Settings::loadDefaults())->addRows($settingsQuery);
|
||||
$queries[] = $settingsQuery;
|
||||
|
||||
$queries[] = $sql->createTable("ContactRequest")
|
||||
->addSerial("id")
|
||||
->addString("from_name", 32)
|
||||
->addString("from_email", 64)
|
||||
->addString("message", 512)
|
||||
->addString("messageId", 78, true) # null = don't sync with mails (usually if mail could not be sent)
|
||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
||||
->unique("messageId")
|
||||
->primaryKey("id");
|
||||
|
||||
$queries[] = $sql->createTable("ContactMessage")
|
||||
->addSerial("id")
|
||||
->addInt("request_id")
|
||||
->addInt("user_id", true) # null = customer has sent this message
|
||||
->addString("message", 512)
|
||||
->addString("messageId", 78)
|
||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
||||
->addBool("read", false)
|
||||
->unique("messageId")
|
||||
->primaryKey("id")
|
||||
->foreignKey("request_id", "ContactRequest", "id", new CascadeStrategy())
|
||||
->foreignKey("user_id", "User", "id", new SetNullStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("ApiPermission")
|
||||
->addString("method", 32)
|
||||
->addJson("groups", true, '[]')
|
||||
->addString("description", 128, false, "")
|
||||
->primaryKey("method");
|
||||
|
||||
$queries[] = $sql->createTable("MailQueue")
|
||||
->addSerial("id")
|
||||
->addString("from", 64)
|
||||
->addString("to", 64)
|
||||
->addString("subject")
|
||||
->addString("body")
|
||||
->addString("replyTo", 64, true)
|
||||
->addString("replyName", 32, true)
|
||||
->addString("gpgFingerprint", 64, true)
|
||||
->addEnum("status", ["waiting", "success", "error"], false, 'waiting')
|
||||
->addInt("retryCount", false, 5)
|
||||
->addDateTime("nextTry", false, $sql->now())
|
||||
->addString("errorMessage", NULL, true)
|
||||
->primaryKey("id");
|
||||
|
||||
$queries = array_merge($queries, \Core\Configuration\Patch\EntityLog_2021_04_08::createTableLog($sql, "MailQueue", 30));
|
||||
|
||||
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
|
||||
->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves")
|
||||
->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys")
|
||||
->addRow("ApiKey/refresh", array(), "Allows users to refresh their API-Keys")
|
||||
->addRow("ApiKey/revoke", array(), "Allows users to revoke their API-Keys")
|
||||
->addRow("Groups/fetch", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to list all available groups")
|
||||
->addRow("Groups/create", array(USER_GROUP_ADMIN), "Allows users to create a new groups")
|
||||
->addRow("Groups/delete", array(USER_GROUP_ADMIN), "Allows users to delete a group")
|
||||
->addRow("Routes/fetch", array(USER_GROUP_ADMIN), "Allows users to list all configured routes")
|
||||
->addRow("Routes/save", array(USER_GROUP_ADMIN), "Allows users to create, delete and modify routes")
|
||||
->addRow("Mail/test", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to send a test email to a given address")
|
||||
->addRow("Mail/Sync", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to synchronize mails with the database")
|
||||
->addRow("Settings/get", array(USER_GROUP_ADMIN), "Allows users to fetch server settings")
|
||||
->addRow("Settings/set", array(USER_GROUP_ADMIN), "Allows users create, delete or modify server settings")
|
||||
->addRow("Stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch server stats")
|
||||
->addRow("User/create", array(USER_GROUP_ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
|
||||
->addRow("User/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to list all registered users")
|
||||
->addRow("User/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to get information about a single user")
|
||||
->addRow("User/invite", array(USER_GROUP_ADMIN), "Allows users to create a new user and send them an invitation link")
|
||||
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
|
||||
->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user")
|
||||
->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions")
|
||||
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics")
|
||||
->addRow("Contact/respond", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to respond to contact requests")
|
||||
->addRow("Contact/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch all contact requests")
|
||||
->addRow("Contact/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see messages within a contact request");
|
||||
|
||||
self::loadPatches($queries, $sql);
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
private static function loadPatches(&$queries, $sql) {
|
||||
$baseDirs = ["Core", "Site"];
|
||||
foreach ($baseDirs as $baseDir) {
|
||||
$patchDirectory = "./$baseDir/Configuration/Patch/";
|
||||
if (file_exists($patchDirectory) && is_dir($patchDirectory)) {
|
||||
$scan_arr = scandir($patchDirectory);
|
||||
$files_arr = array_diff($scan_arr, array('.', '..'));
|
||||
foreach ($files_arr as $file) {
|
||||
$suffix = ".class.php";
|
||||
if (endsWith($file, $suffix)) {
|
||||
$className = substr($file, 0, strlen($file) - strlen($suffix));
|
||||
$className = "\\$baseDir\\Configuration\\Patch\\$className";
|
||||
$method = "$className::createQueries";
|
||||
$patchQueries = call_user_func($method, $sql);
|
||||
foreach ($patchQueries as $query) $queries[] = $query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadEntities(&$queries, $sql) {
|
||||
$handlers = [];
|
||||
$baseDirs = ["Core", "Site"];
|
||||
foreach ($baseDirs as $baseDir) {
|
||||
$entityDirectory = "./$baseDir/Objects/DatabaseEntity/";
|
||||
if (file_exists($entityDirectory) && is_dir($entityDirectory)) {
|
||||
$scan_arr = scandir($entityDirectory);
|
||||
$files_arr = array_diff($scan_arr, array('.', '..'));
|
||||
foreach ($files_arr as $file) {
|
||||
$suffix = ".class.php";
|
||||
if (endsWith($file, $suffix)) {
|
||||
$className = substr($file, 0, strlen($file) - strlen($suffix));
|
||||
if (!in_array($className, ["DatabaseEntity", "DatabaseEntityQuery", "DatabaseEntityHandler"])) {
|
||||
$className = "\\$baseDir\\Objects\\DatabaseEntity\\$className";
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
if ($reflectionClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
$method = "$className::getHandler";
|
||||
$handler = call_user_func($method, $sql);
|
||||
$handlers[$handler->getTableName()] = $handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tableCount = count($handlers);
|
||||
$createdTables = [];
|
||||
while (!empty($handlers)) {
|
||||
$prevCount = $tableCount;
|
||||
$unmetDependenciesTotal = [];
|
||||
|
||||
foreach ($handlers as $tableName => $handler) {
|
||||
$dependsOn = $handler->dependsOn();
|
||||
$unmetDependencies = array_diff($dependsOn, $createdTables);
|
||||
if (empty($unmetDependencies)) {
|
||||
$queries[] = $handler->getTableQuery();
|
||||
$createdTables[] = $tableName;
|
||||
unset($handlers[$tableName]);
|
||||
} else {
|
||||
$unmetDependenciesTotal = array_merge($unmetDependenciesTotal, $unmetDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
$tableCount = count($handlers);
|
||||
if ($tableCount === $prevCount) {
|
||||
throw new Exception("Circular or unmet table dependency detected. Unmet dependencies: "
|
||||
. implode(", ", $unmetDependenciesTotal));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Core/Configuration/DatabaseScript.class.php
Normal file
9
Core/Configuration/DatabaseScript.class.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Configuration;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
abstract class DatabaseScript {
|
||||
public static abstract function createQueries(SQL $sql);
|
||||
}
|
||||
95
Core/Configuration/Patch/EntityLog_2021_04_08.class.php
Normal file
95
Core/Configuration/Patch/EntityLog_2021_04_08.class.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Configuration\Patch;
|
||||
|
||||
use Core\Configuration\DatabaseScript;
|
||||
use Core\Driver\SQL\Column\IntColumn;
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
use Core\Driver\SQL\Query\CreateProcedure;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Driver\SQL\Type\CurrentColumn;
|
||||
use Core\Driver\SQL\Type\CurrentTable;
|
||||
use Core\Driver\SQL\Type\Trigger;
|
||||
|
||||
class EntityLog_2021_04_08 extends DatabaseScript {
|
||||
|
||||
public static function createTableLog(SQL $sql, string $table, int $lifetime = 90): array {
|
||||
return [
|
||||
$sql->createTrigger("${table}_trg_insert")
|
||||
->after()->insert($table)
|
||||
->exec(new CreateProcedure($sql, "InsertEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
"lifetime" => $lifetime,
|
||||
]),
|
||||
|
||||
$sql->createTrigger("${table}_trg_update")
|
||||
->after()->update($table)
|
||||
->exec(new CreateProcedure($sql, "UpdateEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
]),
|
||||
|
||||
$sql->createTrigger("${table}_trg_delete")
|
||||
->after()->delete($table)
|
||||
->exec(new CreateProcedure($sql, "DeleteEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
|
||||
$queries = array();
|
||||
|
||||
$queries[] = $sql->createTable("EntityLog")
|
||||
->addInt("entityId")
|
||||
->addString("tableName")
|
||||
->addDateTime("modified", false, $sql->now())
|
||||
->addInt("lifetime", false, 90);
|
||||
|
||||
$insertProcedure = $sql->createProcedure("InsertEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("id"))
|
||||
->param(new IntColumn("lifetime", false, 90))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->insert("EntityLog", ["entityId", "tableName", "lifetime"])
|
||||
->addRow(new CurrentColumn("id"), new CurrentTable(), new CurrentColumn("lifetime"))
|
||||
));
|
||||
|
||||
$updateProcedure = $sql->createProcedure("UpdateEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("id"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->update("EntityLog")
|
||||
->set("modified", $sql->now())
|
||||
->where(new Compare("entityId", new CurrentColumn("id")))
|
||||
->where(new Compare("tableName", new CurrentTable()))
|
||||
));
|
||||
|
||||
$deleteProcedure = $sql->createProcedure("DeleteEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("id"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->delete("EntityLog")
|
||||
->where(new Compare("entityId", new CurrentColumn("id")))
|
||||
->where(new Compare("tableName", new CurrentTable()))
|
||||
));
|
||||
|
||||
$queries[] = $insertProcedure;
|
||||
$queries[] = $updateProcedure;
|
||||
$queries[] = $deleteProcedure;
|
||||
|
||||
$tables = ["ContactRequest"];
|
||||
foreach ($tables as $table) {
|
||||
$queries = array_merge($queries, self::createTableLog($sql, $table));
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
}
|
||||
16
Core/Configuration/Patch/SystemLog_2022_03_30.class.php
Normal file
16
Core/Configuration/Patch/SystemLog_2022_03_30.class.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Configuration\Patch;
|
||||
|
||||
use Core\Configuration\DatabaseScript;
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class SystemLog_2022_03_30 extends DatabaseScript {
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
return [
|
||||
$sql->insert("ApiPermission", ["method", "groups", "description"])
|
||||
->addRow("Logs/get", [USER_GROUP_ADMIN], "Allows users to fetch system logs")
|
||||
];
|
||||
}
|
||||
}
|
||||
221
Core/Configuration/Settings.class.php
Normal file
221
Core/Configuration/Settings.class.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Do not change settings here, they are dynamically loaded from database.
|
||||
*/
|
||||
|
||||
namespace Core\Configuration;
|
||||
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Driver\SQL\Query\Insert;
|
||||
use Core\Objects\Context;
|
||||
|
||||
class Settings {
|
||||
|
||||
//
|
||||
private bool $installationComplete;
|
||||
|
||||
// settings
|
||||
private string $siteName;
|
||||
private string $baseUrl;
|
||||
private ?string $jwtPublicKey;
|
||||
private ?string $jwtSecretKey;
|
||||
private string $jwtAlgorithm;
|
||||
private bool $registrationAllowed;
|
||||
private bool $recaptchaEnabled;
|
||||
private bool $mailEnabled;
|
||||
private string $recaptchaPublicKey;
|
||||
private string $recaptchaPrivateKey;
|
||||
private string $mailSender;
|
||||
private string $mailFooter;
|
||||
private array $allowedExtensions;
|
||||
|
||||
//
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct() {
|
||||
$this->logger = new Logger("Settings");
|
||||
}
|
||||
|
||||
public function getJwtPublicKey(bool $allowPrivate = true): ?\Firebase\JWT\Key {
|
||||
if (empty($this->jwtPublicKey)) {
|
||||
// we might have a symmetric key, should we instead return the private key?
|
||||
return $allowPrivate ? new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm) : null;
|
||||
} else {
|
||||
return new \Firebase\JWT\Key($this->jwtPublicKey, $this->jwtAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
public function getJwtSecretKey(): \Firebase\JWT\Key {
|
||||
return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm);
|
||||
}
|
||||
|
||||
public function isInstalled(): bool {
|
||||
return $this->installationComplete;
|
||||
}
|
||||
|
||||
public static function loadDefaults(): Settings {
|
||||
$hostname = $_SERVER["SERVER_NAME"] ?? "localhost";
|
||||
$protocol = getProtocol();
|
||||
$settings = new Settings();
|
||||
|
||||
// General
|
||||
$settings->siteName = "WebBase";
|
||||
$settings->baseUrl = "$protocol://$hostname";
|
||||
$settings->allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'htm', 'html'];
|
||||
$settings->installationComplete = false;
|
||||
$settings->registrationAllowed = false;
|
||||
|
||||
// JWT
|
||||
$settings->jwtSecretKey = null;
|
||||
$settings->jwtPublicKey = null;
|
||||
$settings->jwtAlgorithm = "HS256";
|
||||
|
||||
// Recaptcha
|
||||
$settings->recaptchaEnabled = false;
|
||||
$settings->recaptchaPublicKey = "";
|
||||
$settings->recaptchaPrivateKey = "";
|
||||
|
||||
// Mail
|
||||
$settings->mailEnabled = false;
|
||||
$settings->mailSender = "webmaster@localhost";
|
||||
$settings->mailFooter = "";
|
||||
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function generateJwtKey(string $algorithm = null): bool {
|
||||
$this->jwtAlgorithm = $algorithm ?? $this->jwtAlgorithm;
|
||||
|
||||
// TODO: key encryption necessary?
|
||||
if (in_array($this->jwtAlgorithm, ["HS256", "HS384", "HS512"])) {
|
||||
$this->jwtSecretKey = generateRandomString(32);
|
||||
$this->jwtPublicKey = null;
|
||||
} else if (in_array($this->jwtAlgorithm, ["RS256", "RS384", "RS512"])) {
|
||||
$bits = intval(substr($this->jwtAlgorithm, 2));
|
||||
$private_key = openssl_pkey_new(["private_key_bits" => $bits]);
|
||||
$this->jwtPublicKey = openssl_pkey_get_details($private_key)['key'];
|
||||
openssl_pkey_export($private_key, $this->jwtSecretKey);
|
||||
} else if (in_array($this->jwtAlgorithm, ["ES256", "ES384"])) {
|
||||
// $ec = new \Elliptic\EC('secp256k1'); ??
|
||||
$this->logger->error("JWT algorithm: '$this->jwtAlgorithm' is currently not supported.");
|
||||
return false;
|
||||
} else if ($this->jwtAlgorithm == "EdDSA") {
|
||||
$keyPair = sodium_crypto_sign_keypair();
|
||||
$this->jwtSecretKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
|
||||
$this->jwtPublicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
|
||||
} else {
|
||||
$this->logger->error("Invalid JWT algorithm: '$this->jwtAlgorithm', expected one of: " .
|
||||
implode(",", array_keys(\Firebase\JWT\JWT::$supported_algs)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isJwtAlgorithmSupported(string $algorithm): bool {
|
||||
return in_array(strtoupper($algorithm), ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "EDDSA"]);
|
||||
}
|
||||
|
||||
public function saveJwtKey(Context $context) {
|
||||
$req = new \Core\API\Settings\Set($context);
|
||||
$req->execute(array("settings" => array(
|
||||
"jwt_secret_key" => $this->jwtSecretKey,
|
||||
"jwt_public_key" => $this->jwtSecretKey,
|
||||
"jwt_algorithm" => $this->jwtAlgorithm,
|
||||
)));
|
||||
|
||||
return $req;
|
||||
}
|
||||
|
||||
public function loadFromDatabase(Context $context): bool {
|
||||
$this->logger = new Logger("Settings", $context->getSQL());
|
||||
$req = new \Core\API\Settings\Get($context);
|
||||
$success = $req->execute();
|
||||
|
||||
if ($success) {
|
||||
$result = $req->getResult()["settings"];
|
||||
$this->siteName = $result["site_name"] ?? $this->siteName;
|
||||
$this->baseUrl = $result["base_url"] ?? $this->baseUrl;
|
||||
$this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed;
|
||||
$this->installationComplete = $result["installation_completed"] ?? $this->installationComplete;
|
||||
$this->jwtSecretKey = $result["jwt_secret_key"] ?? $this->jwtSecretKey;
|
||||
$this->jwtPublicKey = $result["jwt_public_key"] ?? $this->jwtPublicKey;
|
||||
$this->jwtAlgorithm = $result["jwt_algorithm"] ?? $this->jwtAlgorithm;
|
||||
$this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled;
|
||||
$this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey;
|
||||
$this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey;
|
||||
$this->mailEnabled = $result["mail_enabled"] ?? $this->mailEnabled;
|
||||
$this->mailSender = $result["mail_from"] ?? $this->mailSender;
|
||||
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
|
||||
$this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions)));
|
||||
|
||||
if (!isset($result["jwt_secret_key"])) {
|
||||
if ($this->generateJwtKey()) {
|
||||
$this->saveJwtKey($context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addRows(Insert $query) {
|
||||
$query->addRow("site_name", $this->siteName, false, false)
|
||||
->addRow("base_url", $this->baseUrl, false, false)
|
||||
->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false)
|
||||
->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true)
|
||||
->addRow("jwt_secret_key", $this->jwtSecretKey, true, false)
|
||||
->addRow("jwt_public_key", $this->jwtPublicKey, false, false)
|
||||
->addRow("jwt_algorithm", $this->jwtAlgorithm, false, false)
|
||||
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
||||
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
||||
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
||||
->addRow("allowed_extensions", implode(",", $this->allowedExtensions), true, false);
|
||||
}
|
||||
|
||||
public function getSiteName(): string {
|
||||
return $this->siteName;
|
||||
}
|
||||
|
||||
public function getBaseUrl(): string {
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
public function isRecaptchaEnabled(): bool {
|
||||
return $this->recaptchaEnabled;
|
||||
}
|
||||
|
||||
public function getRecaptchaSiteKey(): string {
|
||||
return $this->recaptchaPublicKey;
|
||||
}
|
||||
|
||||
public function getRecaptchaSecretKey(): string {
|
||||
return $this->recaptchaPrivateKey;
|
||||
}
|
||||
|
||||
public function isRegistrationAllowed(): bool {
|
||||
return $this->registrationAllowed;
|
||||
}
|
||||
|
||||
public function isMailEnabled(): bool {
|
||||
return $this->mailEnabled;
|
||||
}
|
||||
|
||||
public function getMailSender(): string {
|
||||
return $this->mailSender;
|
||||
}
|
||||
|
||||
public function isExtensionAllowed(string $ext): bool {
|
||||
return empty($this->allowedExtensions) || in_array(strtolower(trim($ext)), $this->allowedExtensions);
|
||||
}
|
||||
|
||||
public function getDomain(): string {
|
||||
return parse_url($this->getBaseUrl(), PHP_URL_HOST);
|
||||
}
|
||||
|
||||
public function getLogger(): Logger {
|
||||
return $this->logger;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user