Files
urbackup/MEMORY.md
T
2026-08-31 08:20:18 +02:00

41 KiB
Raw Blame History

MEMORY.md - Stato del Plugin UrBackup

Ultima modifica: 31/08/2026

31/08/2026 — Fix campi API username/password non editabili in prod (solo UI)

  • Sintomo utente: installato il plugin sul server produttivo, nel form Server non si vede il campo API username e il campo API password è bloccato (asterischi fissi non editabili), anche per l'utente che ha installato il plugin.
  • Causa: divergenza nel controllo diritti. I front usavano Profile::canCurrentUser() (legge i diritti da glpi_profilerights su DB), ma Server::showFormFields() (form server) usava Session::haveRight(self::$rightname, UPDATE) per calcolare $canEdit (e idem rawSearchOptions() riga 308 e showUnlinkedClientsTab()/showMissingClientsTab() righe 1155/1222). Session::haveRight dipende dalla cache dei diritti in sessione ($_SESSION['glpiactiveprofile']['rights']), popolata al login. Dopo l'installazione/aggiornamento del plugin i diritti sono scritti in DB ma la sessione corrente non viene ricostruita, quindi haveRight resta false → ramo read-only: username stampato come testo (invisibile se vuoto), password ****** fissa.
  • Fix: sostituito Session::haveRight(self::$rightname, ...) con Profile::canCurrentUser(...) in Server.php (righe ~308, ~447-449, ~1155, ~1222) e AssetTab.php (riga ~446, tab Asset), coerente col resto del plugin, legge dal DB e aggira la cache di sessione stantia.
  • Nessun bump versione (nessuna modifica DB, regola 6); nessuna nuova stringa i18n.
  • Verifica IA: php -l OK; git diff autorevisione OK. Verifica UI utente OBBLIGATORIA: aggiornare il file su prod, poi testare il form Server (API username editabile + API password campo input con placeholder).

25/08/2026 — Blocco "This asset hosts the UrBackup server" in evidenza (solo UI)

  • Richiesta utente: mettere in evidenza con riquadro colorato il blocco host + nome del server UrBackup.
  • Modifiche: AssetTab.php::showHostServerBlock() — classi dedicate plugin-urbackup-host-card / -card-header sul card Bootstrap e plugin-urbackup-host-server-link sui link server; public/css/urbackup.css — card con bordo 2px #1e6091, header blu #1e6091 testo bianco, body #eaf3fa, link server bold 1.05rem.
  • Nessun bump versione (nessuna modifica DB, regola 6); nessuna nuova stringa i18n.
  • Verifiche IA: php -l OK; git diff autorevisione OK. Verifica visiva browser: CONFERMATA dall'utente il 25/08/2026 ("ok funziona tutto").

