itflow/post/admin/admin_backup.php

167 lines
6.0 KiB
PHP

<?php
/*
* ITFlow - GET/POST request handler for DB / master key backup
*/
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
require_once "includes/app_version.php";
if (isset($_GET['download_backup'])) {
validateCSRFToken($_GET['csrf_token']);
global $mysqli, $database;
$timestamp = date('YmdHis');
$baseName = "itflow_$timestamp";
$sqlFile = "$baseName.sql";
$uploadsZip = "$baseName_uploads.zip";
$finalZip = "$baseName.zip";
$versionFile = "version.txt";
// === 1. Generate SQL Dump ===
$sqlContent = "-- UTF-8 + Foreign Key Safe Dump\n";
$sqlContent .= "SET NAMES 'utf8mb4';\n";
$sqlContent .= "SET foreign_key_checks = 0;\n\n";
$tables = [];
$res = $mysqli->query("SHOW TABLES");
while ($row = $res->fetch_row()) {
$tables[] = $row[0];
}
foreach ($tables as $table) {
$createRes = $mysqli->query("SHOW CREATE TABLE `$table`");
$createRow = $createRes->fetch_assoc();
$createSQL = array_values($createRow)[1];
$sqlContent .= "\n-- ----------------------------\n";
$sqlContent .= "-- Table structure for `$table`\n";
$sqlContent .= "-- ----------------------------\n";
$sqlContent .= "DROP TABLE IF EXISTS `$table`;\n";
$sqlContent .= $createSQL . ";\n\n";
$dataRes = $mysqli->query("SELECT * FROM `$table`");
if ($dataRes->num_rows > 0) {
$sqlContent .= "-- Dumping data for table `$table`\n";
while ($row = $dataRes->fetch_assoc()) {
$columns = array_map(fn($col) => '`' . $mysqli->real_escape_string($col) . '`', array_keys($row));
$values = array_map(function ($val) use ($mysqli) {
return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'";
}, array_values($row));
$sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n";
}
$sqlContent .= "\n";
}
}
$sqlContent .= "SET foreign_key_checks = 1;\n";
file_put_contents($sqlFile, $sqlContent);
// === 2. Create uploads.zip ===
function zipFolder($folderPath, $zipFilePath) {
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
die("Cannot open <$zipFilePath>");
}
$folderPath = realpath($folderPath);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($folderPath),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($folderPath) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
zipFolder("uploads", $uploadsZip);
// === 3. Generate version.txt ===
$commitHash = trim(shell_exec('git log -1 --format=%H'));
$gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD'));
$versionContent = "ITFlow Backup Metadata\n";
$versionContent .= "-----------------------------\n";
$versionContent .= "Generated: " . date('Y-m-d H:i:s') . "\n";
$versionContent .= "Backup File: $baseName.zip\n";
$versionContent .= "Generated By: $session_name\n";
$versionContent .= "Host: " . gethostname() . "\n";
$versionContent .= "Git Branch: $gitBranch\n";
$versionContent .= "Git Commit: $commitHash\n";
$versionContent .= "ITFlow Version: " . (defined('APP_VERSION') ? APP_VERSION : 'Unknown') . "\n";
$versionContent .= "Database Version: " . (defined('CURRENT_DATABASE_VERSION') ? CURRENT_DATABASE_VERSION : 'Unknown') . "\n";
file_put_contents($versionFile, $versionContent);
// === 4. Combine into final .zip file ===
$final = new ZipArchive();
if ($final->open($finalZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
die("Cannot create final backup zip.");
}
$final->addFile($sqlFile, "db.sql");
$final->addFile($uploadsZip, "uploads.zip");
$final->addFile($versionFile, "version.txt");
$final->close();
// Cleanup temp files before download
unlink($sqlFile);
unlink($uploadsZip);
unlink($versionFile);
// === 5. Serve the zip for download ===
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $finalZip . '"');
header('Content-Length: ' . filesize($finalZip));
flush();
readfile($finalZip);
unlink($finalZip); // remove final zip after serving
logAction("System", "Backup Download", "$session_name downloaded full backup.");
$_SESSION['alert_message'] = "Full backup downloaded.";
exit;
}
if (isset($_POST['backup_master_key'])) {
validateCSRFToken($_POST['csrf_token']);
$password = $_POST['password'];
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE user_id = $session_user_id");
$row = mysqli_fetch_array($sql);
if (password_verify($password, $row['user_password'])) {
$site_encryption_master_key = decryptUserSpecificKey($row['user_specific_encryption_ciphertext'], $password);
// Logging
logAction("Master Key", "Download", "$session_name retrieved the master encryption key");
// App Notify
appNotify("Master Key", "$session_name retrieved the master encryption key");
echo "==============================";
echo "<br>Master encryption key:<br>";
echo "<b>$site_encryption_master_key</b>";
echo "<br>==============================";
} else {
// Log the failure
logAction("Master Key", "Download", "$session_name attempted to retrieve the master encryption key but failed");
$_SESSION['alert_type'] = "error";
$_SESSION['alert_message'] = "Incorrect password.";
header("Location: " . $_SERVER["HTTP_REFERER"]);
}
}