43 KiB
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.
- Bug 1 — Livello log sempre vuoto (
AssetTab.php:728): l'APIlivelogrestituisceloglevel(int 1=ERROR,2=WARNING,3=INFO,4=DEBUG), il codice leggevalevel/severity(mai presenti). AggiuntoformatLogLevel()che mappaloglevel→stringa. - Bug 2 — "No client logs available" fuorviante (
AssetTab.php:733): seclient_found=falseil 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). - Bug 3 — JOIN IP invertito (
Server.phpbatchLoadIps~1516 egetAssetIp~1614): JOIN glpi_ipaddresses↔glpi_networknames generavann.items_id = ipa.id; corretto ann.id = ipa.items_id(conipa.itemtype='NetworkName'), coerente con coreglpi/src/Report.php. Colonna IP di Linked/Missing ora corretta. - Bug 4 — Chiave cache sessione non univoca (
AssetTab.php:374):$cache_keyincludeva solo server id+nome client; oraurbackup_data_{serverid}_{itemtype}_{items_id}, evita cross-contaminazione tra asset omonimi. - Bug 5 — Query Missing/Unlinked non filtrate per server (
Server.php:1047e:1245): aggiuntoWHERE plugin_urbackup_servers_id = server correnteinshowUnlinkedClientsTab()eshowMissingClientsTab(), così asset collegati ad altri server non interferiscono. - Bug 6 — server_test.ajax.php auth + CSRF (
front/server_test.ajax.php): sostituitoProfile::canCurrentUser(UPDATE)(non entity-aware) con$server->check($id, UPDATE)in try/catch → 403 JSON; creatopublic/js/urbackup.js(registrato viaHooks::ADD_JAVASCRIPTin setup.php) che legge metaglpi:csrf_tokene invia headerX-Glpi-Csrf-Tokensu ogni POST AJAX del plugin (pre-requisito del listener GLPI 11CheckCsrfListener). - Bug 7 — dropdown_host.ajax.php info-disclosure (
front/dropdown_host.ajax.php): aggiunto checkProfile::canCurrentUser(READ). - 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 -lOK su tutti i file;git diffautorevisione OK;Hooks::ADD_JAVASCRIPTverificato insrc/Glpi/Plugin/Hooks.php:60; metaglpi:csrf_tokenverificato intemplates/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 daglpi_profilerightssu DB), maServer::showFormFields()(form server) usavaSession::haveRight(self::$rightname, UPDATE)per calcolare$canEdit(e idemrawSearchOptions()riga 308 eshowUnlinkedClientsTab()/showMissingClientsTab()righe 1155/1222).Session::haveRightdipende 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, quindihaveRightresta false → ramo read-only: username stampato come testo (invisibile se vuoto), password******fissa. - Fix: sostituito
Session::haveRight(self::$rightname, ...)conProfile::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 -lOK; 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 dedicateplugin-urbackup-host-card/-card-headersul card Bootstrap eplugin-urbackup-host-server-linksui 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 -lOK; 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::updateItemOnSelectEventgenera$("#target").load(url, {params})— jQuery.load()con data oggetto invia POST, mentrefront/dropdown_host.ajax.phpera GET-only (405) → div mai popolato. (Nota: il CSRF non era il blocco per.load(): il global$(document).ajaxSendin public/js/common.js aggiungeX-Glpi-Csrf-Tokena OGNI POST AJAX, letto dagetAjaxCsrfToken()sul<meta property="glpi:csrf_token">.) - Causa 2 (500):
Session::getMatchingActiveEntities()richiede esattamente 1 argomento (è un filtro di entity,@since 10.0.13) → usareSession::getActiveEntities()(Session.php:2194). - Causa 3 (parametro sbagliato): in
Dropdown::showil parametro per limitare le entità èentity(default -1, riga 133), NONentity_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) — serveecho 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 divurbackup_host_items$rand(GET = bodyless → nessun check CSRF; jQuery esegue gli script inline iniettati, incluso il$(function(){})del config select2). Rimossouse Ajax;(non più usato). - Lezione GLPI 11 — i dropdown sono LAZY:
Dropdown::show()NON renderizza più le<option>: genera un componenteHtml::jsAjaxDropdown(select2) conparamsJSON +_idor_token; i valori arrivano via POST a/ajax/getDropdownValue.php(setupAjaxDropdownin public/js/common.js:type:"POST"+ajaxSendglobale per il CSRF). Il markup contiene solo l'option vuota finché l'utente non apre/cerca. - Lezione IDOR:
Session::getNewIDORTokenNON ruota (accumula token validi 2h,cleanIDORTokens);validateIDORconfronta le chiavi salvate (itemtype, entity_restrict, displaywith, condition) con la POST. Il config JS serializzato da jQuery mandaentity_restrictcon 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 conDropdown::showreale, 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 -lOK; 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).
07/08/2026 — Bump 0.7.3 (link Server ↔ asset host hardware)
- 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) + KEYhost_asset(host_itemtype,host_items_id) — NIENTE unique (più server per host ammessi, es. VM). Migrazione idempotenteplugin_urbackup_install_update_servers_table()in install.php + empty.sql.glpi_plugin_urbackup_serverassetsNON 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$.getsudropdown_host.ajax.phpalchangedel select tipo — vedi sezione Fix + divurbackup_host_items$randpre-popolato conDropdown::showconentity => Session::getActiveEntities()+ link all'host); validazione inprepareInputForUpdate()(itemtype vuoto/items_id ≤0/class mancante/!isItemtypeEnabled/item inesistente → azzera entrambi);getHostAsset(): ?CommonDBTM;getServersHostingAsset(string, int): arraycon guard$DB->fieldExists(self::getTable(),'host_itemtype')(robustezza pre-migrazione — pattern di Config::getEnableComputer). - front/dropdown_host.ajax.php (NUOVO): GET only,
Session::checkLoginUser(), validazioneclass_exists+Config::isItemtypeEnabled, rispondeDropdown::show($itemtype, ['name'=>'host_items_id','value'=>..., 'entity'=>Session::getActiveEntities(), 'display_emptychoice'=>true]). - AssetTab.php:
showHostServerBlock(CommonDBTM $item)chiamata per prima indisplayTabContentForItem()— 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;.moricompilati (msgfmt --check OK, warning header pre-esistenti) + cache traduzioni svuotata (files/_cache/*/translations/). - Verifiche IA:
php -lOK (setup.php, install/install.php, Server.php, AssetTab.php, dropdown_host.ajax.php); test CLI/tmp/opencode/urbackup_host_test.php10/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 querymax-width: 575.98px→flex-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
!importantsu.nav-tabs(display/flex-direction/flex-wrap) +.nav-iteme.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
.moerano corretti (msgunfmt/msgfmt --check OK). - Causa radice: GLPI 11
Session::loadLanguage()(src/Session.php:816+) crea$TRANSLATE(anonima class che estendeLaminas\I18n\Translator\Translator) con cache catalogo viaI18nCache(src/Glpi/Cache/I18nCache.php) →CacheManager::getTranslationsCacheInstance()(CONTEXT_TRANSLATIONS) →FilesystemAdaptersufiles/_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.monuovi. - Regola operativa: dopo OGNI ricompilazione dei
.model plugin, svuotarefiles/_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 sonologin_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 marcatoNOTUPDATEDe 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_VERSIONinsetup.php(regola 6 di AGENTS.md). - Meccanismo GLPI verificato (
src/Plugin.php):checkPluginState()(~righe 909-933) confrontaplugin_version_urbackup()['version']conglpi_plugins.version; se diverse →state = NOTUPDATEDe 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)), poiphp bin/console glpi:plugin:activate urbackup(l'install impostaNOTACTIVATED) — 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 headerProject-Id-Versiondei.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$rowse il loop generico lo passava dahtmlspecialchars()→ 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 unacardcon header + 4 card (row-cols-1/2/4): Linked server (link +protocol://ip:port), API status (badge verde/rosso dalast_api_status+ messaggio +last_api_check), server version (dash se vuoto), Client name. Valorifw-bold, labeltext-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 orfanaStaterimossa dai 3.po. - Locales: +2 msgid (
Client State,API status), -1 (State) → 181 tradotti + header;.moricompilati (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') inglpi_plugin_urbackup_configs(migrazione idempotenteplugin_urbackup_install_add_enable_computer_config()in install.php + INSERT in empty.sql) → bumpPLUGIN_URBACKUP_VERSION0.7.1→0.7.2(regola 6 AGENTS.md). - Perché prima "Computer sempre abilitato":
Config::isItemtypeEnabled()hardcodedreturn trueperComputer+registerClass(AssetTab, addtabon Computer)incondizionato in setup.php + badge statico inshowForm(). - Config.php: nuovo
getEnableComputer()(cache statica, guardTableExists, fallbacktruese riga/tabella assente),isItemtypeEnabled('Computer')egetEnabledItemtypes()ora rispettano il toggle; nuovogetEnabledAssetDefinitions()— lista Asset Definition con capacità UrBackup viahasCapacityEnabled()con istanza dagetAvailableCapacities()(fallback: decode raw JSONcapacities→array_column('name')). - setup.php:
registerClass(AssetTab, addtabon Computer)solo seConfig::getEnableComputer(); per le Asset Definition la registrazione tab resta inUrBackupCapacity::onClassBootstrap()(non toccata). - front/config.form.php: reintrodotto handler POST
update(CSRF gestito dal listener globale GLPI 11 — nienteSession::checkCSRF()): upsertenable_computer,Session::addMessageAfterRedirect, redirect aConfig::getFormURL(); form conDropdown::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.phprifiuta, unlinked/missing clients escludono Computer; i link esistenti inglpi_plugin_urbackup_serverassetsrestano intatti (solo visibilità UI). - Locales: +5 msgid (180 tradotti + header), header
Project-Id-Version→ 0.7.2,.moricompilati conmsgfmt --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()) → bumpPLUGIN_URBACKUP_VERSION0.7.0→0.7.1. - Eseguita la procedura standard:
glpi:plugin:install urbackup+glpi:plugin:activate urbackup; verificatoglpi_plugins.version = 0.7.1,state = ACTIVE, pagine front funzionanti. README.mdChangelog 0.7.1 aggiunto; header.po→ 0.7.1 con.moricompilati.
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 su0.0.0.0:55414; endpoint/x?a=salte 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.mdaggiornato: login 2-fasi riuscito, rimosso riferimento achangeClientSetting()(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: nuovogetApiPassword()(decrypt on-the-fly con fallback legacy in chiaro) +isApiPasswordEncrypted()(rileva formato base64+nonce ≥ 24B, evita warningtrigger_errordidecrypt()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 idempotenteplugin_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 eseguetestConnection()SOLO se cambianoip_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: loopopcache_invalidate()attivo solo sedefined('GLPI_ENVIRONMENT_TYPE') && GLPI_ENVIRONMENT_TYPE === 'development'(enumGlpi\Application\Environment, valoriproduction|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), sostituisceServerAsset::getAssetName()per riga (2 query/riga prima).
6. Localizzazioni it_IT / de_DE / en_GB aggiornate
- I 3
.poerano 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"), headerProject-Id-Version→ 0.7.0. .moricompilati conmsgfmt --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 -lOK 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)
src/Server.php: rimossitestApiConnection()+isNetworkError()(nessun chiamante; l'endpointfront/server_test.ajax.phpimplementa il test inline),getAssetGroupName()(mai chiamato), importuse CommonGLPI;inutilizzato.src/UrbackupApiClient.php: rimossichangeClientSetting()(duplicato diupdateClientSettings(), mai chiamato) eextractSettingValue()(duplicato della copia privata diAssetTab, mai chiamato).src/Config.php: rimossiensureDefaultConfiguration()esaveConfiguration()(no-op con commento "kept for backward compatibility" ma senza alcun chiamante utile) + import inutilizzatiDBmysql,Html.install/install.php: rimossa funzioneplugin_urbackup_install_update_assettypes_table()(mai chiamata dainstall_process()); rimosso bloccohasCapacity()/enableCapacity()con API inesistente in GLPI 11.0.8 (caveat 18, ora documentato nel codice: l'enable passa dal form Capacities UI); rimossa chiamataConfig::ensureDefaultConfiguration(); rimossi importAssetDefinitionManager,UrBackupCapacity.front/asset.form.php: rimossi handler POSTstart_file_backup/start_image_backup(nessun form del plugin li emette; i bottoni usanoexecute+urbackup_action).front/config.form.php: rimosso handler POSTupdate(il form non esiste più;Config::saveConfiguration()rimosso); aggiunto bootstrapinclude_once inc/includes.phpmancante; rimossoglobal $CFG_GLPI;inutilizzato.public/js/urbackup.js: FILE ELIMINATO (cercava#plugin-urbackup-api-statuse.plugin-urbackup-test-api, elementi mai renderizzati da nessun file PHP/Twig) + rimosso hookADD_JAVASCRIPTinsetup.php.hook.php: rimossirequire_once install/install.phpeinstall/uninstall.php(caricati ad ogni richiesta inutilmente; il lifecycle li carica dasetup.php).src/MassiveAction.php: rimossouse Session;inutilizzato; corrette due parentesi di chiusura con indentazione errata.src/AssetTab.php: rimossi parametri inutilizzati$server/$linkdashowStateSection()/showActionsSection()e$linkdashowInternalTabs()(privati).
Fix di sicurezza
- XSS (stored) —
AssetTab::showStateSection():$displayValue(valori status/settings dall'API UrBackup, potenzialmente ostili) era stampato SENZAhtmlspecialchars()→ ora escapato (htmlspecialchars((string) $displayValue)). 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
useal posto dei FQCN inline.
- rimosso
- Password API non più esposta nel page source (
Server::showFormFields): campoapi_passwordora convalue=''+ placeholder******+ hint "leave empty to keep current";prepareInputForUpdate()mantiene la password esistente quando il campo è vuoto. (Risolto il 05/08/2026:api_passwordora cifrata con GLPIKey — vedi sezione aggiornata.) - Validazione input server:
protocolwhitelistatohttp|https(prima qualsiasi stringa finiva ingetWebInterfaceUrl()→ href e URL cURL) eportclampato1..65535inprepareInputForAdd/prepareInputForUpdate. front/asset.form.phpdisconnect: condizione diritti allineata aServerAsset::disconnectAsset()(solo UPDATE; prima UPDATE|DELETE incoerente).front/server.form.php:$_GET['id']castato a int.
Verifiche
php -lOK su tutti i file PHP del plugin.- Smoke test HTTP (ambiente locale, senza auth):
server_test.ajax.phpGET→302 (login), POST→403 (denied),config.form.phpeserver.php→ 200 senza fatal error. (Nota: la verifica del 05/08/2026 ha confermato che il server UrBackup locale è attivo sulocalhost:55414e 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
→ fatto:api_passwordin chiaro suglpi_plugin_urbackup_servers→ cifrare conGLPIKey(new GLPIKey())->encrypt()inprepareInputForAdd/Update+ migrazioneplugin_urbackup_install_encrypt_api_passwords()+getApiPassword()con fallback legacy.→ fatto: solo sesetup.phptop-level: loopopcache_invalidate()ad OGNI richiesta webGLPI_ENVIRONMENT_TYPE === 'development'.→ fatto: solo se i parametri di connessione cambiano.Server::prepareInputForUpdate(): test connessione API ad ogni save→ fatto: batch loading per itemtype.Server::showUnlinkedClientsTab(): N+1 suServerAsset::getAssetName()
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
AGENTS.md(sovrascritto — prima era identico aAGENTS.md_netbackup): ruolo Senior GLPI Plugin Architect, regole assolute (Git rule con autorizzazione esplicita, strict types PHP 8.3/8.4, namespaceGlpiPlugin\Urbackup\), CSRF GLPI 11 (CheckCsrfListener globale, nienteSession::checkCSRF()nei front), limiti ambiente locale (server UrBackup locale verificato attivo:localhost:55414), struttura v0.7.0 e hook essenziali.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.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 ininstall/install.php(mai in upgrade/uninstall).Html::hidden()in GLPI 11 usa la firma(name, options)— patternHtml::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]).- Hook
plugin_urbackup_MassiveActions($type)riceve l'itemtype come stringa. api_passwordin chiaro: limite noto → risolto il 05/08/2026 con cifratura(new GLPIKey())->encrypt()/decrypt()(non esisteGLPIKey::getInstance()).
Server UrBackup per test in locale (VERIFICATO ATTIVO)
- URL:
http://localhost:55414 - Credenziali: utente
admin, password12345678 - Scopo: eseguire API semplici (login, status, backup) in locale per debugging e validazione rapida.
- Stato: processo
urbackupsrvattivo (porta 55414), endpoint/x?a=salte 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-368chiama$definition->hasCapacity(UrBackupCapacity::class)e$definition->enableCapacity(UrBackupCapacity::class)— in GLPI 11.0.8 suAssetDefinitionesistono SOLOhasCapacityEnabled(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 diGlpi\Asset\Asset(tab sempre visibile). - TODO: correggere con l'API core corretta: check con
hasCapacityEnabled(new UrBackupCapacity())(richiede un oggettoCapacityInterface); l'abilitazione/disabilitazione in GLPI 11.0.8 avviene via inputcapacitiesdel form (array di spec) processato inAssetDefinition::post_updateItem()(AssetDefinition.php:316-440) — NON esistono metodi pubblicienableCapacity/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 viaCheckCsrfListener - 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 headerX-Glpi-Csrf-TokencongetAjaxCsrfToken()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:
Asset::__construct()— itera 30+ Capacity classi per ogni istanzaAsset::post_getFromDB()— decodifica JSON custom fields e li processaeval()autoloading — le classi concrete sono definite viaeval()a runtime
Ottimizzazioni applicate
AssetTab.php::loadApiData()— letto$item->fields['name']direttamente invece di chiamareServerAsset::getAssetName()che faceva una secondagetFromDB()ridondanteAssetTab.php::startBackup(),saveInternetMode(),saveDefaultDirs(),showServerLinkedBlock()— stesso pattern,$item->fields['name']al posto digetAssetName()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— registraAssetTabviaCommonGLPI::registerStandardTab()inonClassBootstrap()setup.php— registra capacità, CSS, hooksrc/AssetTab.php— display tab content + tab interni (Stato/Azioni/Info-Log) + host server blocksrc/ServerAsset.php— gestione collegamenti asset-serversrc/Config.php— itemtype enabled checksrc/UrbackupApiClient.php— client API con caching in-memory (per istanza) e sessionesrc/LocationHelper.php— risoluzione location radicesrc/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 pergetStatus()egetClientSettings()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)
src/Server.php:436—$canUpdatecontrollava soloSession::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.src/Profile.php:73-77—installRights()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 = 0e nessun profilo riceveva i diritti completi.
Fix applicati
Server.php::showFormFields()— Sostituito$canUpdatecon$canEdit: per nuovi server ($ID <= 0) usaCREATE, per server esistenti usaUPDATE.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
install/mysql/plugin_urbackup-empty.sql— Aggiunta colonnausers_id INT UNSIGNED NOT NULL DEFAULT 0dopolocations_id+ indiceinstall/install.php— Aggiunto$migration->addField('users_id', ...)e$migration->addKey('users_id')nella migrazione del serversrc/Server.php— Aggiunto search optionid=8perUser::getTable()+ auto-set diusers_idda$_SESSION['glpiID']inprepareInputForAdd()
Versione
- 0.7.3
0.7.3 — Link Server ↔ asset host hardware
- Colonne
host_itemtype/host_items_id+ KEYhost_assetsuglpi_plugin_urbackup_servers(migrazione idempotente + empty.sql) → bump 0.7.2 → 0.7.3 (regola 6). - Form server: riga "Hardware host" con dropdown tipo (
Dropdown::showItemTypes) + dropdown elementi AJAX ($.getsufront/dropdown_host.ajax.php, vedi sezione Fix del 07/08/2026). - AssetTab: blocco "This asset hosts the UrBackup server" (sempre visibile, anche se client).
- Validazione in
prepareInputForUpdate()+ helpergetHostAsset()/getServersHostingAsset()(guardfieldExists).
0.7.2 — Toggle Computer configurabile + lista Asset custom con capacità attiva
enable_computeringlpi_plugin_urbackup_configs(default '1') letto daConfig::getEnableComputer(); registrazione tab su Computer condizionale in setup.php;isItemtypeEnabled/getEnabledItemtypesrispettano il toggle.- Config page: dropdown Sì/No per Computer + lista Asset Definition con capacità UrBackup attivata (
getEnabledAssetDefinitions()). - front/config.form.php: handler POST
updateripristinato (upsert, redirect, messaggio). - DB: migrazione idempotente + INSERT in empty.sql → bump 0.7.2 (regola 6).
0.7.1 — Cifratura api_password (GLPIKey), ottimizzazioni e localizzazioni
api_passwordcifrata con(new GLPIKey())->encrypt()on save (Server::prepareInputForAdd/Update),Server::getApiPassword()con fallback legacy, migrazione idempotenteplugin_urbackup_install_encrypt_api_passwords()(install.php) — applicata al DB (server id=1).- Test connessione API solo a parametri cambiati in
prepareInputForUpdate()(niente più chiamate da 30s ad ogni save). - Loop
opcache_invalidate()solo in development (GLPI_ENVIRONMENT_TYPE === 'development'). - Fix N+1 in
Server::showUnlinkedClientsTab()(batch loading per itemtype). - Localizzazioni: 115 nuove traduzioni it/de/en, 9 orfane rimosse,
.moricompilati. - 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.mdaggiornato.
0.7.0 — Pulizia, sicurezza e DB cleanup
- CSRF hardening:
Session::checkCSRF()aggiunto suasset.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) - File deprecati rimossi: 12 file (composer copy, AGENTS_OLD.MD, js/, ajax/, front/test/view, FIX_PERMISSIONS.sh, removed/)
- Dead code rimosso: 3 Controller, 1 Command, 4 template Twig
- DB migration: DROP
glpi_plugin_urbackup_profileseglpi_plugin_urbackup_assettypes; add indexlocation_activesu servers; adddate_creation/date_modsu serverassets - SQL schema pulito: Rimosse tabelle legacy dall'empty.sql
- PHP lint warnings: Rimosse
use Html;euse Search;superflue - README.md creato
- PURGE right: Aggiunto PURGE ai diritti di installazione (
Profile.php:76,90); applicato a tutti i profili Super-Admin esistenti nel DB - CSRF GLPI 11 fix:
Session::checkCSRF()richiede$_POSTcome argomento in GLPI 11; aggiuntoX-Glpi-Csrf-Tokenheader al JS AJAX - Session::isDebugActive(): Metodo inesistente in GLPI 11; sostituito in
AssetTab.php:150con($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE