commit 6d1891a5838b1feaf400fa9fff839437bfa23b2e Author: Collin Date: Wed Jan 31 13:32:14 2024 +0000 initial upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4cb68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/debug/* +build/release/* +src/vendor/ \ No newline at end of file diff --git a/build-phar b/build-phar new file mode 100644 index 0000000..da1e9fa --- /dev/null +++ b/build-phar @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +php -dphar.readonly=0 build/build-phar.php \ No newline at end of file diff --git a/build/build-phar.php b/build/build-phar.php new file mode 100644 index 0000000..bf06023 --- /dev/null +++ b/build/build-phar.php @@ -0,0 +1,75 @@ +{"path"}; +$filename = $config->{"filename"}; +$version = $config->{"version"}; +$branch = "debug"; + +if($config->{"debug"}) $path = $path."/debug"; +else { + $path = $path."/release"; + $branch = "release"; +} + +echo "[CONF] Debug=".$config->{"debug"}."\n"; +echo "[CONF] Path=".$path."\n"; +echo "[CONF] Filename=".$filename."\n"; +echo "[CONF] Version=".$version."\n"; + +echo "\nBuild? (Y/n)\n"; +$handle = fopen ("php://stdin","r"); +$line = fgets($handle); +if(trim(strtolower($line)) === 'no' || trim(strtolower($line)) === "n"){ + echo "ABORTING!\n"; + exit; +} + +$start = time(); + +builderLog("[COMPOSER] Running 'composer install'"); +exec("cd src ; ./composer.phar install"); + +if(file_exists($path."/".$filename."-".$version.".phar")){ + unlink($path."/".$filename."-".$version.".phar"); + builderLog("[CLEANUP] Deleted an old build"); +} +if(file_exists("build/run-phar.sh")){ + unlink(__DIR__."/run-phar.sh"); + builderLog("[CLEANUP] Deleted an old runner"); +} + +try { + builderLog("[BUILD] Creating a new Phar object"); + $phar = new Phar($path."/".$filename."-".$version.".phar", 0, $path."/".$filename."-".$version.".phar"); + + builderLog("[BUILD] Adding files to the Phar archive"); + $phar->buildFromDirectory(__DIR__ . '/../src'); + + builderLog("[BUILD] Set the default stub file"); + $phar->setDefaultStub('LonaDB/LonaDB.php', 'LonaDB/LonaBD.php'); + + $phar->setAlias($filename."-".$version.".phar"); + + builderLog("[BUILD] Saving the new Phar archive"); + $phar->stopBuffering(); + + builderLog("[INFO] Phar archive created successfully"); + + builderLog("[RUN] Generating run script"); + file_put_contents("./build/run-phar.sh", "cd ".$path." ; php ".$filename."-".$version.".phar"); + + builderLog("[RUN] Adding Permissions to run script"); + exec("chmod 777 ./build/run-phar.sh"); + + echo "Done!\nBuilt in ".(time() - $start) ." ms!\n"; +} catch (Exception $e) { + builderLog('[ERROR] '.$e->getMessage()); +} diff --git a/build/run-phar.sh b/build/run-phar.sh new file mode 100755 index 0000000..60eec8d --- /dev/null +++ b/build/run-phar.sh @@ -0,0 +1 @@ +cd build/debug ; php LonaDB-2.0.phar \ No newline at end of file diff --git a/build_config.json b/build_config.json new file mode 100644 index 0000000..3f11d88 --- /dev/null +++ b/build_config.json @@ -0,0 +1,6 @@ +{ + "filename": "LonaDB", + "version": "4.0.0-beta", + "path": "build", + "debug": true +} \ No newline at end of file diff --git a/run b/run new file mode 100644 index 0000000..84010e3 --- /dev/null +++ b/run @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +./build/run-phar.sh \ No newline at end of file diff --git a/src/LonaDB/Logger.php b/src/LonaDB/Logger.php new file mode 100644 index 0000000..795741b --- /dev/null +++ b/src/LonaDB/Logger.php @@ -0,0 +1,70 @@ +LonaDB = $lonaDB; + } + + private function log(string $message){ + echo($message); + if($this->LonaDB->config["logging"]) fwrite($this->LogFile, $message); + } + + public function LoadLogger(){ + if($this->LonaDB->config["logging"]) $this->LogFile = fopen('log.txt','a'); + } + + public function Warning($msg){ + $log = date("Y-m-d h:i:s")." [WARNING] ".$msg."\n"; + $this->log($log); + } + + public function Error($msg){ + $log = date("Y-m-d h:i:s")." [ERROR] ".$msg."\n"; + $this->log($log); + } + + public function Create($msg){ + $log = date("Y-m-d h:i:s")." [CREATE] ".$msg."\n"; + $this->log($log); + } + + public function Load($msg){ + $log = date("Y-m-d h:i:s")." [LOAD] ".$msg."\n"; + $this->log($log); + } + + public function Info($msg){ + $log = date("Y-m-d h:i:s")." [INFO] ".$msg."\n"; + $this->log($log); + } + + public function Table($msg){ + $log = date("Y-m-d h:i:s")." [TABLE] ".$msg."\n"; + $this->log($log); + } + + public function User($msg){ + $log = date("Y-m-d h:i:s")." [USER] ".$msg."\n"; + $this->log($log); + } + + public function InfoCache($msg){ + $log = date("Y-m-d h:i:s")." [INFO] ".$msg."\n"; + echo($log); + $this->infoCache = $this->infoCache.$log; + } + + public function DropCache(){ + if($this->LonaDB->config["logging"]) fwrite($this->LogFile, $this->infoCache); + } +} \ No newline at end of file diff --git a/src/LonaDB/LonaDB.php b/src/LonaDB/LonaDB.php new file mode 100644 index 0000000..a2cd227 --- /dev/null +++ b/src/LonaDB/LonaDB.php @@ -0,0 +1,122 @@ +EncryptionKey = str_replace("\n","",$key); + unset($key); + + $this->Logger = new Logger($this); + + try{ + echo chr(27).chr(91).'H'.chr(27).chr(91).'J'; + error_reporting(E_ERROR | E_PARSE); + + $this->Logger->InfoCache("Looking for config."); + + //somehow file_exists() always retuns false for me... But this checks if the file did exist in the first place. + file_put_contents("configuration.lona", file_get_contents("configuration.lona")); + if(file_get_contents("configuration.lona") === "") $this->setup(); + + else{ + $parts = explode(':', file_get_contents("./configuration.lona")); + $decrypted = openssl_decrypt($parts[0], AES_256_CBC, $this->EncryptionKey, 0, base64_decode($parts[1])); + if(!json_decode($decrypted, true)) { + echo "Encryption Key does not match the existing Configuration file. Exiting.\n"; + exit; + } + } + + $this->Logger->InfoCache("Loading config."); + $parts = explode(':', file_get_contents("./configuration.lona")); + $decrypted = openssl_decrypt($parts[0], AES_256_CBC, $this->EncryptionKey, 0, base64_decode($parts[1])); + $this->config = json_decode($decrypted, true); + + $this->Logger->InfoCache("Checking config."); + if(!$this->config["port"] || !$this->config["address"] || !$this->config["encryptionKey"] || !$this->config["root"]) { + $this->setup(); + } + + $this->Logger->LoadLogger(); + $this->Logger->DropCache(); + + $this->Logger->Info("Loading Server class."); + $this->Server = new Server($this); + $this->Logger->Info("Loading TableManager class."); + $this->TableManager = new TableManager($this); + $this->Logger->Info("Loading UserManager class."); + $this->UserManager = new UserManager($this); + } + catch (\Exception $e){ + $this->Logger->Error($e); + } + } + + private function setup() { + $this->Logger->InfoCache("Invalid or missing config. Starting setup."); + echo "Database port:\n"; + $portHandle = fopen ("php://stdin","r"); + $port = fgets($portHandle); + fclose($portHandle); + + echo "Database address:\n"; + $addrHandle = fopen ("php://stdin","r"); + $addr = fgets($addrHandle); + fclose($addrHandle); + + echo "Table encryption key:\n"; + $keyHandle = fopen ("php://stdin","r"); + $key = fgets($keyHandle); + fclose($keyHandle); + + echo "Password for root user:\n"; + $rootHandle = fopen ("php://stdin","r"); + $root = fgets($rootHandle); + fclose($rootHandle); + + echo "Enable logging? (y/N):\n"; + $logHandle = fopen ("php://stdin","r"); + $logAns = fgets($logHandle); + fclose($logHandle); + $log = false; + if(trim(strtolower($logAns)) === "y") $log = true; + + $this->Logger->InfoCache("Saving config."); + + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_256_CBC)); + $save = array( + "port" => str_replace("\n","",$port), + "address" => str_replace("\n","",$addr), + "logging" => $log, + "encryptionKey" => str_replace("\n","",$key), + "root" => str_replace("\n","",$root) + ); + + $encrypted = openssl_encrypt(json_encode($save), AES_256_CBC, $this->EncryptionKey, 0, $iv); + file_put_contents("./configuration.lona", $encrypted.":".base64_encode($iv)); + } +} + +$run = new LonaDB(); diff --git a/src/LonaDB/Server.php b/src/LonaDB/Server.php new file mode 100644 index 0000000..3314f5a --- /dev/null +++ b/src/LonaDB/Server.php @@ -0,0 +1,18 @@ +LonaDB = $lonaDB; + $this->config = $lonaDB->config; + } +} + +?> diff --git a/src/LonaDB/Tables/Table.php b/src/LonaDB/Tables/Table.php new file mode 100644 index 0000000..4a5303d --- /dev/null +++ b/src/LonaDB/Tables/Table.php @@ -0,0 +1,135 @@ +LonaDB = $lonaDB; + + if(!$create){ + $this->LonaDB->Logger->Load("Trying to load table '".$name."'"); + + $parts = explode(':', file_get_contents("./data/tables/".$name)); + $temp = json_decode(openssl_decrypt($parts[0], AES_256_CBC, $this->LonaDB->config["encryptionKey"], 0, base64_decode($parts[1])), true); + + $this->file = substr($name, 0, -5); + $this->data = $temp["data"]; + $this->permissions = $temp["permissions"]; + $this->Owner = $temp["owner"]; + } + else{ + $this->LonaDB->Logger->Table("Trying to generate table '".$name."'"); + + $this->file = $name; + $this->data = array(); + $this->permissions = array(); + $this->Owner = $owner; + + $this->Save(); + } + + $this->Name = $this->file; + } + + public function GetOwner(string $user = ""){ + if($user === "") return $this->Owner; + + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' is trying to get the owner name."); + if($this->CheckPermission($user, "read")) return; + return $this->Owner; + } + + public function SetOwner(string $name, string $user){ + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' is trying to change the owner to '".$name."'"); + if($user !== "root" && $user !== $this->Owner) return; + + $this->Owner = $name; + $this->Save(); + } + + public function Set(string $name, mixed $value, string $user){ + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' is trying to set the variable '".$name."' to '".strval($value)."'"); + if(!$this->CheckPermission($user, "write")) return; + + $this->data[$name] = $value; + $this->Save(); + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' set the variable '".$name."' to '".strval($value)."'"); + } + + public function Delete(string $name, string $user){ + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' is trying to delete the variable '".$name."'"); + if(!$this->CheckPermission($user, "write")) return; + + unset($this->data[$name]); + $this->Save(); + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' deleted the variable '".$name."'"); + } + + public function Get(string $name, string $user){ + $this->LonaDB->Logger->Table("(".$this->file.") User '".$user."' is trying to get the variable '".$name."'"); + if(!$this->CheckPermission($user, "read")) return null; + + return $this->data->{$name}; + } + + public function CheckPermission(string $user, string $permission){ + $this->LonaDB->Logger->Table("(".$this->file.") Checkin permission '".$permission."' for user '".$user."'"); + + if($user === $this->Owner || $this->permissions[$user]["admin"]) return true; + if(!$this->permissions[$user][$permission]) return false; + + return true; + } + + public function AddPermission(string $name, string $permission, string $user){ + if($user !== $this->Owner && !$this->permissions[$user]["admin"]) return + $this->LonaDB->Logger->Table("(".$this->file.") Missing permission! Adding permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + if($user !== $this->Owner && $permission === "admin") return + $this->LonaDB->Logger->Table("(".$this->file.") Not the Owner! Adding permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + $this->LonaDB->Logger->Table("(".$this->file.") Adding permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + $this->permissions[$name][$permission] = true; + $this->Save(); + } + + public function RemovePermission(string $name, string $permission, string $user){ + if($user !== $this->Owner && !$this->permissions[$user]["admin"]) return + $this->LonaDB->Logger->Table("(".$this->file.") Missing permission! Removing permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + if($user !== $this->Owner && $permission === "admin") return + $this->LonaDB->Logger->Table("(".$this->file.") Not the Owner! Removing permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + $this->LonaDB->Logger->Table("(".$this->file.") Removing permission '".$permission."' for user '".$name."', requested by '".$user."'"); + + unset($this->permissions[$name][$permission]); + + if($this->permissions[$name] === array()) unset($this->permissions[$name]); + $this->Save(); + } + + private function Save(){ + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_256_CBC)); + $save = array( + "data" => $this->data, + "permissions" => $this->permissions, + "owner" => $this->Owner + ); + + $encrypted = openssl_encrypt(json_encode($save), AES_256_CBC, $this->LonaDB->config["encryptionKey"], 0, $iv); + file_put_contents("./data/tables/".$this->file.".lona", $encrypted.":".base64_encode($iv)); + } +} \ No newline at end of file diff --git a/src/LonaDB/Tables/TableManager.php b/src/LonaDB/Tables/TableManager.php new file mode 100644 index 0000000..a9faf98 --- /dev/null +++ b/src/LonaDB/Tables/TableManager.php @@ -0,0 +1,80 @@ +LonaDB = $lonaDB; + $this->Tables = array(); + + if(!is_dir("data/")) mkdir("data/"); + if(!is_dir("data/tables/")) mkdir("data/tables/"); + + $counter = 0; + + foreach (new \DirectoryIterator('data/tables') as $fileInfo) { + if(str_ends_with($fileInfo->getFilename(), ".lona")){ + $this->LonaDB->Logger->Load("Loading table from file '" . $fileInfo . "'"); + $this->Tables[substr($fileInfo->getFilename(), 0, -5)] = new Table($this->LonaDB, false, $fileInfo->getFilename()); + $counter = $counter + 1; + } + } + + if($counter === 0){ + $this->CreateTable("Default", "root"); + } + } + + public function GetTable(string $name){ + if(!$this->Tables[$name]) return false; + return $this->Tables[$name]; + } + + public function ListTables(string $user = ""){ + $tables = array(); + if($user !== ""){ + foreach($this->Tables as $table){ + if($table->CheckPermission($user, "write")) array_push($tables, $table->Name); + else if($table->CheckPermission($user, "read")) array_push($tables, $table->Name); + } + } + else{ + foreach($this->Tables as $table){ + array_push($tables, $table->Name); + } + } + + return $tables; + } + + public function CreateTable(string $name, string $owner){ + $this->LonaDB->Logger->Table("Trying to create table '" . $name . "', owned by user '" . $owner . "'"); + if($this->Tables[$name]) { + $this->LonaDB->Logger->Error("Table '" . $name . "' already exists"); + return false; + } + $this->Tables[$name] = new Table($this->LonaDB, true, $name, $owner); + $this->LonaDB->Logger->Table("Table '" . $name . "' has been created"); + } + + public function DeleteTable(string $name, string $user){ + $this->LonaDB->Logger->Table("Trying to delete table '" . $name . "', requested by user '" . $user . "'"); + if(!$this->Tables[$name]) { + $this->LonaDB->Logger->Error("Table '" . $name . "' doesn't exist"); + return; + } + + if($user !== $this->Tables[$name]->GetOwner()) return + $this->LonaDB->Logger->Table("Not the owner! Trying to delete table '" . $name . "', requested by user '" . $user . "'"); + + unlink("data/".$name.".json"); + unset($this->Tables[$name]); + $this->LonaDB->Logger->Table("Deleted table '" . $name . "', requested by user '" . $user . "'"); + } +} \ No newline at end of file diff --git a/src/LonaDB/Users/UserManager.php b/src/LonaDB/Users/UserManager.php new file mode 100644 index 0000000..f05f5e4 --- /dev/null +++ b/src/LonaDB/Users/UserManager.php @@ -0,0 +1,86 @@ +LonaDB = $lonaDB; + $this->Tables = array(); + + if(!is_dir("data/")) mkdir("data/"); + + file_put_contents("data/Users.lona", file_get_contents("data/Users.lona")); + if(file_get_contents("data/Users.lona") === "") { + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_256_CBC)); + $save = array(); + + $encrypted = openssl_encrypt(json_encode($save), AES_256_CBC, $this->LonaDB->config["encryptionKey"], 0, $iv); + file_put_contents("./data/Users.lona", $encrypted.":".base64_encode($iv)); + } + + $parts = explode(':', file_get_contents("./data/Users.lona")); + $this->Users = json_decode(openssl_decrypt($parts[0], AES_256_CBC, $this->LonaDB->config["encryptionKey"], 0, base64_decode($parts[1])), true); + } + + public function CheckPassword(string $name, string $password){ + $this->LonaDB->Logger->User("Trying to check password for user '" . $name . "'"); + if($name === "root" && $password === $this->LonaDB->config["root"]) return true; + + if(!$this->Users[$name]) { + $this->LonaDB->Logger->User("User '".$name."'doesn't exist"); + return false; + } + + if($this->Users[$name]["password"] !== $password) return false; + + return true; + } + + public function CreateUser(string $name, string $password){ + if($name === "root") return; + $this->LonaDB->Logger->User("Trying to create user '" . $name . "'"); + if($this->Users[$name]) { + $this->LonaDB->Logger->Error("User '" . $name . "' already exists"); + return false; + } + + $this->Users[$name] = array( + "role" => "user", + "password" => $password, + "permissions" => [] + ); + + $this->LonaDB->Logger->User("User '" . $name . "' has been created"); + + $this->Save(); + } + + public function DeleteUser(string $name){ + if($name === "root") return; + $this->LonaDB->Logger->User("Trying to delete user '" . $name . "'"); + + if(!$this->Users[$name]) { + $this->LonaDB->Logger->Error("User '" . $name . "' doesn't exist"); + return; + } + + unset($this->Users[$name]); + $this->LonaDB->Logger->User("Deleted user '" . $name . "'"); + $this->Save(); + } + + public function Save(){ + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_256_CBC)); + + $encrypted = openssl_encrypt(json_encode($this->Users), AES_256_CBC, $this->LonaDB->config["encryptionKey"], 0, $iv); + file_put_contents("./data/Users.lona", $encrypted.":".base64_encode($iv)); + } +} \ No newline at end of file diff --git a/src/composer.json b/src/composer.json new file mode 100644 index 0000000..3a68df6 --- /dev/null +++ b/src/composer.json @@ -0,0 +1,17 @@ +{ + "name": "lona/database", + "description": "Object oriented DBMS", + "type": "project", + "license": "GNU AGPLv3", + "autoload": { + "psr-4": { + "LonaDB\\": "LonaDB/" + } + }, + "authors": [ + { + "name": "Lona Devs" + } + ], + "require": {} +} diff --git a/src/composer.lock b/src/composer.lock new file mode 100644 index 0000000..0bc8ffb --- /dev/null +++ b/src/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dc4784b5c77dc7aef8720fbec77a7d65", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/src/composer.phar b/src/composer.phar new file mode 100755 index 0000000..4723b1b Binary files /dev/null and b/src/composer.phar differ