logger = new Logger("Settings"); } public static function getAll(?SQL $sql, ?string $pattern = null, bool $external = false): ?array { $query = $sql->select("name", "value") ->from("Settings"); if ($pattern) { $query->where(new CondRegex(new Column("name"), $pattern)); } if ($external) { $query->where(new CondNot("private")); } $res = $query->execute(); if ($res !== false && $res !== null) { $settings = array(); foreach($res as $row) { $settings[$row["name"]] = $row["value"]; } return $settings; } else { return null; } } public static function get(?SQL $sql, string $key, mixed $defaultValue): mixed { $res = $sql->select("value") ->from("Settings") ->whereEq("name", $key) ->execute(); if ($res === false || $res === null) { return null; } else { return (empty($res)) ? $defaultValue : $res[0]["value"]; } } public function getJwtPublicKey(bool $allowPrivate = true): ?\Firebase\JWT\Key { if (empty($this->jwtPublicKey)) { if ($allowPrivate && $this->jwtSecretKey) { // we might have a symmetric key, should we instead return the private key? return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm); } return null; } else { return new \Firebase\JWT\Key($this->jwtPublicKey, $this->jwtAlgorithm); } } public function getJwtSecretKey(): ?\Firebase\JWT\Key { return $this->jwtSecretKey ? new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm) : null; } public function isInstalled(): bool { return $this->installationComplete; } public static function loadDefaults(): Settings { $hostname = $_SERVER["SERVER_NAME"] ?? null; if (empty($hostname)) { $hostname = $_SERVER["HTTP_HOST"]; if (empty($hostname)) { $hostname = "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; $settings->timeZone = date_default_timezone_get(); // 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 = ""; $settings->mailAsync = false; 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): \Core\API\Settings\Set { $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->timeZone = $result["time_zone"] ?? $this->timeZone; $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->mailAsync = $result["mail_async"] ?? $this->mailAsync; $this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions))); if (!isset($result["jwt_secret_key"])) { if ($this->generateJwtKey()) { $this->saveJwtKey($context); } } date_default_timezone_set($this->timeZone); } return false; } public function addRows(Insert $query): void { $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("time_zone", $this->timeZone, false, false) ->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) ->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); } public function getSiteName(): string { return $this->siteName; } public function getTimeZone(): string { return $this->timeZone; } public function setTimeZone(string $tz) { $this->timeZone = $tz; } 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 isMailAsync(): bool { return $this->mailAsync; } 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; } }