diff --git a/server/composer.json b/server/composer.json index 71e09ba2..a506cdee 100644 --- a/server/composer.json +++ b/server/composer.json @@ -4,7 +4,8 @@ "respect/validation": "^1.1", "phpmailer/phpmailer": "^5.2", "google/recaptcha": "~1.1", - "gabordemooij/redbean": "^4.3" + "gabordemooij/redbean": "^4.3", + "ifsnop/mysqldump-php": "2.*" }, "require-dev": { "phpunit/phpunit": "5.0.*" diff --git a/server/controllers/system.php b/server/controllers/system.php index 2cde4fe2..e66ecc5d 100644 --- a/server/controllers/system.php +++ b/server/controllers/system.php @@ -9,6 +9,8 @@ require_once 'system/get-logs.php'; require_once 'system/get-mail-templates.php'; require_once 'system/edit-mail-template.php'; require_once 'system/recover-mail-template.php'; +require_once 'system/backup-database.php'; +require_once 'system/download.php'; $systemControllerGroup = new ControllerGroup(); $systemControllerGroup->setGroupPath('/system'); @@ -23,5 +25,7 @@ $systemControllerGroup->addController(new GetLogsController); $systemControllerGroup->addController(new GetMailTemplatesController); $systemControllerGroup->addController(new EditMailTemplateController); $systemControllerGroup->addController(new RecoverMailTemplateController); +$systemControllerGroup->addController(new BackupDatabaseController); +$systemControllerGroup->addController(new DownloadController); $systemControllerGroup->finalize(); \ No newline at end of file diff --git a/server/controllers/system/backup-database.php b/server/controllers/system/backup-database.php new file mode 100644 index 00000000..82b1fccf --- /dev/null +++ b/server/controllers/system/backup-database.php @@ -0,0 +1,30 @@ + 'staff_3', + 'requestData' => [] + ]; + } + + public function handler() { + global $mysql_host; + global $mysql_database; + global $mysql_user; + global $mysql_password; + + $fileDownloader = FileDownloader::getInstance(); + $fileDownloader->setFileName('backup.sql'); + + $mysqlDump = new IMysqldump\Mysqldump('mysql:host='. $mysql_host .';dbname=' . $mysql_database, $mysql_user, $mysql_password); + $mysqlDump->start($fileDownloader->getFullFilePath()); + + if($fileDownloader->download()) { + $fileDownloader->eraseFile(); + } + } +} \ No newline at end of file diff --git a/server/controllers/system/download.php b/server/controllers/system/download.php new file mode 100644 index 00000000..0b481bf8 --- /dev/null +++ b/server/controllers/system/download.php @@ -0,0 +1,24 @@ + 'staff_1', + 'requestData' => [ + 'file' => [ + 'validation' => DataValidator::alnum('_.')->noWhitespace() + ] + ] + ]; + } + + public function handler() { + $fileDownloader = FileDownloader::getInstance(); + $fileDownloader->setFileName(Controller::request('file')); + $fileDownloader->download(); + } +} \ No newline at end of file diff --git a/server/controllers/system/init-settings.php b/server/controllers/system/init-settings.php index 98fce211..6613b17a 100644 --- a/server/controllers/system/init-settings.php +++ b/server/controllers/system/init-settings.php @@ -40,7 +40,10 @@ class InitSettingsController extends Controller { 'allow-attachments' => 0, 'max-size' => 0, 'title' => 'Support Center', - 'url' => 'http://www.opensupports.com/support' + 'url' => 'http://www.opensupports.com/support', + 'ticket-gap' => Hashing::generateRandomPrime(100000, 999999), + 'file-gap' => Hashing::generateRandomPrime(100000, 999999), + 'file-first-number' => Hashing::generateRandomNumber(100000, 999999), ]); } diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index e0052543..bdd16d22 100644 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -35,4 +35,5 @@ class ERRORS { const INVALID_TEMPLATE = 'INVALID_TEMPLATE'; const INVALID_SUBJECT = 'INVALID_SUBJECT'; const INVALID_BODY = 'INVALID_BODY'; + const INVALID_FILE = 'INVALID_FILE'; } diff --git a/server/index.php b/server/index.php index a9056e1e..d8508ba3 100644 --- a/server/index.php +++ b/server/index.php @@ -18,6 +18,10 @@ include_once 'libs/Hashing.php'; include_once 'libs/MailSender.php'; include_once 'libs/Date.php'; include_once 'libs/DataStoreList.php'; +include_once 'libs/LinearCongruentialGenerator.php'; +include_once 'libs/FileManager.php'; +include_once 'libs/FileDownloader.php'; +include_once 'libs/FileUploader.php'; // LOAD DATA spl_autoload_register(function ($class) { diff --git a/server/libs/FileDownloader.php b/server/libs/FileDownloader.php new file mode 100644 index 00000000..6f49ca64 --- /dev/null +++ b/server/libs/FileDownloader.php @@ -0,0 +1,40 @@ +getFullFilePath(); + + if(file_exists($fullFilePath) && is_file($fullFilePath)) { + header('Cache-control: private'); + header('Content-Type: application/octet-stream'); + header('Content-Length: '.filesize($fullFilePath)); + header('Content-Disposition: filename='. $this->getFileName()); + + flush(); + $file = fopen($fullFilePath, 'r'); + print fread($file, filesize($fullFilePath)); + fclose($file); + + return true; + } else { + return false; + } + } + + public function eraseFile() { + unlink($this->getLocalPath() . $this->getFileName()); + } +} \ No newline at end of file diff --git a/server/libs/FileManager.php b/server/libs/FileManager.php new file mode 100644 index 00000000..fd05bb13 --- /dev/null +++ b/server/libs/FileManager.php @@ -0,0 +1,26 @@ +localPath = $localPath; + } + + public function setFileName($fileName) { + $this->fileName = $fileName; + } + + public function getLocalPath() { + return $this->localPath; + } + + public function getFileName() { + return $this->fileName; + } + + public function getFullFilePath() { + return $this->getLocalPath() . $this->getFileName(); + } +} \ No newline at end of file diff --git a/server/libs/FileUploader.php b/server/libs/FileUploader.php index 55eb9bbc..dfc9c09b 100644 --- a/server/libs/FileUploader.php +++ b/server/libs/FileUploader.php @@ -1,5 +1,10 @@ generateNewName($file['name']); + + if($file['size'] > (1024 * $this->maxSize)) { + return false; + } + + move_uploaded_file($file['tmp_name'], $this->getLocalPath() . $newFileName); + + return true; } + + private function generateNewName($fileName) { + $newName = $fileName; + $newName = strtolower($newName); + $newName = preg_replace('/\s+/', '_', $newName); + + if ($this->linearCongruentialGenerator instanceof LinearCongruentialGenerator) { + $newName = $this->linearCongruentialGenerator->generate($this->linearCongruentialGeneratorOffset) . '_' . $newName; + } + + return $newName; + } + + public function setGeneratorValues($gap, $first, $offset) { + $this->linearCongruentialGenerator = new LinearCongruentialGenerator(); + $this->linearCongruentialGeneratorOffset = $offset; + + $this->linearCongruentialGenerator->setGap($gap); + $this->linearCongruentialGenerator->setFirst($first); + } + + public function setMaxSize($maxSize) { + $this->maxSize = $maxSize; + } + } \ No newline at end of file diff --git a/server/libs/Hashing.php b/server/libs/Hashing.php index aa402cf5..da076181 100644 --- a/server/libs/Hashing.php +++ b/server/libs/Hashing.php @@ -7,10 +7,36 @@ class Hashing { public static function verifyPassword($password, $hash) { return password_verify($password, $hash); } + public static function generateRandomToken() { return md5(uniqid(rand())); } - public static function getRandomTicketNumber($min,$max) { - return rand($min,$max); + + public static function generateRandomNumber($min, $max) { + return rand($min, $max); + } + + public static function generateRandomPrime($min, $max) { + $number = Hashing::generateRandomNumber($min, $max); + + while(!Hashing::isPrime($number)) { + $number = Hashing::generateRandomNumber($min, $max); + } + + return $number; + } + + public static function isPrime($number) { + $sqrt = sqrt($number); + $prime = true; + + for($i = 0; $i < $sqrt; $i++) { + if($sqrt % 2 === 0) { + $prime = false; + break; + } + } + + return $prime; } } \ No newline at end of file diff --git a/server/libs/LinearCongruentialGenerator.php b/server/libs/LinearCongruentialGenerator.php new file mode 100644 index 00000000..aafddb16 --- /dev/null +++ b/server/libs/LinearCongruentialGenerator.php @@ -0,0 +1,30 @@ +min = $min; + $this->max = $max; + } + + public function setGap($gap) { + if(!Hashing::isPrime($gap)) throw new Exception('LinearCongruentialGenerator: gap must be prime'); + + $this->gap = $gap; + } + + public function setFirst($first) { + $this->first = $first; + } + + public function generate($offset) { + return ($this->first - $this->min + $offset * $this->gap) % ($this->max - $this->min + 1) + $this->min; + } + + public function generateFirst() { + return Hashing::generateRandomNumber($this->min, $this->max); + } +} \ No newline at end of file diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 315434e8..88351ea6 100644 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -46,17 +46,16 @@ class Ticket extends DataStore { } public function generateUniqueTicketNumber() { + $linearCongruentialGenerator = new LinearCongruentialGenerator(); $ticketQuantity = Ticket::count(); - $minValue = 100000; - $maxValue = 999999; - + if ($ticketQuantity === 0) { - $ticketNumber = Hashing::getRandomTicketNumber($minValue, $maxValue); + $ticketNumber = $linearCongruentialGenerator->generateFirst(); } else { - $firstTicketNumber = Ticket::getTicket(1)->ticketNumber; - $gap = 176611; //TODO: USE RANDOM PRIME INSTEAD - - $ticketNumber = ($firstTicketNumber - $minValue + $ticketQuantity * $gap) % ($maxValue - $minValue + 1) + $minValue; + $linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value); + $linearCongruentialGenerator->setFirst(Ticket::getTicket(1)->ticketNumber); + + $ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity); } return $ticketNumber; diff --git a/tests/ticket/create.rb b/tests/ticket/create.rb index 481e0b50..fbc6be44 100644 --- a/tests/ticket/create.rb +++ b/tests/ticket/create.rb @@ -147,13 +147,15 @@ describe '/ticket/create' do csrf_token: $csrf_token }) + ticket_number_gap = $database.getRow('setting', 'ticket-gap', 'name')['value'].to_i + ticket0 = $database.getRow('ticket','Winter is coming','title')['ticket_number'].to_i ticket1 = $database.getRow('ticket','Winter is coming1','title')['ticket_number'].to_i ticket2 = $database.getRow('ticket','Winter is coming2','title')['ticket_number'].to_i ticket3 = $database.getRow('ticket','Winter is coming3','title')['ticket_number'].to_i - (ticket1).should.equal((ticket0 - 100000 + 1 * 176611) % 900000 + 100000) - (ticket2).should.equal((ticket0 - 100000 + 2 * 176611) % 900000 + 100000) - (ticket3).should.equal((ticket0 - 100000 + 3 * 176611) % 900000 + 100000) + (ticket1).should.equal((ticket0 - 100000 + 1 * ticket_number_gap) % 900000 + 100000) + (ticket2).should.equal((ticket0 - 100000 + 2 * ticket_number_gap) % 900000 + 100000) + (ticket3).should.equal((ticket0 - 100000 + 3 * ticket_number_gap) % 900000 + 100000) end end