mirror of
https://github.com/devcode-it/openstamanager.git
synced 2025-06-05 22:09:38 +02:00
feat: Cifratura backup tramite password
This commit is contained in:
@@ -1 +0,0 @@
|
||||
Deny from all
|
||||
@@ -28,9 +28,6 @@ $db_options = [
|
||||
// 'sort_buffer_size' => '2M',
|
||||
];
|
||||
|
||||
// Percorso della cartella di backup
|
||||
$backup_dir = __DIR__.'/backup/';
|
||||
|
||||
// Tema selezionato per il front-end
|
||||
$theme = 'default';
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ switch (filter('op')) {
|
||||
if ($result) {
|
||||
flash()->info(tr('Nuovo backup creato correttamente!'));
|
||||
} else {
|
||||
$backup_dir = Backup::getDirectory();
|
||||
flash()->error(tr('Errore durante la creazione del backup!').' '.str_replace('_DIR_', '"'.$backup_dir.'"', tr('Verifica che la cartella _DIR_ abbia i permessi di scrittura!')));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
@@ -83,7 +84,7 @@ switch (filter('op')) {
|
||||
$number = intval($number);
|
||||
|
||||
$backups = Backup::getList();
|
||||
$backup = $backups[$number] ?: $backup_dir;
|
||||
$backup = $backups[$number] ?: Backup::getDirectory();
|
||||
|
||||
echo Util\FileSystem::size($backup);
|
||||
|
||||
@@ -111,7 +112,10 @@ if (filter('op') == 'restore') {
|
||||
}
|
||||
|
||||
try {
|
||||
$result = Backup::restore($path, is_file($path));
|
||||
// Ottieni la password per i backup esterni se impostata
|
||||
$password = setting('Password backup esterni');
|
||||
|
||||
$result = Backup::restore($path, is_file($path), $password);
|
||||
$database->beginTransaction();
|
||||
|
||||
if ($result) {
|
||||
|
||||
162
src/Backup.php
162
src/Backup.php
@@ -45,9 +45,46 @@ class Backup
|
||||
*/
|
||||
public static function getDirectory()
|
||||
{
|
||||
$result = App::getConfig()['backup_dir'];
|
||||
// Ottieni l'adattatore di archiviazione selezionato
|
||||
$adapter = self::getStorageAdapter();
|
||||
$backup_dir = base_dir().'/backups';
|
||||
|
||||
$result = rtrim((string) $result, '/');
|
||||
// Estrai la directory dalle opzioni dell'adattatore
|
||||
if (!empty($adapter)) {
|
||||
$options = $adapter->options;
|
||||
// Se options è una stringa JSON, decodificala
|
||||
if (is_string($options)) {
|
||||
// Prova a decodificare il JSON normalmente
|
||||
$decoded = json_decode($options, true);
|
||||
|
||||
// Se la decodifica fallisce, prova a gestire il caso specifico con $ nella password
|
||||
if ($decoded === null && strpos($options, 'password') !== false) {
|
||||
// Estrai manualmente il valore di root usando espressioni regolari
|
||||
if (preg_match('/"root":"([^"]+)"/', $options, $matches)) {
|
||||
$backup_dir = $matches[1];
|
||||
}
|
||||
} else {
|
||||
$options = $decoded ?: [];
|
||||
|
||||
// Verifica se esiste la chiave 'directory' o 'root'
|
||||
if (!empty($options)) {
|
||||
if (isset($options['directory'])) {
|
||||
$backup_dir = base_dir().$options['directory'];
|
||||
} elseif (isset($options['root'])) {
|
||||
$backup_dir = $options['root'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback al percorso di configurazione se disponibile
|
||||
if (empty($backup_dir)) {
|
||||
$config = App::getConfig();
|
||||
$backup_dir = isset($config['backup_dir']) ? $config['backup_dir'] : base_dir().'/backups';
|
||||
}
|
||||
|
||||
$result = rtrim((string) $backup_dir, '/');
|
||||
if (!directory($result) || !is_writable($result)) {
|
||||
// throw new UnexpectedValueException();
|
||||
}
|
||||
@@ -177,6 +214,7 @@ class Backup
|
||||
'tmp',
|
||||
'.git',
|
||||
'.github',
|
||||
'.config', // Aggiungi la directory .config per evitare errori di permesso
|
||||
],
|
||||
];
|
||||
|
||||
@@ -187,20 +225,79 @@ class Backup
|
||||
$ignores['dirs'][] = basename($backup_dir);
|
||||
}
|
||||
|
||||
// Nome del file di backup
|
||||
$backup_filename = $backup_name.'.zip';
|
||||
$backup_path = $backup_dir.'/'.$backup_filename;
|
||||
|
||||
// Creazione backup in formato ZIP
|
||||
if (extension_loaded('zip')) {
|
||||
$result = Zip::create([
|
||||
base_dir(),
|
||||
self::getDatabaseDirectory(),
|
||||
], $backup_dir.'/'.$backup_name.'.zip', $ignores);
|
||||
}
|
||||
// Verifica se è impostata una password per il backup
|
||||
$password = setting('Password di protezione backup');
|
||||
|
||||
// Se è impostata una password e ZipArchive è disponibile, crea un backup protetto da password
|
||||
if (!empty($password) && class_exists('ZipArchive')) {
|
||||
// Crea un percorso temporaneo per il backup
|
||||
$temp_path = $backup_path . '.tmp';
|
||||
|
||||
// Crea prima un backup normale
|
||||
$result = Zip::create([
|
||||
base_dir(),
|
||||
self::getDatabaseDirectory(),
|
||||
], $temp_path, $ignores);
|
||||
|
||||
if ($result) {
|
||||
// Crea un nuovo ZIP protetto da password
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($backup_path, \ZipArchive::CREATE) === true) {
|
||||
// Apri il file ZIP originale in lettura
|
||||
$original_zip = new \ZipArchive();
|
||||
if ($original_zip->open($temp_path) === true) {
|
||||
// Imposta la password per il nuovo ZIP
|
||||
$zip->setPassword($password);
|
||||
|
||||
// Copia tutti i file dal backup originale al nuovo backup protetto da password
|
||||
for ($i = 0; $i < $original_zip->numFiles; $i++) {
|
||||
$stat = $original_zip->statIndex($i);
|
||||
$file_content = $original_zip->getFromIndex($i);
|
||||
|
||||
// Aggiungi il file al nuovo ZIP
|
||||
$zip->addFromString($stat['name'], $file_content);
|
||||
|
||||
// Cripta il file con AES-256
|
||||
$zip->setEncryptionIndex($i, \ZipArchive::EM_AES_256, $password);
|
||||
}
|
||||
|
||||
// Chiudi i file ZIP
|
||||
$original_zip->close();
|
||||
$zip->close();
|
||||
|
||||
// Rimuovi il file temporaneo
|
||||
unlink($temp_path);
|
||||
} else {
|
||||
// Fallback al metodo normale
|
||||
$zip->close();
|
||||
unlink($backup_path);
|
||||
rename($temp_path, $backup_path);
|
||||
}
|
||||
} else {
|
||||
// Fallback al metodo normale
|
||||
rename($temp_path, $backup_path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Crea un backup normale senza password
|
||||
$result = Zip::create([
|
||||
base_dir(),
|
||||
self::getDatabaseDirectory(),
|
||||
], $backup_path, $ignores);
|
||||
}
|
||||
}
|
||||
// Creazione backup attraverso la copia dei file
|
||||
else {
|
||||
$result = copyr([
|
||||
base_dir(),
|
||||
self::getDatabaseDirectory(),
|
||||
], $backup_dir.'/'.$backup_name.'.zip', $ignores);
|
||||
], $backup_path, $ignores);
|
||||
}
|
||||
|
||||
// Rimozione cartella temporanea
|
||||
@@ -248,11 +345,37 @@ class Backup
|
||||
* Ripristina un backup esistente.
|
||||
*
|
||||
* @param string $path
|
||||
* @param bool $cleanup
|
||||
* @param string|null $password Password per decriptare il backup (se criptato)
|
||||
*/
|
||||
public static function restore($path, $cleanup = true)
|
||||
public static function restore($path, $cleanup = true, $password = null)
|
||||
{
|
||||
$database = database();
|
||||
$extraction_dir = is_dir($path) ? $path : Zip::extract($path);
|
||||
|
||||
// Se il backup non è una directory e è stata fornita una password, prova a estrarlo con la password
|
||||
if (!is_dir($path) && !empty($password) && class_exists('ZipArchive')) {
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($path) === true) {
|
||||
// Imposta la password per l'estrazione
|
||||
$zip->setPassword($password);
|
||||
|
||||
// Estrai il backup in una directory temporanea
|
||||
$extraction_dir = sys_get_temp_dir().'/'.basename($path, '.zip');
|
||||
if (!directory($extraction_dir)) {
|
||||
mkdir($extraction_dir, 0777, true);
|
||||
}
|
||||
|
||||
// Estrai tutti i file
|
||||
$zip->extractTo($extraction_dir);
|
||||
$zip->close();
|
||||
} else {
|
||||
// Se non è possibile aprire il file ZIP con la password, prova a estrarlo normalmente
|
||||
$extraction_dir = Zip::extract($path);
|
||||
}
|
||||
} else {
|
||||
// Estrai il backup normalmente
|
||||
$extraction_dir = is_dir($path) ? $path : Zip::extract($path);
|
||||
}
|
||||
|
||||
// TODO: Forzo il log out di tutti gli utenti e ne impedisco il login
|
||||
// fino a ripristino ultimato
|
||||
@@ -340,6 +463,8 @@ class Backup
|
||||
return slashes($result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Restituisce l'elenco delle variabili da sostituire normalizzato per l'utilizzo.
|
||||
*/
|
||||
@@ -348,6 +473,23 @@ class Backup
|
||||
return Generator::getReplaces();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restituisce l'adattatore di archiviazione da utilizzare per i backup.
|
||||
*
|
||||
* @return \Modules\FileAdapters\FileAdapter
|
||||
*/
|
||||
public static function getStorageAdapter()
|
||||
{
|
||||
$adapter_id = setting('Adattatore archiviazione backup');
|
||||
|
||||
// Se non è stato selezionato un adattatore, utilizzo quello predefinito
|
||||
if (empty($adapter_id)) {
|
||||
return \Modules\FileAdapters\FileAdapter::getDefaultConnector();
|
||||
}
|
||||
|
||||
return \Modules\FileAdapters\FileAdapter::find($adapter_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restituisce il nome previsto per il backup successivo.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<?php
|
||||
|
||||
// Spostamento backup
|
||||
$directory = 'backup/';
|
||||
$files = glob($directory.'*.{zip}', GLOB_BRACE);
|
||||
$new_folder = 'files/backups/';
|
||||
directory($new_folder);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = basename($file);
|
||||
rename($file, $new_folder.$filename);
|
||||
}
|
||||
|
||||
// File e cartelle deprecate
|
||||
$files = [
|
||||
'templates/bilancio/settings.php',
|
||||
@@ -12,10 +23,11 @@ $files = [
|
||||
'templates/preventivi/settings.php',
|
||||
'templates/prima_nota/settings.php',
|
||||
'templates/scadenzario/settings.php',
|
||||
'backup/',
|
||||
];
|
||||
|
||||
foreach ($files as $key => $value) {
|
||||
$files[$key] = realpath(base_dir().'/'.$value);
|
||||
}
|
||||
|
||||
delete($files);
|
||||
delete($files);
|
||||
@@ -6,15 +6,15 @@ INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`) VALUES (1, (SEL
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`) VALUES (1, (SELECT id FROM zz_settings WHERE `nome`='Api key ibanapi.com'), 'Api key ibanapi.com');
|
||||
|
||||
-- Aggiunta impostazione per OpenRouter API Key
|
||||
INSERT INTO `zz_settings` (`id`, `nome`, `valore`, `tipo`, `editable`, `sezione`, `order`) VALUES
|
||||
INSERT INTO `zz_settings` (`id`, `nome`, `valore`, `tipo`, `editable`, `sezione`, `order`) VALUES
|
||||
(NULL, 'OpenRouter API Key', '', 'string', 1, 'API', NULL);
|
||||
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
(1, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'OpenRouter API Key'),
|
||||
'OpenRouter API Key',
|
||||
'API Key per l''integrazione con OpenRouter AI. Ottieni la tua chiave da https://openrouter.ai/keys');
|
||||
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
(2, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'OpenRouter API Key'),
|
||||
'OpenRouter API Key',
|
||||
'API Key for OpenRouter AI integration. Get your key from https://openrouter.ai/keys');
|
||||
@@ -54,23 +54,23 @@ INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
INSERT INTO `zz_modules` (`name`, `directory`, `options`, `options2`, `icon`, `version`, `compatibility`, `order`, `parent`, `default`, `enabled`, `use_notes`, `use_checklists`) VALUES ('Descrizioni predefinite', 'descrizioni_predefinite', 'SELECT |select| FROM `zz_default_description` WHERE 1=1 HAVING 2=2', '', 'fa fa-circle-o', '2.8', '2.8', '8', (SELECT `id` FROM `zz_modules` AS `t` WHERE `name` = 'Tabelle'), '1', '1', '1', '1');
|
||||
|
||||
SELECT @id_module := `id` FROM `zz_modules` WHERE `name` = 'Descrizioni predefinite';
|
||||
INSERT INTO `zz_modules_lang` (`id_lang`, `id_record`, `title`, `meta_title`) VALUES
|
||||
INSERT INTO `zz_modules_lang` (`id_lang`, `id_record`, `title`, `meta_title`) VALUES
|
||||
('1', @id_module, 'Descrizioni predefinite', 'Descrizioni predefinite'),
|
||||
('2', @id_module, 'Descrizioni predefinite', 'Descrizioni predefinite');
|
||||
|
||||
SELECT @id_module := `id` FROM `zz_modules` WHERE `name` = 'Descrizioni predefinite';
|
||||
INSERT INTO `zz_views` (`id_module`, `name`, `query`, `order`, `search`, `slow`, `format`, `html_format`, `search_inside`, `order_by`, `visible`, `summable`, `avg`, `default`) VALUES
|
||||
INSERT INTO `zz_views` (`id_module`, `name`, `query`, `order`, `search`, `slow`, `format`, `html_format`, `search_inside`, `order_by`, `visible`, `summable`, `avg`, `default`) VALUES
|
||||
(@id_module, 'Descrizione', '`zz_default_description`.`descrizione`', '3', '1', '0', '0', '0', NULL, NULL, '1', '0', '0', '1'),
|
||||
(@id_module, 'Nome', 'zz_default_description.name', '2', '1', '0', '0', '0', NULL, NULL, '1', '0', '0', '1'),
|
||||
(@id_module, 'id', '`zz_default_description`.`id`', '1', '0', '0', '0', '0', NULL, NULL, '0', '0', '0', '1');
|
||||
(@id_module, 'id', '`zz_default_description`.`id`', '1', '0', '0', '0', '0', NULL, NULL, '0', '0', '0', '1');
|
||||
|
||||
SELECT @id_module := `id` FROM `zz_modules` WHERE `name` = 'Descrizioni predefinite';
|
||||
INSERT INTO `zz_views_lang` (`id_lang`, `id_record`, `title`) VALUES
|
||||
('1', (SELECT `id` FROM `zz_views` WHERE `name` = 'Descrizione' AND `id_module` = @id_module), 'Descrizione'),
|
||||
('2', (SELECT `id` FROM `zz_views` WHERE `name` = 'Descrizione' AND `id_module` = @id_module), 'Description'),
|
||||
INSERT INTO `zz_views_lang` (`id_lang`, `id_record`, `title`) VALUES
|
||||
('1', (SELECT `id` FROM `zz_views` WHERE `name` = 'Descrizione' AND `id_module` = @id_module), 'Descrizione'),
|
||||
('2', (SELECT `id` FROM `zz_views` WHERE `name` = 'Descrizione' AND `id_module` = @id_module), 'Description'),
|
||||
('1', (SELECT `id` FROM `zz_views` WHERE `name` = 'Nome' AND `id_module` = @id_module), 'Nome'),
|
||||
('2', (SELECT `id` FROM `zz_views` WHERE `name` = 'Nome' AND `id_module` = @id_module), 'Name'),
|
||||
('1', (SELECT `id` FROM `zz_views` WHERE `name` = 'id' AND `id_module` = @id_module), 'id'),
|
||||
('2', (SELECT `id` FROM `zz_views` WHERE `name` = 'Nome' AND `id_module` = @id_module), 'Name'),
|
||||
('1', (SELECT `id` FROM `zz_views` WHERE `name` = 'id' AND `id_module` = @id_module), 'id'),
|
||||
('2', (SELECT `id` FROM `zz_views` WHERE `name` = 'id' AND `id_module` = @id_module), 'id');
|
||||
|
||||
CREATE TABLE `zz_default_description` (`id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `descrizione` TEXT NOT NULL , `note` TEXT NOT NULL , PRIMARY KEY (`id`));
|
||||
@@ -92,7 +92,7 @@ INSERT INTO `zz_views_lang` (`id_lang`, `id_record`, `title`) VALUES
|
||||
-- Miglioria plugin Assicurazione crediti
|
||||
UPDATE `zz_plugins` SET `options` = '{ \"main_query\": [ { \"type\": \"table\", \"fields\": \"Fido assicurato, Data inizio, Data fine, Totale, Residuo\", \"query\": \"SELECT id, DATE_FORMAT(data_inizio,\'%d/%m/%Y\') AS \'Data inizio\', DATE_FORMAT(data_fine,\'%d/%m/%Y\') AS \'Data fine\', ROUND(fido_assicurato, 2) AS \'Fido assicurato\', ROUND(totale, 2) AS Totale, ROUND(fido_assicurato - totale, 2) AS Residuo, IF((fido_assicurato - totale) < 0, \'#f4af1b\', \'#4dc347\') AS _bg_ FROM an_assicurazione_crediti WHERE 1=1 AND id_anagrafica = |id_parent| HAVING 2=2 ORDER BY an_assicurazione_crediti.id DESC\"} ]}' WHERE `zz_plugins`.`name` = 'Assicurazione crediti';
|
||||
|
||||
ALTER TABLE `my_impianti` ADD `note` VARCHAR(255) NULL AFTER `descrizione`;
|
||||
ALTER TABLE `my_impianti` ADD `note` VARCHAR(255) NULL AFTER `descrizione`;
|
||||
|
||||
-- Aggiunta colonne Marchio e Modello nella vista Articoli (nascoste di default)
|
||||
SELECT @id_module := `id` FROM `zz_modules` WHERE `name` = 'Articoli';
|
||||
@@ -105,4 +105,25 @@ INSERT INTO `zz_views_lang` (`id_lang`, `id_record`, `title`) VALUES
|
||||
(1, @id-1, 'Marchio'),
|
||||
(2, @id-1, 'Brand'),
|
||||
(1, @id, 'Modello'),
|
||||
(2, @id, 'Model');
|
||||
(2, @id, 'Model');
|
||||
|
||||
INSERT INTO `zz_storage_adapters` (`name`, `class`, `options`, `can_delete`, `is_default`, `is_local`) VALUES
|
||||
('Backup', '\\Modules\\FileAdapters\\Adapters\\LocalAdapter', '{ \"directory\":\"/files/backups\" }', 1, 0, 1);
|
||||
|
||||
ALTER TABLE `zz_settings` CHANGE `is_user_setting` `is_user_setting` TINYINT(1) NOT NULL DEFAULT '0';
|
||||
|
||||
-- Aggiunta impostazione per l'adattatore di archiviazione per i backup
|
||||
INSERT INTO `zz_settings` (`id`, `nome`, `valore`, `tipo`, `editable`, `sezione`, `order`) VALUES
|
||||
(NULL, 'Adattatore archiviazione backup', (SELECT `id` FROM `zz_storage_adapters` WHERE name = 'Backup'), 'query=SELECT `id`, `name` AS descrizione FROM `zz_storage_adapters` WHERE `deleted_at` IS NULL ORDER BY `name`', 1, 'Backup', NULL);
|
||||
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
(1, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'Adattatore archiviazione backup'), 'Adattatore archiviazione backup', 'Adattatore di archiviazione da utilizzare per i backup'),
|
||||
(2, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'Adattatore archiviazione backup'), 'Backup storage adapter', 'Storage adapter to use for backups');
|
||||
|
||||
-- Aggiunta impostazione per la password dei backup esterni
|
||||
INSERT INTO `zz_settings` (`id`, `nome`, `valore`, `tipo`, `editable`, `sezione`, `order`) VALUES
|
||||
(NULL, 'Password di protezione backup', '', 'password', 1, 'Backup', NULL);
|
||||
|
||||
INSERT INTO `zz_settings_lang` (`id_lang`, `id_record`, `title`, `help`) VALUES
|
||||
(1, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'Password di protezione backup'), 'Password di protezione backup', 'Password da utilizzare per proteggere i backup in formato zip'),
|
||||
(2, (SELECT `id` FROM `zz_settings` WHERE `nome` = 'Password di protezione backup'), 'Backup protection password', 'Password to use for protecting zip backups');
|
||||
|
||||
Reference in New Issue
Block a user