fase 1 - elaborazione qwen

This commit is contained in:
mariano
2026-05-22 13:37:55 +02:00
parent 44a318b6f0
commit ba2293102d
6 changed files with 294 additions and 0 deletions
+28
View File
@@ -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;
+24
View File
@@ -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
}
}
}
+17
View File
@@ -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;
}
+52
View File
@@ -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;
}
+30
View File
@@ -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
View File
@@ -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;
}
}
}