fase 1 - elaborazione qwen
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
include('../../../inc/includes.php');
|
||||
|
||||
header("Content-Type: application/octet-stream");
|
||||
header("Pragma: no-cache");
|
||||
Html::header_nocache();
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id || !\PluginNetconfig\Config::canView()) {
|
||||
http_response_code(403);
|
||||
exit('Access denied');
|
||||
}
|
||||
|
||||
$config = new \PluginNetconfig\Config();
|
||||
if (!$config->getFromDB($id)) {
|
||||
http_response_code(404);
|
||||
exit('Configuration not found');
|
||||
}
|
||||
|
||||
$content = $config->getDecryptedContent();
|
||||
|
||||
// Opzionale: mascheramento credenziali
|
||||
$content = preg_replace('/(?<=password\s|secret\s|enable\s)[^\s\r\n]+/i', '***REDACTED***', $content);
|
||||
|
||||
header('Content-Disposition: attachment; filename="netconfig_' . $id . '_' . date('Ymd_His') . '.txt"');
|
||||
echo $content;
|
||||
exit;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "glpi-plugin/netconfig",
|
||||
"description": "Backup e versioning configurazioni di rete per GLPI 11",
|
||||
"type": "glpi-plugin",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/diff": "^6.4",
|
||||
"guzzlehttp/guzzle": "^7.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PluginNetconfig\\": "src/",
|
||||
"PluginNetconfig\\Api\\": "src/Api/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use DBConnection;
|
||||
|
||||
function plugin_netconfig_install() {
|
||||
$db = DBConnection::getReadConnection();
|
||||
$sql = file_get_contents(__DIR__ . '/install/mysql/install.sql');
|
||||
$db->doQuery($sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
function plugin_netconfig_uninstall() {
|
||||
$db = DBConnection::getReadConnection();
|
||||
$db->doQuery("DROP TABLE IF EXISTS `glpi_plugin_netconfig_configs`");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
define('PLUGIN_NETCONFIG_VERSION', '1.0.0');
|
||||
define('PLUGIN_NETCONFIG_NAME', 'netconfig');
|
||||
|
||||
function plugin_init_netconfig() {
|
||||
global $PLUGIN_HOOKS;
|
||||
|
||||
$PLUGIN_HOOKS['csrf_compliant']['netconfig'] = true;
|
||||
$PLUGIN_HOOKS['compatible']['netconfig'] = ['glpi' => '>=11.0'];
|
||||
|
||||
// Integrazione menu GLPI
|
||||
$PLUGIN_HOOKS['menu_toadd']['netconfig'] = [
|
||||
'tools' => \PluginNetconfig\Config::class,
|
||||
];
|
||||
|
||||
// Registrazione endpoint API per l'agent
|
||||
$PLUGIN_HOOKS['add_api_endpoint']['netconfig'] = [
|
||||
'/plugin/netconfig/agent' => 'PluginNetconfig\Agent\Receive',
|
||||
];
|
||||
|
||||
// Attivazione massive actions
|
||||
$PLUGIN_HOOKS['use_massive_actions']['netconfig'] = true;
|
||||
|
||||
// Registrazione automatica dei diritti profilo
|
||||
if (class_exists('\PluginNetconfig\Config')) {
|
||||
$PLUGIN_HOOKS['add_javascript']['netconfig'][] = 'netconfig.js'; // Opzionale
|
||||
}
|
||||
}
|
||||
|
||||
function plugin_version_netconfig() {
|
||||
return [
|
||||
'name' => 'Network Config Backup',
|
||||
'version' => PLUGIN_NETCONFIG_VERSION,
|
||||
'author' => 'Your Name / Team',
|
||||
'license' => 'GPL-3.0-or-later',
|
||||
'homepage' => '',
|
||||
'requirements' => [
|
||||
'glpi' => ['min' => '11.0.0', 'max' => '11.99.99'],
|
||||
'php' => ['min' => '8.2.0'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function plugin_install_netconfig() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function plugin_uninstall_netconfig() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace PluginNetconfig\Agent\Task;
|
||||
|
||||
use PluginNetconfig\Config;
|
||||
use Glpi\Toolbox\Encryption;
|
||||
use Session;
|
||||
|
||||
class Backup
|
||||
{
|
||||
public static function handleReceived(array $input): bool
|
||||
{
|
||||
if (!Config::canCreate()) return false;
|
||||
|
||||
$hash = hash('sha256', $input['config']);
|
||||
$latest = Config::getLastByDevice($input['device_id']);
|
||||
|
||||
if ($latest && $latest->fields['config_hash'] === $hash) {
|
||||
return true; // Nessuna modifica
|
||||
}
|
||||
|
||||
$config = new Config();
|
||||
$data = [
|
||||
'networkdevices_id' => $input['device_id'],
|
||||
'config_content' => Encryption::encrypt($input['config']),
|
||||
'config_hash' => $hash,
|
||||
'users_id' => Session::getLoginUserID() ?: 0, // 0 = agent/system
|
||||
'created_at' => $_SESSION['glpi_currenttime']
|
||||
];
|
||||
return $config->add($data) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PluginNetconfig;
|
||||
|
||||
use CommonDBTM;
|
||||
use CommonGLPI;
|
||||
use Session;
|
||||
use MassiveAction;
|
||||
use Glpi\Toolbox\Encryption;
|
||||
use Symfony\Component\Diff\Differ;
|
||||
use Symfony\Component\Diff\Output\HtmlOutput;
|
||||
use Glpi\Templating\Twig\TemplateRenderer;
|
||||
|
||||
class Config extends CommonDBTM
|
||||
{
|
||||
static $rightname = 'plugin_netconfig';
|
||||
|
||||
public static function getTypeName($nb = 0): string
|
||||
{
|
||||
return __('Network Configurations', 'netconfig');
|
||||
}
|
||||
|
||||
public static function getRights($interface = 'central'): array
|
||||
{
|
||||
return [
|
||||
READ => __('Read'),
|
||||
UPDATE => __('Update'),
|
||||
CREATE => __('Create'),
|
||||
PURGE => __('Delete permanently'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function canView(): bool { return Session::haveRight(self::$rightname, READ); }
|
||||
public static function canCreate(): bool { return Session::haveRight(self::$rightname, CREATE); }
|
||||
public static function canUpdate(): bool { return Session::haveRight(self::$rightname, UPDATE); }
|
||||
public static function canPurge(): bool { return Session::haveRight(self::$rightname, PURGE); }
|
||||
|
||||
public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string
|
||||
{
|
||||
if ($item->getType() === 'NetworkEquipment' && self::canView()) {
|
||||
return __('Config History', 'netconfig') . ' (' . countElementsInTable(self::getTable(), ['networkdevices_id' => $item->getID()]) . ')';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0): bool
|
||||
{
|
||||
if ($item->getType() !== 'NetworkEquipment') return true;
|
||||
|
||||
$configs = (new self())->find(
|
||||
['networkdevices_id' => $item->getID()],
|
||||
'created_at DESC'
|
||||
);
|
||||
|
||||
$twig = new TemplateRenderer();
|
||||
echo $twig->render('@netconfig/config_tab.html.twig', [
|
||||
'configs' => $configs,
|
||||
'can_export' => self::canView(),
|
||||
'can_create' => self::canCreate()
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getLastConfigByDevice(int $networkdevices_id): ?array
|
||||
{
|
||||
$db = DBConnection::getReadConnection();
|
||||
$stmt = $db->prepare("SELECT * FROM " . self::getTable() . " WHERE networkdevices_id = ? ORDER BY created_at DESC LIMIT 1");
|
||||
$stmt->bind_param('i', $networkdevices_id);
|
||||
$stmt->execute();
|
||||
return $stmt->get_result()->fetch_assoc() ?: null;
|
||||
}
|
||||
|
||||
public static function saveConfig(array $data): bool
|
||||
{
|
||||
$config = new self();
|
||||
$hash = hash('sha256', $data['content']);
|
||||
$last = self::getLastConfigByDevice($data['networkdevices_id']);
|
||||
|
||||
if ($last && $last['config_hash'] === $hash) {
|
||||
return true; // Nessuna modifica rilevata
|
||||
}
|
||||
|
||||
$input = [
|
||||
'networkdevices_id' => $data['networkdevices_id'],
|
||||
'config_content' => Encryption::encrypt($data['content']),
|
||||
'config_hash' => $hash,
|
||||
'users_id' => $data['users_id'] ?? Session::getLoginUserID() ?: 0,
|
||||
'is_encrypted' => 1,
|
||||
];
|
||||
|
||||
return $config->add($input) !== false;
|
||||
}
|
||||
|
||||
public function getDecryptedContent(): string
|
||||
{
|
||||
return $this->fields['is_encrypted']
|
||||
? Encryption::decrypt($this->fields['config_content'])
|
||||
: $this->fields['config_content'];
|
||||
}
|
||||
|
||||
public function getDiffFromPrevious(): string
|
||||
{
|
||||
$last = self::getLastConfigByDevice($this->fields['networkdevices_id']);
|
||||
if (!$last || $last['id'] === $this->fields['id']) return '';
|
||||
|
||||
$prev = new self();
|
||||
$prev->getFromDB($last['id']);
|
||||
$differ = new Differ(new HtmlOutput());
|
||||
return $differ->diff($prev->getDecryptedContent(), $this->getDecryptedContent());
|
||||
}
|
||||
|
||||
// MASSIVE ACTIONS
|
||||
public function getSpecificMassiveActions($checkitem = null): array
|
||||
{
|
||||
$actions = parent::getSpecificMassiveActions($checkitem);
|
||||
if (self::canCreate()) {
|
||||
$actions[self::class . ':BackupNow'] = __('Force backup now', 'netconfig');
|
||||
}
|
||||
if (self::canView()) {
|
||||
$actions[self::class . ':ExportSelected'] = __('Export selected', 'netconfig');
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
public static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids): void
|
||||
{
|
||||
switch ($ma->getAction()) {
|
||||
case self::class . ':BackupNow':
|
||||
foreach ($ids as $id) {
|
||||
// Qui puoi integrare coda RabbitMQ, webhook o chiamata diretta all'agent
|
||||
$ma->itemDone($item::getType(), $id, MassiveAction::ACTION_OK);
|
||||
}
|
||||
break;
|
||||
case self::class . ':ExportSelected':
|
||||
foreach ($ids as $id) {
|
||||
// Redirect to export script
|
||||
$ma->itemDone($item::getType(), $id, MassiveAction::ACTION_OK);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user