07/08/2026 — Fix dropdown "Hardware host" (0.7.3): elemento non compariva

  • Sintomo utente: scelto il tipo host (es. Computer) nel form server, il dropdown degli elementi non compariva.
  • Causa 1 (405): Ajax::updateItemOnSelectEvent genera $("#target").load(url, {params}) — jQuery .load() con data oggetto invia POST, mentre front/dropdown_host.ajax.php era GET-only (405) → div mai popolato. (Nota: il CSRF non era il blocco per .load(): il global $(document).ajaxSend in public/js/common.js aggiunge X-Glpi-Csrf-Token a OGNI POST AJAX, letto da getAjaxCsrfToken() sul <meta property="glpi:csrf_token">.)
  • Causa 2 (500): Session::getMatchingActiveEntities() richiede esattamente 1 argomento (è un filtro di entity, @since 10.0.13) → usare Session::getActiveEntities() (Session.php:2194).
  • Causa 3 (parametro sbagliato): in Dropdown::show il parametro per limitare le entità è entity (default -1, riga 133), NON entity_restrict (che è solo l'output serializzato del config, riga 287).
  • Causa 4 (scriptBlock NON emesso): Html::scriptBlock() in GLPI 11 ritorna la stringa (return sprintf(...), Html.php:5145) — serve echo Html::scriptBlock(...); senza echo lo script sparisce silenziosamente.
  • Fix: JS inline nel form via echo Html::scriptBlock(...): $(document).on('change', '#dropdown_host_itemtype$rand', ...)$.get(PLUGIN_URBACKUP_WEB_DIR.'/front/dropdown_host.ajax.php', {itemtype, value:0}).html() del div urbackup_host_items$rand (GET = bodyless → nessun check CSRF; jQuery esegue gli script inline iniettati, incluso il $(function(){}) del config select2). Rimosso use Ajax; (non più usato).
  • Lezione GLPI 11 — i dropdown sono LAZY: Dropdown::show() NON renderizza più le <option>: genera un componente Html::jsAjaxDropdown (select2) con params JSON + _idor_token; i valori arrivano via POST a /ajax/getDropdownValue.php (setupAjaxDropdown in public/js/common.js: type:"POST" + ajaxSend globale per il CSRF). Il markup contiene solo l'option vuota finché l'utente non apre/cerca.
  • Lezione IDOR: Session::getNewIDORToken NON ruota (accumula token validi 2h, cleanIDORTokens); validateIDOR confronta le chiavi salvate (itemtype, entity_restrict, displaywith, condition) con la POST. Il config JS serializzato da jQuery manda entity_restrict con la stessa forma salvata nel token → match. Non è possibile testare via curl la chiamata a getDropdownValue (la serializzazione jQuery degli array []chiave= diverge da -d @file.json → validateIDOR false) → test e2e in CLI: generare il dropdown con Dropdown::show reale, estrarre il config dal markup, ricostruire la POST → validateIDOR: true (provato con /tmp/opencode/idor_e2e.php; il "No active session" dopo è limite del bootstrap CLI, non del flusso).
  • Stato: php -l OK; form contiene lo script $.get (verificato su server.form.php?id=1: 200); endpoint GET 200 con select+config+IDOR; validateIDOR true via /tmp/opencode/idor_e2e.php; verifica visiva in browser = utente (regola 7).
  • Richiesta utente: collegare il "UrBackup Server" del plugin con il Computer / custom Asset dell'inventario che lo ospita (analogo a computer↔monitor) per trovare l'hardware fisico su cui gira il server.
  • Design (decisioni utente): (1) itemtype host = SOLO tipi con capacità attiva (Config::getEnabledItemtypes()); (2) blocco "Questo asset ospita il server UrBackup" SEMPRE visibile sull'asset host, anche se l'asset è anche client; (3) niente search option in questo giro (MVP).
  • Modifica DB: glpi_plugin_urbackup_servers + host_itemtype VARCHAR(255) DEFAULT NULL (after comment) + host_items_id INT UNSIGNED NOT NULL DEFAULT 0 (after host_itemtype) + KEY host_asset (host_itemtype,host_items_id) — NIENTE unique (più server per host ammessi, es. VM). Migrazione idempotente plugin_urbackup_install_update_servers_table() in install.php + empty.sql. glpi_plugin_urbackup_serverassets NON toccata (semantica CLIENT). → bump 0.7.2 → 0.7.3 (regola 6).
  • Server.php: riga "Hardware host" in showFormFields() dopo la riga Comments (Dropdown::showItemTypes('host_itemtype', Config::getEnabledItemtypes(), ...) + echo Html::scriptBlock(...) con $.get su dropdown_host.ajax.php al change del select tipo — vedi sezione Fix + div urbackup_host_items$rand pre-popolato con Dropdown::show con entity => Session::getActiveEntities() + link all'host); validazione in prepareInputForUpdate() (itemtype vuoto/items_id ≤0/class mancante/!isItemtypeEnabled/item inesistente → azzera entrambi); getHostAsset(): ?CommonDBTM; getServersHostingAsset(string, int): array con guard $DB->fieldExists(self::getTable(),'host_itemtype') (robustezza pre-migrazione — pattern di Config::getEnableComputer).
  • front/dropdown_host.ajax.php (NUOVO): GET only, Session::checkLoginUser(), validazione class_exists + Config::isItemtypeEnabled, risponde Dropdown::show($itemtype, ['name'=>'host_items_id','value'=>..., 'entity'=>Session::getActiveEntities(), 'display_emptychoice'=>true]).
  • AssetTab.php: showHostServerBlock(CommonDBTM $item) chiamata per prima in displayTabContentForItem() — card "This asset hosts the UrBackup server" con lista link (Server::getFormURLWithID) dai server che puntano all'asset; sempre visibile se c'è un host.
  • Locales: +2 msgid per lingua (182→184): Hardware host (it "Host hardware", de "Hardware-Host", en "Hardware host"), This asset hosts the UrBackup server (it "Questo asset ospita il server UrBackup", de "Dieses Asset hostet den UrBackup-Server", en identica); header .po → 0.7.3; .mo ricompilati (msgfmt --check OK, warning header pre-esistenti) + cache traduzioni svuotata (files/_cache/*/translations/).
  • Verifiche IA: php -l OK (setup.php, install/install.php, Server.php, AssetTab.php, dropdown_host.ajax.php); test CLI /tmp/opencode/urbackup_host_test.php 10/10 PASS (prepareInputForUpdate: host non inviato/items_id=0/itemtype vuoto/Monitor non abilitato/Computer valido/items_id inesistente; getHostAsset null; getServersHostingAsset SKIP in attesa migrazione).
  • Stato attuale: glpi_plugins.version=0.7.3, state=1 (ACTIVE — update UI eseguito dall'utente il 07/08/2026, poi fix dropdown). Verifica visiva checklist 0.7.3 CONFERMATA dall'utente il 25/08/2026 ("ok funziona tutto") — feature 0.7.3 CHIUSA.
  • Checklist UI utente 0.7.3 (verifica finale): (1) Admin → Server → aprire un server → "Hardware host": tipo → elemento (dropdown selezionabile) → Salva; (2) ricaricare il server: link host sotto la riga; (3) aprire l'asset host → card "Questo asset ospita il server UrBackup" con link al server; (4) verificare traduzioni it_IT/DE ("Host hardware", "Questo asset ospita il server UrBackup"); (5) smoke HTTP dropdown: GET front/dropdown_host.ajax.php?itemtype=Computer (sessione) → 200 select; itemtype=Monitor → rifiutato; senza sessione → 302/403.
  • Doc aggiornate: README.md Changelog 0.7.3; GLPIDEV.md (schema v0.7.3 + caveat 21 con le API caveat dropdown lazy/IDOR + nota api_password cifrata); MEMORY.md (questo file).

06/08/2026 — Linguette interne tab sempre su una riga (desktop)

  • Richiesta utente: le linguette "Stato Client / Azioni / Info-Log" risultavano a capo una sopra l'altra anche su monitor normale → fix CSS in public/css/urbackup.css (nessun bump: solo UI).
  • Indagine: markup già ul.nav.nav-tabs#urbackupTabs (row di default); test headless chromium con CSS reali (tabler.min.css + css_glpi.min.css + urbackup.css) e wrapper reale (card-tabs + #tabspanel + tab-content p-2 flex-grow-1 card) mostrava affiancate → la causa nel browser utente non è riproducibile (probabile flex non applicato/cache/zoom); fix deterministico con regola dedicata.
  • Fix: .plugin-urbackup-inner-tabs .nav-tabs { display:flex; flex-direction:row; flex-wrap:nowrap; overflow-x:auto } + .nav-item { display:inline-flex; flex:0 0 auto }; media query max-width: 575.98pxflex-wrap:wrap (comportamento mobile invariato, come richiesto dall'utente).
  • Verifica headless: 1366px → wrap=nowrap, x=8/129/214 stessa y; 480px → wrap=wrap.
  • Iterazione 2 (sintomo: linguette su una riga ma estese per tutta la pagina, 3 bottoni oltre lo schermo = width:100%/flex-grow esterni): override deterministico con !important su .nav-tabs (display/flex-direction/flex-wrap) + .nav-item e .nav-link (flex:0 0 auto !important, width:auto !important, max-width:none !important, white-space:nowrap) — nessuna regola esterna può più allargarli. Verifica headless: w=118/82/103, flex=0 0 auto, xs=8/129/214, nessun overflow.
  • ⚠️ Lezione test: una pagina GLPI scaricata via curl e riaperta come file:// NON applica i CSS (href relativi) → rendering con soli user-agent defaults (font "Times New Roman", ul block, li list-item, a inline) → test INVALIDO. Usare sempre pagine di test con href CSS assoluti (es. http://localhost/...).

06/08/2026 — RISOLTO: nuove chiavi i18n non tradotte nel browser (cache catalogo traduzioni GLPI)

  • Sintomo: "API status" e "Client State" (chiavi nuove) restavano in inglese nel browser; le chiavi pre-esistenti ("Stato client", "Stato UrBackup") erano tradotte — i .mo erano corretti (msgunfmt/msgfmt --check OK).
  • Causa radice: GLPI 11 Session::loadLanguage() (src/Session.php:816+) crea $TRANSLATE (anonima class che estende Laminas\I18n\Translator\Translator) con cache catalogo via I18nCache (src/Glpi/Cache/I18nCache.php) → CacheManager::getTranslationsCacheInstance() (CONTEXT_TRANSLATIONS) → FilesystemAdapter su files/_cache/<env-hash>/translations/ con TTL 0 = infinito e NIENTE invalidazione su mtime: dopo la ricompilazione dei .mo, il vecchio catalogo continua a essere servito finché la cache non viene svuotata. (Il path contiene l'hash della versione GLPI, es. 11.0.8-53eff695-production — cambia a ogni bump di GLPI.)
  • Fix: eliminare i file in files/_cache/<env-hash>/translations/ (12 file) → la cache si rigenera al primo caricamento con i .mo nuovi.
  • Regola operativa: dopo OGNI ricompilazione dei .mo del plugin, svuotare files/_cache/*/translations/ (non è un'azione UI — la regola 7 NON si applica; è manutenzione cache).
  • Verifica HTTP (login 06/08): GET /ajax/common.tabs.php?_glpi_tab=GlpiPlugin%5CUrbackup%5CAssetTab%241&_itemtype=Computer&id=1 (sessione glpi/glpi) → "Stato Client" (tab nav), "Stato client" (header sezione), "Stato API" + badge "Connessione API OK". Nota: il parametro è _itemtype (non _glpi_itemtype); il token CSRF di login in GLPI 11.0.8 sta in <meta property="glpi:csrf_token"> e i campi form sono login_name/login_password; il POST va a /front/login.php.

REGOLA PERMANENTE — Verifica UI da parte dell'utente

  • Tutte le azioni che l'utente normalmente esegue dalla UI di GLPI (update/attivazione plugin, toggle di configurazione, link/unlink asset, test connessione, azioni backup) DEVONO essere eseguite dall'utente per verificarne il funzionamento reale — l'IA NON le esegue al suo posto (né via console glpi:plugin:*, né via HTTP/curl con sessione).
  • Per i bump di versione (regola 6): l'IA consegna codice + migrazioni idempotenti + bump PLUGIN_URBACKUP_VERSION, poi l'utente esegue l'update dalla UI: il plugin viene marcato NOTUPDATED e deattivato → Configurazione → Plugin → pulsante "Aggiorna""Attiva" → verifica della feature con la checklist fornita dall'IA.
  • Verifiche che NON passano dalla UI (es. php -l, bootstrap CLI, query DB) restano compito dell'IA.
  • ⚠️ Contesto storico: l'update 0.7.1 e 0.7.2 fu eseguito dall'IA via console (glpi:plugin:install/activate) — comportamento NON più ammesso da questa regola (istituita il 05/08/2026).

REGOLA PERMANENTE — Gestione versione per modifiche DB (standard GLPI)

  • Da ora in poi: OGNI modifica che tocca il database (nuove tabelle/colonne/indici, migrazioni di dati, cambi di default) DEVE essere accompagnata da un incremento di PLUGIN_URBACKUP_VERSION in setup.php (regola 6 di AGENTS.md).
  • Meccanismo GLPI verificato (src/Plugin.php): checkPluginState() (~righe 909-933) confronta plugin_version_urbackup()['version'] con glpi_plugins.version; se diverse → state = NOTUPDATED e plugin DEATTIVATO ("update process has to be launched").
  • Procedura update: meccanismo standard GLPI php bin/console glpi:plugin:install urbackup (da /var/www/glpi) → plugin_urbackup_install() con migrazioni idempotenti (new Migration(PLUGIN_URBACKUP_VERSION)), poi php bin/console glpi:plugin:activate urbackup (l'install imposta NOTACTIVATED) — MA l'esecuzione è dell'utente via UI (regola 7): Configurazione → Plugin → "Aggiorna" → "Attiva". I comandi console restano solo riferimento documentale.
  • Aggiornare sempre anche: README.md (Changelog) e header Project-Id-Version dei .po (+ ricompilazione .mo + svuotamento cache traduzioni).

05/08/2026 — Tab asset: card "Stato UrBackup", rename "Client State", fix badge Internet mode

  • Bugfix (regressione fix XSS 05/08): showStateSection()$internetModeDisplay (HTML badge) finiva nell'array $rows e il loop generico lo passava da htmlspecialchars() → si vedeva il testo letterale <span class="badge bg-secondary">No</span>. Fix: riga dedicata per "Internet mode" fuori dal loop (badge raw, label escapata, contenuto escapato in costruzione) — pattern già usato per l'authkey; la protezione XSS sui valori API resta intatta. Audit: unico caso nel plugin (Server.php/Config.php echo direttamente i badge).
  • Opzione A — card Bootstrap in showServerLinkedBlock(): la tabella 2×4 "UrBackup status" è ora una card con header + 4 card (row-cols-1/2/4): Linked server (link + protocol://ip:port), API status (badge verde/rosso da last_api_status + messaggio + last_api_check), server version (dash se vuoto), Client name. Valori fw-bold, label text-uppercase text-muted small; nessun CSS custom, solo utility Bootstrap 5.
  • Rename tab interna: __('State', 'urbackup')__('Client State', 'urbackup') (it "Stato Client", de "Client-Status", en "Client State"); msgid orfana State rimossa dai 3 .po.
  • Locales: +2 msgid (Client State, API status), -1 (State) → 181 tradotti + header; .mo ricompilati (msgfmt --check OK). Nessun bump versione (solo UI, regola 6 non applicabile).

05/08/2026 — Bump 0.7.2 (toggle Computer configurabile + lista Asset custom)

  • Modifica DB: nuova riga enable_computer ('1') in glpi_plugin_urbackup_configs (migrazione idempotente plugin_urbackup_install_add_enable_computer_config() in install.php + INSERT in empty.sql) → bump PLUGIN_URBACKUP_VERSION 0.7.10.7.2 (regola 6 AGENTS.md).
  • Perché prima "Computer sempre abilitato": Config::isItemtypeEnabled() hardcoded return true per Computer + registerClass(AssetTab, addtabon Computer) incondizionato in setup.php + badge statico in showForm().
  • Config.php: nuovo getEnableComputer() (cache statica, guard TableExists, fallback true se riga/tabella assente), isItemtypeEnabled('Computer') e getEnabledItemtypes() ora rispettano il toggle; nuovo getEnabledAssetDefinitions() — lista Asset Definition con capacità UrBackup via hasCapacityEnabled() con istanza da getAvailableCapacities() (fallback: decode raw JSON capacitiesarray_column('name')).
  • setup.php: registerClass(AssetTab, addtabon Computer) solo se Config::getEnableComputer(); per le Asset Definition la registrazione tab resta in UrBackupCapacity::onClassBootstrap() (non toccata).
  • front/config.form.php: reintrodotto handler POST update (CSRF gestito dal listener globale GLPI 11 — niente Session::checkCSRF()): upsert enable_computer, Session::addMessageAfterRedirect, redirect a Config::getFormURL(); form con Dropdown::showYesNo + Html::hidden('_glpi_csrf_token', ...) + Html::submit(__('Save')).
  • Nuova sezione config page: "Custom assets with "Urbackup" capacity enabled" (it: Asset custom con Capacità "Urbackup" attivata, de: Benutzerdefinierte Assets mit aktivierter Kapazität "Urbackup") — colonne Name / System name / Active (badge Yes/No); su DB locale: definizioni Server e NAS con capacità attiva.
  • Comportamento a Computer disattivato: tab nascosto (doppia protezione: registrazione condizionale + gate in AssetTab::getTabNameForItem/displayTabContentForItem), MassiveActions bloccate (hook.php:47), front/asset.form.php rifiuta, unlinked/missing clients escludono Computer; i link esistenti in glpi_plugin_urbackup_serverassets restano intatti (solo visibilità UI).
  • Locales: +5 msgid (180 tradotti + header), header Project-Id-Version → 0.7.2, .mo ricompilati con msgfmt --check (OK per it/de/en).

05/08/2026 — Bump 0.7.1 (migrazione DB: cifratura api_password)

  • Modifica DB della sessione (nuova migrazione plugin_urbackup_install_encrypt_api_passwords()) → bump PLUGIN_URBACKUP_VERSION 0.7.00.7.1.
  • Eseguita la procedura standard: glpi:plugin:install urbackup + glpi:plugin:activate urbackup; verificato glpi_plugins.version = 0.7.1, state = ACTIVE, pagine front funzionanti.
  • README.md Changelog 0.7.1 aggiunto; header .po → 0.7.1 con .mo ricompilati.

05/08/2026 — Cifratura api_password, ottimizzazioni, localizzazioni e correzione claim ambiente

1. Claim "flussi API non testabili in locale" ERRATO — verificato

  • urbackupsrv (pid ~1282) è attivo su 0.0.0.0:55414; endpoint /x?a=salt e login 2-fasi verificati funzionanti (admin/12345678): PBKDF2 hash calcolato e "success":true.
  • La frase "nessun server UrBackup reale raggiungibile in locale" era copiata dalle docs senza riverifica → corretta in MEMORY.md, AGENTS.md, GLPIDEV.md e urbackup-api-actions.md.
  • urbackup-api-actions.md aggiornato: login 2-fasi riuscito, rimosso riferimento a changeClientSetting() (dead code rimosso).

2. Cifratura api_password con GLPIKey (punto ⚠️ 1 risolto)

  • API core corretta: (new GLPIKey())->encrypt()/decrypt()GLPIKey::getInstance() NON esiste in GLPI 11.0.8 (pattern usato dal core: APIRest.php:623, ClientRepository.php:90).
  • src/Server.php: nuovo getApiPassword() (decrypt on-the-fly con fallback legacy in chiaro) + isApiPasswordEncrypted() (rileva formato base64+nonce ≥ 24B, evita warning trigger_error di decrypt() su valori legacy).
  • prepareInputForUpdate(): campo vuoto → unset (mantiene password, fix di un wipe involontario della password ad ogni save); campo valorizzato → cifrato con (new GLPIKey())->encrypt().
  • src/UrbackupApiClient.php: __construct() usa $server->getApiPassword() quando $server instanceof Server.
  • install/install.php: nuova migrazione idempotente plugin_urbackup_install_encrypt_api_passwords() che cifra le password legacy in chiaro (salta valori già cifrati; in caso di chiave non disponibile lascia il valore invariato per non perdere dati).
  • Nota: le righe già in chiaro rimangono usabili finché non avviene il save o l'install/update del plugin (fallback in getApiPassword()).

3. Test connessione API solo a parametri cambiati (punto ⚠️ 2 risolto)

  • prepareInputForUpdate() ora esegue testConnection() SOLO se cambiano ip_address|port|protocol|api_username|api_password|ignore_ssl; salvataggi "neutri" non innescano più richieste API da 30s.

4. Loop opcache solo in development (punto ⚠️ 3 risolto)

  • setup.php: loop opcache_invalidate() attivo solo se defined('GLPI_ENVIRONMENT_TYPE') && GLPI_ENVIRONMENT_TYPE === 'development' (enum Glpi\Application\Environment, valori production|staging|testing|e2e_testing|development; default produzione).

5. Fix N+1 in showUnlinkedClientsTab() (punto ⚠️ 4 risolto)

  • Batch loading nomi asset: 1 query per itemtype (map itemtype-id), sostituisce ServerAsset::getAssetName() per riga (2 query/riga prima).

6. Localizzazioni it_IT / de_DE / en_GB aggiornate

  • I 3 .po erano fermi alla v0.4.0 (69 msgid vs 176 chiavi nel codice). Aggiunte 115 msgid mancanti con traduzioni complete (it/de/en), rimosse 9 orfane (es. "Checking...", "Root location ID", "Server unreachable", "UrBackup rights"), header Project-Id-Version → 0.7.0.
  • .mo ricompilati con msgfmt --check (tutti "175 messaggi tradotti").
  • Audit hardcoded: nessuna stringa utente fuori da __()/_n()/_x() (solo tag HTML nei raw echo); _x('button', 'Save') nel Twig gestito correttamente.

Verifiche

  • php -l OK su tutti i file toccati (Server.php, UrbackupApiClient.php, setup.php, install/install.php).
  • Roundtrip cifratura/decifratura verificato via bootstrap GLPI CLI.
  • Smoke test HTTP post-modifica: pagine server/config caricate senza errori.

Da fare residuo

  • (nessuno dei 4 punti ⚠️ precedenti) — i test REALI su server semi-produttivo restano consigliati, ma i flussi API sono ora verificabili anche in locale (localhost:55414).

05/08/2026 — Verifica codice, rimozione codice morto e audit sicurezza

Codice morto rimosso (verificato con grep su tutto il progetto)

  1. src/Server.php: rimossi testApiConnection() + isNetworkError() (nessun chiamante; l'endpoint front/server_test.ajax.php implementa il test inline), getAssetGroupName() (mai chiamato), import use CommonGLPI; inutilizzato.
  2. src/UrbackupApiClient.php: rimossi changeClientSetting() (duplicato di updateClientSettings(), mai chiamato) e extractSettingValue() (duplicato della copia privata di AssetTab, mai chiamato).
  3. src/Config.php: rimossi ensureDefaultConfiguration() e saveConfiguration() (no-op con commento "kept for backward compatibility" ma senza alcun chiamante utile) + import inutilizzati DBmysql, Html.
  4. install/install.php: rimossa funzione plugin_urbackup_install_update_assettypes_table() (mai chiamata da install_process()); rimosso blocco hasCapacity()/enableCapacity() con API inesistente in GLPI 11.0.8 (caveat 18, ora documentato nel codice: l'enable passa dal form Capacities UI); rimossa chiamata Config::ensureDefaultConfiguration(); rimossi import AssetDefinitionManager, UrBackupCapacity.
  5. front/asset.form.php: rimossi handler POST start_file_backup/start_image_backup (nessun form del plugin li emette; i bottoni usano execute + urbackup_action).
  6. front/config.form.php: rimosso handler POST update (il form non esiste più; Config::saveConfiguration() rimosso); aggiunto bootstrap include_once inc/includes.php mancante; rimosso global $CFG_GLPI; inutilizzato.
  7. public/js/urbackup.js: FILE ELIMINATO (cercava #plugin-urbackup-api-status e .plugin-urbackup-test-api, elementi mai renderizzati da nessun file PHP/Twig) + rimosso hook ADD_JAVASCRIPT in setup.php.
  8. hook.php: rimossi require_once install/install.php e install/uninstall.php (caricati ad ogni richiesta inutilmente; il lifecycle li carica da setup.php).
  9. src/MassiveAction.php: rimosso use Session; inutilizzato; corrette due parentesi di chiusura con indentazione errata.
  10. src/AssetTab.php: rimossi parametri inutilizzati $server/$link da showStateSection()/showActionsSection() e $link da showInternalTabs() (privati).

Fix di sicurezza

  1. XSS (stored) — AssetTab::showStateSection(): $displayValue (valori status/settings dall'API UrBackup, potenzialmente ostili) era stampato SENZA htmlspecialchars() → ora escapato (htmlspecialchars((string) $displayValue)).
  2. front/server_test.ajax.php:
    • rimosso $AJAX_INCLUDE = 1; (no-op in GLPI 11, innesca deprecation);
    • aggiunto bootstrap include_once inc/includes.php;
    • POST-only (prima accettava GET con scrittura DB → state change via GET, CSRF-friendly);
    • diritto richiesto per la scrittura DB: UPDATE (prima READ poteva sovrascrivere last_api_status/message/check);
    • import espliciti use al posto dei FQCN inline.
  3. Password API non più esposta nel page source (Server::showFormFields): campo api_password ora con value='' + placeholder ****** + hint "leave empty to keep current"; prepareInputForUpdate() mantiene la password esistente quando il campo è vuoto. (Risolto il 05/08/2026: api_password ora cifrata con GLPIKey — vedi sezione aggiornata.)
  4. Validazione input server: protocol whitelistato http|https (prima qualsiasi stringa finiva in getWebInterfaceUrl() → href e URL cURL) e port clampato 1..65535 in prepareInputForAdd/prepareInputForUpdate.
  5. front/asset.form.php disconnect: condizione diritti allineata a ServerAsset::disconnectAsset() (solo UPDATE; prima UPDATE|DELETE incoerente).
  6. front/server.form.php: $_GET['id'] castato a int.

Verifiche

  • php -l OK su tutti i file PHP del plugin.
  • Smoke test HTTP (ambiente locale, senza auth): server_test.ajax.php GET→302 (login), POST→403 (denied), config.form.php e server.php → 200 senza fatal error. (Nota: la verifica del 05/08/2026 ha confermato che il server UrBackup locale è attivo su localhost:55414 e il login API 2-fasi funziona — il claim "flussi API non testabili in locale" era errato, vedi sezione aggiornata.)

Da fare (non bloccanti, segnalati nel report) — TUTTI RISOLTI il 05/08/2026

  • api_password in chiaro su glpi_plugin_urbackup_servers → cifrare con GLPIKey → fatto: (new GLPIKey())->encrypt() in prepareInputForAdd/Update + migrazione plugin_urbackup_install_encrypt_api_passwords() + getApiPassword() con fallback legacy.
  • setup.php top-level: loop opcache_invalidate() ad OGNI richiesta web → fatto: solo se GLPI_ENVIRONMENT_TYPE === 'development'.
  • Server::prepareInputForUpdate(): test connessione API ad ogni save → fatto: solo se i parametri di connessione cambiano.
  • Server::showUnlinkedClientsTab(): N+1 su ServerAsset::getAssetName() → fatto: batch loading per itemtype.

04/08/2026 — Documentazione AI: AGENTS.md / SKILL.md / GLPIDEV.md specifici per urbackup

Generati i 3 file operativi del progetto a partire dai modelli *_netbackup (copie dei file del plugin netbackup presenti in cartella), adattandoli alla realtà verificata del codice:

File prodotti

  1. AGENTS.md (sovrascritto — prima era identico a AGENTS.md_netbackup): ruolo Senior GLPI Plugin Architect, regole assolute (Git rule con autorizzazione esplicita, strict types PHP 8.3/8.4, namespace GlpiPlugin\Urbackup\), CSRF GLPI 11 (CheckCsrfListener globale, niente Session::checkCSRF() nei front), limiti ambiente locale (server UrBackup locale verificato attivo: localhost:55414), struttura v0.7.0 e hook essenziali.
  2. SKILL.md (nuovo): competenze UrBackup Web API (login salt/PBKDF2, azioni, struct setting, versioni ≥ 2.4), Capacity system GLPI 11, location-aware matching, network resilience, security zero-trust.
  3. GLPIDEV.md (nuovo): reference API GLPI 11.0.8 verificata sul core /var/www/glpi — DB layer, tabelle plugin, Migration, CSRF, Capacity system, UrBackupApiClient (tabella azioni), LocationHelper/batch loading, 18 caveat verificati.

Fatti ri-verificati durante la stesura (da codice reale)

  • $DB->runFile() è deprecato ma usato SOLO per schema iniziale in install/install.php (mai in upgrade/uninstall).
  • Html::hidden() in GLPI 11 usa la firma (name, options) — pattern Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]).
  • Hook plugin_urbackup_MassiveActions($type) riceve l'itemtype come stringa.
  • api_password in chiaro: limite noto → risolto il 05/08/2026 con cifratura (new GLPIKey())->encrypt()/decrypt() (non esiste GLPIKey::getInstance()).

Server UrBackup per test in locale (VERIFICATO ATTIVO)

  • URL: http://localhost:55414
  • Credenziali: utente admin, password 12345678
  • Scopo: eseguire API semplici (login, status, backup) in locale per debugging e validazione rapida.
  • Stato: processo urbackupsrv attivo (porta 55414), endpoint /x?a=salt e login 2-fasi (PBKDF2) verificati funzionanti il 05/08/2026.
  • Risultati test API: documentato in /var/www/glpi/plugins/urbackup/urbackup-api-actions.md.

⚠️ BUG latente trovato: hasCapacity()/enableCapacity() inesistenti in GLPI 11.0.8

  • install/install.php:367-368 chiama $definition->hasCapacity(UrBackupCapacity::class) e $definition->enableCapacity(UrBackupCapacity::class) — in GLPI 11.0.8 su AssetDefinition esistono SOLO hasCapacityEnabled(CapacityInterface $capacity) (oggetto, non stringa), getEnabledCapacities(), getCapacityConfiguration() (src/Glpi/Asset/AssetDefinition.php:627-652).
  • Impatto attuale: la chiamata è in try/catch → durante l'install si mostra il messaggio "Error enabling UrBackup capacity on definitions: ..." ma la migrazione continua; le capacità NON vengono auto-abilitate.
  • Impatto funzionale nullo oggi perché Config::isItemtypeEnabled() ritorna true per tutte le sottoclassi di Glpi\Asset\Asset (tab sempre visibile).
  • TODO: correggere con l'API core corretta: check con hasCapacityEnabled(new UrBackupCapacity()) (richiede un oggetto CapacityInterface); l'abilitazione/disabilitazione in GLPI 11.0.8 avviene via input capacities del form (array di spec) processato in AssetDefinition::post_updateItem() (AssetDefinition.php:316-440) — NON esistono metodi pubblici enableCapacity/saveCapacity/disableCapacity. Documentato in GLPIDEV.md §2.6 e caveat 18. → NOTA: rimosso come dead code il 05/08/2026 (il blocco non è più in install.php).

GLPI 11 — Session::checkCSRF() breaking change

In GLPI 11, Session::checkCSRF() richiede il primo argomento $data (i dati POST da validare). In GLPI ≤10 accettava zero argomenti. Il listener globale CheckCsrfListener gestisce già il CSRF per tutte le richieste POST, ma il plugin chiamava Session::checkCSRF() senza argomenti causando errore fatale.

Fix applicati (tutti i file)

  • RIMOSSE tutte le chiamate esplicite Session::checkCSRF() dai 4 file front — GLPI 11 le gestisce già globalmente via CheckCsrfListener
  • Il listener globale consuma il token CSRF, quindi una seconda chiamata dal plugin fallisce perché il token non è più valido
  • public/js/urbackup.js — aggiunto header X-Glpi-Csrf-Token con getAjaxCsrfToken() per le richieste AJAX (NOTA 05/08/2026: urbackup.js è stato ELIMINATO come dead code — il pattern CSRF AJAX resta documentato per riferimento)

Performance - Asset Definition vs Computer

Problema

Gli Asset Definition custom (GLPI 11) sono 2-5x più lenti dei Computer nativi nel caricamento del tab UrBackup.

Causa

L'overhead è in GLPI 11 core, non nel plugin:

  1. Asset::__construct() — itera 30+ Capacity classi per ogni istanza
  2. Asset::post_getFromDB() — decodifica JSON custom fields e li processa
  3. eval() autoloading — le classi concrete sono definite via eval() a runtime

Ottimizzazioni applicate

  1. AssetTab.php::loadApiData() — letto $item->fields['name'] direttamente invece di chiamare ServerAsset::getAssetName() che faceva una seconda getFromDB() ridondante
  2. AssetTab.php::startBackup(), saveInternetMode(), saveDefaultDirs(), showServerLinkedBlock() — stesso pattern, $item->fields['name'] al posto di getAssetName()
  3. Server.php::showMissingClientsTab() — batch loading IP e gruppi: batchLoadIps() (1 query per itemtype vs 1 per riga) + batchLoadGroups() (1 query per itemtype vs 1 per riga)

Architettura

  • src/Capacity/UrBackupCapacity.php — registra AssetTab via CommonGLPI::registerStandardTab() in onClassBootstrap()
  • setup.php — registra capacità, CSS, hook
  • src/AssetTab.php — display tab content + tab interni (Stato/Azioni/Info-Log) + host server block
  • src/ServerAsset.php — gestione collegamenti asset-server
  • src/Config.php — itemtype enabled check
  • src/UrbackupApiClient.php — client API con caching in-memory (per istanza) e sessione
  • src/LocationHelper.php — risoluzione location radice
  • src/Server.php — CRUD server, tab missing clients, form (incl. host asset)

Asset Tab Interni

  • 3 sub-tab: Stato, Azioni (solo UPDATE/CREATE), Info/Log
  • CSS: tab con tonalità di grigio differenti (scuro/medio/chiaro)
  • Caricamento dati API con caching sessione 30s
  • Dati caricati: status, settings, authkey, backup recenti (10), log (50)

Cache

  • UrbackupApiClient: cache in-memory per getStatus() e getClientSettings()
  • AssetTab::loadApiData(): cache sessione 30s (chiave: server_id + client_name)
  • API timeout: 30s, connect timeout: 5s

Bugfix: Campi API username/password non visibili in nuova installazione

Problema

In una nuova installazione del plugin, i campi "API username" e "API password" non venivano renderizzati come input — si vedeva solo la label ma non il campo editabile.

Cause (2 bug distinti)

  1. src/Server.php:436$canUpdate controllava solo Session::haveRight(self::$rightname, UPDATE). In fase di creazione di un nuovo server, l'utente ha diritto CREATE ma non necessariamente UPDATE, quindi il campo non veniva mostrato.
  2. src/Profile.php:73-77installRights() usava $_SESSION['glpiactiveprofile']['id'] per assegnare i diritti completi al profilo corrente. In installazione via CLI (php bin/console glpi:plugin:install), non c'è sessione, quindi $profiles_id = 0 e nessun profilo riceveva i diritti completi.

Fix applicati

  1. Server.php::showFormFields() — Sostituito $canUpdate con $canEdit: per nuovi server ($ID <= 0) usa CREATE, per server esistenti usa UPDATE.
  2. Profile.php::installRights() — In assenza di sessione attiva (CLI), cerca il profilo "Super-Admin" tramite query diretta e gli assegna tutti i diritti.

Bugfix: Unknown column 'users_id' — Group::getDataItems() SQL error

Problema

Su server produttivo, Group::showItems() generava un errore MySQL 1054 (Unknown column 'users_id') quando GLPI iterava sugli itemtype registrati con linkgroup_types => true. La query includeva:

WHERE ((... ) OR (`users_id` IN (SELECT `users_id` FROM `glpi_groups_users` WHERE `groups_id` IN ('7483'))))

La colonna users_id non esisteva nella tabella glpi_plugin_urbackup_servers.

Causa

Server è registrato in setup.php:57 con linkgroup_types => true, il che fa sì che GLPI lo includa nelle query di Group::getDataItems(). Il metodo aggiunge automaticamente una condizione users_id IN (...) assumendo che ogni itemtype abbia tale colonna.

Fix

  1. install/mysql/plugin_urbackup-empty.sql — Aggiunta colonna users_id INT UNSIGNED NOT NULL DEFAULT 0 dopo locations_id + indice
  2. install/install.php — Aggiunto $migration->addField('users_id', ...) e $migration->addKey('users_id') nella migrazione del server
  3. src/Server.php — Aggiunto search option id=8 per User::getTable() + auto-set di users_id da $_SESSION['glpiID'] in prepareInputForAdd()

Versione

  • 0.7.3
  1. Colonne host_itemtype/host_items_id + KEY host_asset su glpi_plugin_urbackup_servers (migrazione idempotente + empty.sql) → bump 0.7.2 → 0.7.3 (regola 6).
  2. Form server: riga "Hardware host" con dropdown tipo (Dropdown::showItemTypes) + dropdown elementi AJAX ($.get su front/dropdown_host.ajax.php, vedi sezione Fix del 07/08/2026).
  3. AssetTab: blocco "This asset hosts the UrBackup server" (sempre visibile, anche se client).
  4. Validazione in prepareInputForUpdate() + helper getHostAsset() / getServersHostingAsset() (guard fieldExists).

0.7.2 — Toggle Computer configurabile + lista Asset custom con capacità attiva

  1. enable_computer in glpi_plugin_urbackup_configs (default '1') letto da Config::getEnableComputer(); registrazione tab su Computer condizionale in setup.php; isItemtypeEnabled/getEnabledItemtypes rispettano il toggle.
  2. Config page: dropdown Sì/No per Computer + lista Asset Definition con capacità UrBackup attivata (getEnabledAssetDefinitions()).
  3. front/config.form.php: handler POST update ripristinato (upsert, redirect, messaggio).
  4. DB: migrazione idempotente + INSERT in empty.sql → bump 0.7.2 (regola 6).

0.7.1 — Cifratura api_password (GLPIKey), ottimizzazioni e localizzazioni

  1. api_password cifrata con (new GLPIKey())->encrypt() on save (Server::prepareInputForAdd/Update), Server::getApiPassword() con fallback legacy, migrazione idempotente plugin_urbackup_install_encrypt_api_passwords() (install.php) — applicata al DB (server id=1).
  2. Test connessione API solo a parametri cambiati in prepareInputForUpdate() (niente più chiamate da 30s ad ogni save).
  3. Loop opcache_invalidate() solo in development (GLPI_ENVIRONMENT_TYPE === 'development').
  4. Fix N+1 in Server::showUnlinkedClientsTab() (batch loading per itemtype).
  5. Localizzazioni: 115 nuove traduzioni it/de/en, 9 orfane rimosse, .mo ricompilati.
  6. Docs: claim "nessun server locale" corretto (server reale verificato attivo + login 2-fasi OK), GLPIKey::getInstance()new GLPIKey() in AGENTS/SKILL/GLPIDEV, urbackup-api-actions.md aggiornato.

0.7.0 — Pulizia, sicurezza e DB cleanup

  1. CSRF hardening: Session::checkCSRF() aggiunto su asset.form.php, server.form.php, server_test.ajax.php, config.form.php (NOTA: poi RIMOSSO il 05/08/2026 — in GLPI 11 il listener globale lo gestisce)
  2. File deprecati rimossi: 12 file (composer copy, AGENTS_OLD.MD, js/, ajax/, front/test/view, FIX_PERMISSIONS.sh, removed/)
  3. Dead code rimosso: 3 Controller, 1 Command, 4 template Twig
  4. DB migration: DROP glpi_plugin_urbackup_profiles e glpi_plugin_urbackup_assettypes; add index location_active su servers; add date_creation/date_mod su serverassets
  5. SQL schema pulito: Rimosse tabelle legacy dall'empty.sql
  6. PHP lint warnings: Rimosse use Html; e use Search; superflue
  7. README.md creato
  8. PURGE right: Aggiunto PURGE ai diritti di installazione (Profile.php:76,90); applicato a tutti i profili Super-Admin esistenti nel DB
  9. CSRF GLPI 11 fix: Session::checkCSRF() richiede $_POST come argomento in GLPI 11; aggiunto X-Glpi-Csrf-Token header al JS AJAX
  10. Session::isDebugActive(): Metodo inesistente in GLPI 11; sostituito in AssetTab.php:150 con ($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE