Releases: monta990/gdmsintegration
Releases · monta990/gdmsintegration
1.5.0
[1.5.0] — 2026-05-23
Fixed
- Entity access check on AJAX endpoints —
alerts.ajax.php,clients.ajax.php, andports.ajax.phpnow callSession::haveAccessToEntity()after parsingentities_id. A user withconfig:READon entity 1 can no longer query data from entity 2 via?entities_id=2. php://inputcapped at 64 KB in firmware.ajax.php — replacedfile_get_contents('php://input')withstream_get_contents(..., 65536)in both theupgradeandupgrade_gdmshandlers.- Missing access control on AJAX endpoints —
alerts.ajax.php,alerts-dismiss.ajax.php,clients.ajax.php, andports.ajax.phpchecked onlycheckLoginUser(). Any authenticated GLPI user could query GWN alerts, WiFi clients, and WAN port state across all entities. All four now requireconfig:READ. - Alert dismiss IDs validated —
alerts-dismiss.ajax.phpnow filters IDs through apreg_match('/^[a-zA-Z0-9_\-]{1,64}$/')whitelist before forwarding to the GWN API. Arbitrary strings are silently dropped. - Port modal labels showed "undefined" —
STR.gateway,STR.dns,STR.wanMac,STR.txrxPkts,STR.linkSpeed, andSTR.descriptionwere missing from$js_stringsindashboard.php. Gateway, DNS, WAN MAC, TX/RX packets, link speed, and description rows in the port detail modal now display correctly. history_import.phpstandalone endpoint disabled — the file now returns HTTP 404 immediately; import is handled exclusively byconfig.form.phpwhich has MIME, size, and transaction controls.ensureSchema()runs once per PHP process — a static flag prevents the ~40ALTER TABLEstatements from executing on every dashboard page load under concurrent PHP-FPM workers.- XLSX upload size limit configurable — the 5 MB cap is now a setting ("Max upload size") in the History Import card (1–50 MB, default 5 MB). Stored in
max_xlsx_size_mbconfig column. - TLS certificate verification restored —
CURLOPT_SSL_VERIFYPEERwasfalseingwnGetFirmwareVersionsBatch()while the other three curl calls usedtrue. All GWN Cloud calls now verify the server certificate. - Firmware download URL restricted to Grandstream CDN —
upgrade_gdmsnow validates thedownloadUrlhost against an allowlist (firmware.grandstream.com,fw.gdms.cloud). Arbitrary URLs including RFC-1918 addresses andfile://URIs are rejected before being forwarded to GDMS. - Offline tickets now open for devices already offline when category flag is enabled — the sync previously only created offline tickets on an
online→offlinetransition; devices that were already offline whentickets_phone/tickets_router/ etc. was enabled never received a ticket. The check now also runs onoffline→offline, relying on the existing duplicate guard insidecreateOfflineTicket()to avoid double tickets. - History import wrapped in DB transaction — INSERT loop now runs inside
START TRANSACTION/COMMIT; any failure triggersROLLBACK, leaving the history table in a consistent state. - Rate limiting moved to database — reboot and factory-reset cooldowns are now stored in
last_reboot_at/last_factory_reset_atcolumns on the device row instead of$_SESSION. Cooldown survives session reset, new browser tabs, and directcurlcalls. - API tokens redacted from log files —
access_token,token, andAuthorizationquery-string values are replaced with[REDACTED]before being written togdmsintegration.log, even in verbose/debug mode. visnetwork_urlescaped in Twig — changed from|rawto|escape('html_attr')indashboard.html.twig. (defence-in-depth)- Factory reset requires
config:PURGE— previously required onlyconfig:UPDATE(standard technician right). Now requires the higherPURGEright, restricting factory reset to administrators and profiles explicitly granted that permission. - Server-side rate limiting for destructive device actions — reboot is limited to once per 60 s per device per user session; factory reset is limited to once per 5 min per device per user session. Bypass via direct POST is blocked server-side regardless of the frontend confirmation UI.
- File upload MIME validation — history XLSX and config JSON uploads now verify the actual file magic bytes via
finfo(FILEINFO_MIME_TYPE)instead of trusting the client-suppliedContent-Type. Rejects non-XLSX files uploaded to the history importer and non-JSON files uploaded to the config importer before any parsing occurs.
Removed
- Webhook receiver removed —
front/webhook.phpand all related infrastructure (stateless path registration,webhook_secretconfig field, HMAC validation logic, locale strings) have been removed. The cron sync and dashboard manual sync provide equivalent coverage without exposing an unauthenticated HTTP endpoint. Thewebhook_secretdatabase column is dropped automatically on first load viaensureSchema().
1.4.3
[1.4.3] — 2026-05-22
Fixed
- Accessibility warnings — label/input associations — all
<label>elements in the config form now haveforattributes pointing to their corresponding fieldid. Inputs that lacked anidreceived one. The Entity dropdown heading was changed from<label>to<p>(no single target field). Eliminates 16 browser accessibility warnings on the config page. - Accessibility warnings — dashboard search field — the Vue filter bar search
<input>now hasid="gdms-device-search"andname="search", resolving the "form field has neither id nor name" browser warning. - Accessibility warnings — firmware schedule label — the "Schedule for" label in the firmware upgrade modal now has
for="gdmsFwDatetime", linking it to the flatpickr date input.
Improved
- vis-network updated to 10.1.0.
1.4.2
[1.4.2] — 2026-05-19
Added
- Factory reset for UC phones/ATAs — the phone SIP detail modal now includes a Factory Reset button (before the Reboot button) with a prominent danger alert explaining the consequences. The button requires two clicks (second click turns yellow "I understand — Reset now", auto-reverts in 6 s) to prevent accidental execution. Calls GDMS
task/addwithtaskType=2. Requiresconfig:UPDATEpermission. -BETA - THIS FEATURE MAY FAIL.
Fixed
- Firmware scheduler date/time picker did not update the field after selection —
altInput: truecombined withwrap: truecaused flatpickr to insert a hidden secondary input inside the Bootstrapbtn-group, leaving the visible field blank after picking a date or time. RemovedaltInput/altFormat; the originaldata-inputfield now updates directly withd/m/Y H:iformat. Schedule submission is unaffected (readsselectedDates[0]) - BETA - THIS FEATURE MAY FAIL. - GWN device disappears intermittently — when the GWN Cloud ap/list request for one network timed out, the plugin treated that network as having zero devices and deleted their state records, causing devices to vanish from the dashboard until the next successful sync.
gwnGetDevices()now returnsfalseon any per-network failure, which triggers the existing removal guard so no state is deleted during a partial API failure. - Restart devices — an immediate execution task is scheduled to restart the device. -BETA - THIS FEATURE MAY FAIL.
- JSON Export — now export and import correctly.
Improved
- Firmware modal — CDN-only devices show "Latest available" — phones/ATAs (GRP, HT, WP, etc.) have no firmware version page on grandstream.com; the modal now displays "Latest available" instead of the raw CDN filename. Version sent to GDMS task is left blank when only a download URL is known, avoiding the previous incorrect behaviour of sending the current firmware version as the target.
- Firmware check_all — parallel GWN version fetch —
check_allnow fetches firmware versions for all GWN networks in a single parallelisedcurl_multibatch instead of one sequential HTTP call per network, matching the behaviour of the existingcheckaction. Reduces latency proportionally to the number of configured networks. - Twig + Vue 3 frontend —
front/dashboard.phpandfront/config.form.phpconverted to standalone Twig templates (templates/dashboard.html.twig,templates/config_form.html.twig). PHP data layer and HTML presentation fully separated. Vue 3 filter bar replaces inline JS DOM manipulation. Compatible with GLPI 11 and GLPI 12.
1.4.1
[1.4.1] — 2026-05-13
Added
- History import from Excel — new card in Configuration lets operators restore availability history from a previously exported
gdms_history_*.xlsxfile (renamed fromgdms_disponibilidad_*.xlsx). Device-days that already have data are skipped (non-destructive). Each imported day generates 100 synthetic records spaced ~14 min apart so the daily online/total ratio exactly reconstructs the original percentage (±1 %). - Plugin configuration export — download all plugin settings as a JSON backup file. An optional checkbox includes API credentials (username, keys, secrets) in the export for full migration scenarios.
- Plugin configuration import — restore settings from a previously exported JSON backup. Credentials are only written when the source file explicitly included them; all other imports leave stored secrets unchanged.
- Firmware modal — device name and private IP — modal header now shows the device name and its private IP (clickable link) so the operator knows which device they are updating without scrolling the table.
- Firmware modal — copy MAC with one click — clicking the MAC code in the modal copies it to the clipboard; shows a brief "Copied!" confirmation.
- Firmware modal — firmware downloads link — info note now includes a direct link to grandstream.com/support/firmware that opens in a new tab.
- Firmware modal — beta-only devices show selectable version — for GDMS-managed phones whose firmware page only has a beta channel (GRP260x, WP8x6, HT8xxV2, GCC6xx), the available version is now shown as a pre-selected radio button instead of a text note, making the scheduled version clearly visible.
- Topology — phone → PBX edges — phones are now connected to their UCM/GCC PBX in the vis-network topology graph based on shared network name; lines are drawn automatically with no extra queries.
- Topology — localised node status — node tooltips now use the active UI language for "Online"/"Offline" instead of hard-coded English.
- Firmware update in progress indicator — after a successful upgrade request the firmware version cell shows "Updating…" instead of going blank; reverts to the real version on the next sync.
Fixed
- CSRF double-upgrade failure — second firmware upgrade in the same session failed with CSRF error. Root cause: GLPI 11 consumes single-use form tokens but preserves
X-Glpi-Csrf-Tokenheader tokens (preserve_token: true). All firmware upgrade fetches now sendX-Requested-With: XMLHttpRequest+X-Glpi-Csrf-Tokenheader; the body token field is removed.
Improved
- Sync performance — eliminated up to 3 DB round-trips per device: device state is loaded once into an in-process PHP cache (
primeCache()) sogetState()andsaveStateWithNetwork()skip per-devicefind()calls; existing topology links are pre-loaded into a PHP set so link deduplication is a hash lookup; all history snapshots are flushed in a single bulkINSERTat the end of the loop instead of one per device. Expected reduction: ~35 % fewer queries on a 35-device account. - vis-network updated to 10.0.3.
1.4.0
[1.4.0] — 2026-05-10
Added
- Phone SIP status dot — phones show a colour-coded 9 px dot in the Ports column (green = SIP registered, red = unregistered; dimmed when offline). Clicking opens a SIP detail modal with: SIP status, extension, site, private IP (clickable), public IP (WHOIS link), last seen, PBX/UCM IP, Do Not Disturb, provisioning sync status + error, scheduled task.
- Phone SIP dot tooltip — hovering the SIP dot shows a native tooltip: SIP state, extension (if any), Do Not Disturb flag.
- PBX / UCM in phone modal — SIP modal shows the UCM/GCC device in the same network as a clickable private IP link with device name. Matched by
siteName; no extra API call. - ATA / HT devices show phone modal — any device whose model prefix matches the phone list (HT, GRP, GXP, GXV, GXW, WP, DP, GHP, GVC, GSC, GDS) renders the SIP dot and modal regardless of GLPI itemtype (Phone or NetworkEquipment).
- GDMS provisioning fields synced — four new fields from
device/listpersisted per device:dnd,is_synchronized,sync_failure_msg,scheduled_task. accountStatus→ SIP status mapping —accountStatus(1 = registered) now mapped tosip_status; was previously unpopulated.lastTime→ last seen fallback — GDMSlastTimestring used aslast_seenwhen GWNlastSeenepoch is absent.- IPv4/IPv6 display preference — new "Private IP display" config setting (IPv4 preferred by default); fallback to other version when preferred is absent; both shown as clickable links when both present.
- IPv6 addresses clickable — IPv6 in the Private IP column rendered as
http://[addr]/links (RFC 2732). - Correct IPv4/IPv6 routing at sync —
privateIpvalues containing:stored inipv6column instead ofprivate_ip.
1.3.8
[1.3.8] — 2026-05-09
Fixed
- GLPI 11/12 compatibility — replaced removed
Html::displayRightError()withthrow new \Glpi\Exception\Http\AccessDeniedHttpException()infront/history_export.php; compatible with both GLPI 11 and 12. - GLPI 11/12 compatibility — replaced non-existent
Html::forbidden()withthrow new \Glpi\Exception\Http\AccessDeniedHttpException()infront/dashboard.php; method never existed in either GLPI version. - GLPI 12 compatibility —
Hooks::CSRF_COMPLIANTregistration insetup.phpnow guarded withdefined()check; constant was removed in GLPI 12 causing a PHP fatal error on plugin load. - GLPI 12 compatibility — replaced
$DB->doQueryOrDie()with$DB->doQuery()insetup.php(install/uninstall); method was removed in GLPI 12. Both methods throw on error so behavior is identical across versions. - GLPI 11/12 compatibility — fixed
$rightnamePHP compile error; GLPI 12 addedstringtype toCommonGLPI::$rightnamewhile GLPI 11 leaves it untyped — PHP requires child type to match parent exactly. IntroducedPluginGdmsintegrationBaseGLPIandPluginGdmsintegrationBaseTMabstract shim classes with conditional type declaration (GLPI_VERSION >= 12); all five plugin classes now extend the appropriate shim and inherit$rightnamewithout redeclaring it.
1.3.7
[1.3.7] — 2026-05-03
Security - OWASP
- Credential redaction in debug logs — GWN OAuth token request URL and token response are no longer logged verbatim. Debug output now shows only
appIDandexpires_in, preventingclient_secretandaccess_tokenfrom appearing infiles/_log/gdmsintegration.logeven when verbose mode is enabled. (OWASP A02/A09) - Advisory lock queries use escaped identifiers —
GET_LOCK/RELEASE_LOCKraw SQL queries in the sync engine now call$DB->escape()on the lock name, following defense-in-depth for all raw query parameters. (OWASP A03) - Webhook signature mismatch returns 204 — failed HMAC verification no longer returns HTTP 403 (which confirmed the endpoint existed and the signature was checked). Now returns
204 No Content, removing the oracle useful for probing or brute-forcing. (OWASP A07) - Webhook GET handler removed — the unauthenticated
GEThealth-check response that disclosed plugin name and endpoint path has been removed. Non-POST requests now receive405with no body. (OWASP A05) - Webhook payload logging scoped — log line now records only
entity,mac, andstatusrather than up to 500 chars of raw JSON payload. (OWASP A09) - Firmware endpoint requires READ right —
firmware.ajax.phpread actions (check,check_all) now requireSession::checkRight('config', READ)instead of a plain session check, preventing any authenticated GLPI user from querying firmware status of all devices. (OWASP A01)
1.3.6
[1.3.6] — 2026-04-30
Added
- Ticket creation by device type — new "Ticket creation by device type" config section with five independent toggles: IP Phones (GRP/GXP/GXV/WP), Routers (GWN7001/7002/7003), Switches (GWN7800/GSS), Access Points (GWN76xx), and IP PBX / UCM (UCM/GCC). All enabled by default for backward compatibility. Disabling a toggle suppresses offline incident tickets for that device category; ticket resolution remains unaffected so existing open tickets still auto-close. Translated in es_MX, fr_FR, de_DE.
- GLPI asset name on tickets — offline and WAN-down ticket subjects now use the GLPI asset name when the device is already registered in GLPI. Falls back to the GDMS cloud name for unregistered devices.
- Private IP in offline tickets — offline incident ticket body now includes the device's private/LAN IP alongside the public IP.
- Dashboard UX improvements — device name and Critical SLA banner links now open in a new browser tab; private IP cell is clickable and opens the device's admin page (
http://<private_ip>) in a new tab; model, MAC, and serial cells copy their value to the clipboard on click (brief ✓ flash confirms the copy).
1.3.5
[1.3.5] — 2026-04-23
Added
- Disable WAN port tickets — new config toggle
wan_tickets_enabled(enabled by default). When turned off, the plugin stops opening incident tickets for WAN link-down and no-internet events entirely. Debounce timers and port state tracking continue normally so the feature can be re-enabled at any time without side-effects. Resolved tickets are still closed automatically. Active regardless of sync method (cron or manual).
1.3.4
[1.3.4] — 2026-04-22
Fixed
- False WAN tickets on all ports — WAN port ticket loop iterated every port in
wan_ports_jsonincluding LAN ports (role=0). LAN link-down events now correctly skipped viarole != 1guard at the top of the loop. - Non-router devices treated as routers —
$is_gwn_routerused!$is_gwn_switchas the only exclusion, so APs (GWN76xx) and any NetworkEquipment with anetworkIdcould enter the router WAN port code path. Now uses explicitpreg_match('/^GWN700[123]/i', $gdms_model)— only GWN7001/7002/7003 trigger WAN port monitoring.
Added
- Asset user as ticket requester — when a GLPI asset has a user assigned (
users_id), that user is set as requester on auto-generated offline and WAN-down tickets. Takes priority over the entity-level default requester configured in plugin settings. - WAN no-internet debounce — new config option
wan_debounce_seconds(default 300 s, range 0–3600). WhenconnectStatusdrops to 0 (internet lost) the plugin waits the configured number of seconds before opening a ticket. Prevents false alerts caused by transient high-latency events that momentarily fail the router's internet reachability test. Physical link-down events (Case A) are never debounced. Timer is stored insidewan_ports_jsonand survives across sync cycles. Setting to 0 restores the previous immediate-open behaviour. Translated in es_MX, fr_FR, de_DE.