fix logs client
This commit is contained in:
@@ -2,6 +2,18 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -113,6 +113,11 @@ plugin_urbackup/
|
||||
- The asset tab now shows a "This asset hosts the UrBackup server" block with links to the hosted servers (always visible, even when the asset is also a linked client)
|
||||
- Server save validates the host link (unknown/disabled itemtype or missing item resets the link)
|
||||
- Added it_IT/de_DE/en_GB translations (2 new strings) and recompiled locales
|
||||
- Fixed the plugin right check on the server form and internal tabs (`Session::haveRight` → `Profile::canCurrentUser`), so API username/password become editable right after plugin installation without re-login
|
||||
- Client logs: fixed the log level column (API returns `loglevel`, now mapped to ERROR/WARNING/INFO/DEBUG); show a clear warning when the client is not found by name instead of the misleading "No client logs available"
|
||||
- Fixed the inverted IP lookup JOIN (`glpi_networknames` ↔ `glpi_ipaddresses`) so the IP column in Linked/Missing clients is correct
|
||||
- Made the missing/unlinked clients queries server-scoped (`plugin_urbackup_servers_id`) and the API data session cache key unique per asset (itemtype+id)
|
||||
- `server_test.ajax.php`: entity-aware `check()` authorization; added `public/js/urbackup.js` (registered via `ADD_JAVASCRIPT`) that sends the `X-Glpi-Csrf-Token` header on plugin AJAX POSTs; `dropdown_host.ajax.php` now requires READ right
|
||||
|
||||
### 0.7.2
|
||||
- UrBackup on Computer is now configurable: new `enable_computer` setting on the plugin configuration page (previously always enabled, hardcoded)
|
||||
|
||||
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
|
||||
use GlpiPlugin\Urbackup\Config;
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
define('GLPI_ROOT', dirname(__DIR__, 4));
|
||||
@@ -22,6 +23,11 @@ Html::header_nocache();
|
||||
|
||||
Session::checkLoginUser();
|
||||
|
||||
if (!Profile::canCurrentUser(READ)) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET') {
|
||||
http_response_code(405);
|
||||
exit;
|
||||
|
||||
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
||||
* only accepts POST requests (GET requests must not trigger state changes).
|
||||
*/
|
||||
|
||||
use GlpiPlugin\Urbackup\Profile;
|
||||
use GlpiPlugin\Urbackup\Server;
|
||||
use GlpiPlugin\Urbackup\UrbackupApiClient;
|
||||
|
||||
@@ -31,12 +30,6 @@ if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!Profile::canCurrentUser(UPDATE)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => __('No permission', 'urbackup')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$server_id = (int) ($_POST['id'] ?? 0);
|
||||
|
||||
if ($server_id <= 0) {
|
||||
@@ -46,8 +39,13 @@ if ($server_id <= 0) {
|
||||
|
||||
$server = new Server();
|
||||
|
||||
if (!$server->getFromDB($server_id)) {
|
||||
echo json_encode(['success' => false, 'message' => __('Server not found', 'urbackup')]);
|
||||
try {
|
||||
// Entity-aware authorization: checks UPDATE right AND access to the server's entity
|
||||
// (returning the server on success, throwing otherwise).
|
||||
$server->check($server_id, UPDATE);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -106,6 +106,13 @@ msgstr ""
|
||||
msgid "Client not found on UrBackup server."
|
||||
msgstr "Client auf dem UrBackup-Server nicht gefunden."
|
||||
|
||||
msgid ""
|
||||
"Client not found on UrBackup server. Check that the asset name matches the "
|
||||
"client name."
|
||||
msgstr ""
|
||||
"Client auf dem UrBackup-Server nicht gefunden. Überprüfen Sie, ob der Asset-"
|
||||
"Name mit dem Client-Namen übereinstimmt."
|
||||
|
||||
msgid "Client state"
|
||||
msgstr "Client-Status"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -101,6 +101,13 @@ msgstr ""
|
||||
msgid "Client not found on UrBackup server."
|
||||
msgstr "Client not found on UrBackup server."
|
||||
|
||||
msgid ""
|
||||
"Client not found on UrBackup server. Check that the asset name matches the "
|
||||
"client name."
|
||||
msgstr ""
|
||||
"Client not found on UrBackup server. Check that the asset name matches the "
|
||||
"client name."
|
||||
|
||||
msgid "Client state"
|
||||
msgstr "Client state"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -105,6 +105,13 @@ msgstr ""
|
||||
msgid "Client not found on UrBackup server."
|
||||
msgstr "Client non trovato sul server UrBackup."
|
||||
|
||||
msgid ""
|
||||
"Client not found on UrBackup server. Check that the asset name matches the "
|
||||
"client name."
|
||||
msgstr ""
|
||||
"Client non trovato sul server UrBackup. Verifica che il nome dell'asset "
|
||||
"corrisponda al nome del client."
|
||||
|
||||
msgid "Client state"
|
||||
msgstr "Stato client"
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
(function ($, document) {
|
||||
'use strict';
|
||||
|
||||
function getCsrfToken() {
|
||||
var meta = document.querySelector('meta[property="glpi:csrf_token"]');
|
||||
return meta ? meta.getAttribute('content') : '';
|
||||
}
|
||||
|
||||
// GLPI 11 validates the CSRF token of every AJAX POST through the
|
||||
// X-Glpi-Csrf-Token header (see CheckCsrfListener). Register the token
|
||||
// on all plugin AJAX requests so endpoints such as server_test.ajax.php
|
||||
// are accepted instead of being rejected with a 403.
|
||||
$(document).on('ajaxSend', function (event, xhr, settings) {
|
||||
var token = getCsrfToken();
|
||||
if (token !== '' && typeof settings !== 'undefined' && settings.type && settings.type.toUpperCase() === 'POST') {
|
||||
xhr.setRequestHeader('X-Glpi-Csrf-Token', token);
|
||||
}
|
||||
});
|
||||
|
||||
// Also cover direct $.ajax/{$.get[Script]/-free POST}
|
||||
var origAjax = $.ajax;
|
||||
$.ajax = function (url, options) {
|
||||
var settings = $.isPlainObject(url) ? url : $.extend({ url: url }, options || {});
|
||||
var token = getCsrfToken();
|
||||
if (settings.type && settings.type.toUpperCase() === 'POST' && token !== '') {
|
||||
settings.headers = settings.headers || {};
|
||||
settings.headers['X-Glpi-Csrf-Token'] = token;
|
||||
}
|
||||
return origAjax.call(this, settings);
|
||||
};
|
||||
})(jQuery, document);
|
||||
@@ -93,6 +93,10 @@ function plugin_init_urbackup(): void
|
||||
$PLUGIN_HOOKS[Hooks::ADD_CSS]['urbackup'] = [
|
||||
'public/css/urbackup.css',
|
||||
];
|
||||
|
||||
$PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = [
|
||||
'public/js/urbackup.js',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-3
@@ -371,7 +371,7 @@ class AssetTab extends CommonDBTM
|
||||
$client_name = (string) ($item->fields['name'] ?? '');
|
||||
$asset_ip = ServerAsset::extractAssetIp($item);
|
||||
|
||||
$cache_key = 'urbackup_data_' . $server->fields['id'] . '_' . $client_name;
|
||||
$cache_key = 'urbackup_data_' . $server->fields['id'] . '_' . $item::class . '_' . $item->fields['id'];
|
||||
if (isset($_SESSION[$cache_key]) && $_SESSION[$cache_key]['time'] > time() - 30) {
|
||||
return $_SESSION[$cache_key]['data'];
|
||||
}
|
||||
@@ -725,14 +725,18 @@ class AssetTab extends CommonDBTM
|
||||
foreach ($logs as $log) {
|
||||
echo "<tr class='tab_bg_1'>";
|
||||
echo "<td>" . htmlspecialchars(self::formatTimestamp($log['time'] ?? $log['created'] ?? '')) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) ($log['level'] ?? $log['severity'] ?? '')) . "</td>";
|
||||
echo "<td>" . htmlspecialchars(self::formatLogLevel($log)) . "</td>";
|
||||
echo "<td>" . htmlspecialchars((string) ($log['message'] ?? $log['msg'] ?? $log['text'] ?? '')) . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
if (count($api_data['logs']) === 0) {
|
||||
echo "<tr class='tab_bg_1'><td colspan='3'>";
|
||||
echo htmlspecialchars(__('No client logs available.', 'urbackup'));
|
||||
if ($api_data['client_found'] !== false) {
|
||||
echo htmlspecialchars(__('No client logs available.', 'urbackup'));
|
||||
} else {
|
||||
echo htmlspecialchars(__('Client not found on UrBackup server. Check that the asset name matches the client name.', 'urbackup'));
|
||||
}
|
||||
echo "</td></tr>";
|
||||
}
|
||||
|
||||
@@ -931,6 +935,31 @@ class AssetTab extends CommonDBTM
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the numeric log level returned by the UrBackup "livelog" API
|
||||
* into a readable label.
|
||||
*
|
||||
* @param array<string, mixed> $log Log entry row
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function formatLogLevel(array $log): string
|
||||
{
|
||||
$raw = (string) ($log['loglevel'] ?? $log['level'] ?? $log['severity'] ?? '');
|
||||
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return match ((int) $raw) {
|
||||
1 => 'ERROR',
|
||||
2 => 'WARNING',
|
||||
3 => 'INFO',
|
||||
4 => 'DEBUG',
|
||||
default => is_numeric($raw) ? $raw : ucfirst(strtolower($raw)),
|
||||
};
|
||||
}
|
||||
|
||||
public static function startBackup(CommonDBTM $item, string $type): bool
|
||||
{
|
||||
$link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false);
|
||||
|
||||
+12
-6
@@ -1045,7 +1045,10 @@ class Server extends CommonDBTM
|
||||
global $DB;
|
||||
|
||||
$iterator = $DB->request([
|
||||
'FROM' => 'glpi_plugin_urbackup_serverassets',
|
||||
'FROM' => 'glpi_plugin_urbackup_serverassets',
|
||||
'WHERE' => [
|
||||
'plugin_urbackup_servers_id' => (int) $server->fields['id'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Batch-load asset names (one query per itemtype) to avoid N+1 lookups.
|
||||
@@ -1240,7 +1243,10 @@ class Server extends CommonDBTM
|
||||
$rootLocationId = LocationHelper::getRootLocationId($serverLocationId);
|
||||
|
||||
$linkedIterator = $DB->request([
|
||||
'FROM' => ServerAsset::getTable(),
|
||||
'FROM' => ServerAsset::getTable(),
|
||||
'WHERE' => [
|
||||
'plugin_urbackup_servers_id' => (int) $server->fields['id'],
|
||||
],
|
||||
]);
|
||||
$linkedAssetKeys = [];
|
||||
foreach ($linkedIterator as $row) {
|
||||
@@ -1515,8 +1521,8 @@ JAVASCRIPT;
|
||||
'INNER JOIN' => [
|
||||
'glpi_networknames AS nn' => [
|
||||
'ON' => [
|
||||
'nn' => 'items_id',
|
||||
'ipa' => 'id',
|
||||
'nn' => 'id',
|
||||
'ipa' => 'items_id',
|
||||
['AND' => ['ipa.itemtype' => 'NetworkName']],
|
||||
],
|
||||
],
|
||||
@@ -1613,8 +1619,8 @@ JAVASCRIPT;
|
||||
'INNER JOIN' => [
|
||||
'glpi_networknames AS nn' => [
|
||||
'ON' => [
|
||||
'nn' => 'items_id',
|
||||
'ipa' => 'id',
|
||||
'nn' => 'id',
|
||||
'ipa' => 'items_id',
|
||||
['AND' => ['ipa.itemtype' => 'NetworkName']],
|
||||
],
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user