Files
urbackup/MEMORY.md
T
2026-08-31 10:10:30 +02:00

43 KiB
Raw Blame History

MEMORY.md - Stato del Plugin UrBackup

Ultima modifica: 31/08/2026

31/08/2026 — Audit statico: fix 7 bug (solo UI/codice, nessuna modifica DB)

Audit statico (IA) + confronto contro sorgente C++ del backend UrBackup e core GLPI 11. Nessun bug di classe critico (crash/XSS/SQLi), ma diversi output errati corretti.

  1. Bug 1 — Livello log sempre vuoto (AssetTab.php:728): l'API livelog restituisce loglevel (int 1=ERROR,2=WARNING,3=INFO,4=DEBUG), il codice leggeva level/severity (mai presenti). Aggiunto formatLogLevel() che mappa loglevel→stringa.
  2. Bug 2 — "No client logs available" fuorviante (AssetTab.php:733): se client_found=false il client non è stato trovato per nome e i log NON vengono mai estratti. Ora mostra avviso "Client not found on UrBackup server. Check that the asset name matches the client name." Il matching resta per NOME client (confermato dall'utente come corretto).
  3. Bug 3 — JOIN IP invertito (Server.php batchLoadIps ~1516 e getAssetIp ~1614): JOIN glpi_ipaddresses↔glpi_networknames generava nn.items_id = ipa.id; corretto a nn.id = ipa.items_id (con ipa.itemtype='NetworkName'), coerente con core glpi/src/Report.php. Colonna IP di Linked/Missing ora corretta.
  4. Bug 4 — Chiave cache sessione non univoca (AssetTab.php:374): $cache_key includeva solo server id+nome client; ora urbackup_data_{serverid}_{itemtype}_{items_id}, evita cross-contaminazione tra asset omonimi.
  5. Bug 5 — Query Missing/Unlinked non filtrate per server (Server.php:1047 e :1245): aggiunto WHERE plugin_urbackup_servers_id = server corrente in showUnlinkedClientsTab() e showMissingClientsTab(), così asset collegati ad altri server non interferiscono.
  6. Bug 6 — server_test.ajax.php auth + CSRF (front/server_test.ajax.php): sostituito Profile::canCurrentUser(UPDATE) (non entity-aware) con $server->check($id, UPDATE) in try/catch → 403 JSON; creato public/js/urbackup.js (registrato via Hooks::ADD_JAVASCRIPT in setup.php) che legge meta glpi:csrf_token e invia header X-Glpi-Csrf-Token su ogni POST AJAX del plugin (pre-requisito del listener GLPI 11 CheckCsrfListener).
  7. Bug 7 — dropdown_host.ajax.php info-disclosure (front/dropdown_host.ajax.php): aggiunto check Profile::canCurrentUser(READ).
  8. Traduzioni: aggiunta la nuova stringa "Client not found on UrBackup server. Check that the asset name matches the client name." a it_IT/de_DE/en_GB .po, ricompilati i .mo (msgfmt). Versione header coerente (0.7.3). Changelog README aggiornato.
  • Verifica IA: php -l OK su tutti i file; git diff autorevisione OK; Hooks::ADD_JAVASCRIPT verificato in src/Glpi/Plugin/Hooks.php:60; meta glpi:csrf_token verificato in templates/layout/parts/head.html.twig:68. Verifica UI utente OBBLIGATORIA (vedi checklist).

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