|
3 | 3 | * GDMS Integration — Webhook receiver |
4 | 4 | * Registered as stateless — called by GDMS Cloud without browser session. |
5 | 5 | * Uses correct Symfony exception namespaces for GLPI 11. |
| 6 | + * |
| 7 | + * Note: GDMS Cloud does not support configurable webhook secrets/signatures. |
| 8 | + * The HMAC check runs only when webhook_secret is configured (e.g. for custom |
| 9 | + * integrations or future GDMS support). Without a secret this endpoint is |
| 10 | + * open to any POST — restrict access at the network/firewall level. |
6 | 11 | */ |
7 | 12 |
|
8 | 13 | use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
9 | 14 | use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; |
10 | 15 | use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; |
11 | 16 |
|
12 | | -// Allow GET for health check / manual testing |
13 | | -if ($_SERVER['REQUEST_METHOD'] === 'GET') { |
14 | | - header('Content-Type: application/json'); |
15 | | - echo json_encode(['status' => 'ok', 'plugin' => 'gdmsintegration', 'endpoint' => 'webhook']); |
16 | | - return; |
17 | | -} |
18 | | - |
19 | 17 | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { |
20 | | - throw new MethodNotAllowedHttpException(['POST'], 'Only POST requests are accepted'); |
| 18 | + // Silently reject non-POST — no plugin fingerprint disclosed |
| 19 | + http_response_code(405); |
| 20 | + exit; |
21 | 21 | } |
22 | 22 |
|
23 | 23 | $raw = file_get_contents('php://input'); |
|
32 | 32 | $sig_header = $_SERVER['HTTP_X_GDMS_SIGNATURE'] ?? ''; |
33 | 33 | $expected = 'sha256=' . hash_hmac('sha256', $raw, $secret); |
34 | 34 | if (!hash_equals($expected, $sig_header)) { |
35 | | - PluginGdmsintegrationUtils::log("Webhook: signature mismatch from " . ($_SERVER['REMOTE_ADDR'] ?? '')); |
36 | | - throw new AccessDeniedHttpException('Invalid webhook signature'); |
| 35 | + // Return 204 — don't confirm endpoint or leak that signature was checked |
| 36 | + http_response_code(204); |
| 37 | + exit; |
37 | 38 | } |
38 | 39 | } |
39 | 40 |
|
|
42 | 43 | throw new BadRequestHttpException('Invalid JSON payload'); |
43 | 44 | } |
44 | 45 |
|
45 | | -PluginGdmsintegrationUtils::log( |
46 | | - "Webhook received — entity:{$entity} | payload:" . substr(json_encode($payload), 0, 500) |
47 | | -); |
48 | | - |
49 | | -// Process device status change from GDMS cloud |
50 | | -$mac = strtolower(trim($payload['mac'] ?? $payload['device_mac'] ?? $payload['deviceMac'] ?? '')); |
| 46 | +$mac = strtolower(trim($payload['mac'] ?? $payload['device_mac'] ?? $payload['deviceMac'] ?? '')); |
51 | 47 | $rawStatus = strtolower($payload['status'] ?? $payload['deviceStatus'] ?? $payload['event'] ?? ''); |
52 | | -$status = in_array($rawStatus, ['offline', 'disconnect', 'down', '0']) ? 'offline' : 'online'; |
| 48 | +$status = in_array($rawStatus, ['offline', 'disconnect', 'down', '0']) ? 'offline' : 'online'; |
53 | 49 |
|
54 | | -PluginGdmsintegrationUtils::log("Webhook: {$mac} → {$status} (raw: {$rawStatus})"); |
| 50 | +PluginGdmsintegrationUtils::log("Webhook: entity:{$entity} mac:{$mac} status:{$status}"); |
55 | 51 |
|
56 | 52 | if (!empty($mac)) { |
57 | | - // Update state immediately so dashboard reflects it |
58 | | - $state = new PluginGdmsintegrationDevice(); |
| 53 | + $state = new PluginGdmsintegrationDevice(); |
59 | 54 | $prevStatus = $state->getState($mac); |
60 | 55 | $state->saveStateWithNetwork($mac, $status); |
61 | 56 |
|
62 | | - // Fire ticket logic on status transition |
63 | 57 | if ($prevStatus !== $status) { |
64 | | - // Load asset info for ticket creation |
65 | 58 | $assetInfo = null; |
66 | 59 | foreach (['NetworkEquipment', 'Phone'] as $itemtype) { |
67 | 60 | $obj = new $itemtype(); |
|
76 | 69 | $deviceRow = $state->find(['mac' => $mac]); |
77 | 70 | $dr = !empty($deviceRow) ? reset($deviceRow) : []; |
78 | 71 | PluginGdmsintegrationSync::triggerOfflineTicket( |
79 | | - $assetInfo['name'] ?? $mac, |
| 72 | + $assetInfo['name'] ?? $mac, |
80 | 73 | $mac, |
81 | | - $assetInfo['serial'] ?? '', |
| 74 | + $assetInfo['serial'] ?? '', |
82 | 75 | $assetInfo['entities_id'] ?? $entity, |
83 | 76 | $assetInfo['_itemtype'], |
84 | 77 | (int)$assetInfo['id'], |
|
0 commit comments