diff --git a/services/nginx/app/classes/shelly_relay_inventory.php b/services/nginx/app/classes/shelly_relay_inventory.php index 7970cc99..29b30881 100644 --- a/services/nginx/app/classes/shelly_relay_inventory.php +++ b/services/nginx/app/classes/shelly_relay_inventory.php @@ -33,7 +33,7 @@ class shelly_relay_inventory * @return array> * @throws Exception */ - public function listRelayOptions(): array + public function listRelayOptions(bool $include_sensitive_network_details = false): array { $devices_status = $this->fetchOwnedDevicesStatus(); $device_catalog = $this->fetchOwnedDeviceCatalog(); @@ -49,7 +49,8 @@ class shelly_relay_inventory $option = $this->buildRelayOption( $normalized_device, - is_array($catalog_entry) ? $catalog_entry : null + is_array($catalog_entry) ? $catalog_entry : null, + $include_sensitive_network_details ); if ($option === null) { continue; @@ -160,7 +161,11 @@ class shelly_relay_inventory * @param array $device * @return array|null */ - private function buildRelayOption(array $device, ?array $catalog_entry = null): ?array + private function buildRelayOption( + array $device, + ?array $catalog_entry = null, + bool $include_sensitive_network_details = false + ): ?array { if ($device === [] || !$this->isRelayCapableDevice($device)) { return null; @@ -203,9 +208,8 @@ class shelly_relay_inventory $online = $this->normalizeBoolean($catalog_entry['cloud_online'] ?? null); } $status_color = $this->extractStatusColor($online); - $local_ip = $this->extractLocalIp($device, $catalog_entry); - return [ + $option = [ 'id' => $device_id, 'name' => $this->buildRelayLabel( $device_type, @@ -223,10 +227,15 @@ class shelly_relay_inventory 'device_generation' => $device_generation, 'control_type' => $control_type, 'control_name' => $control_name !== '' ? $control_name : null, - 'local_ip' => $local_ip, 'status_color' => $status_color, 'online' => $online, ]; + + if ($include_sensitive_network_details) { + $option['local_ip'] = $this->extractLocalIp($device, $catalog_entry); + } + + return $option; } /** diff --git a/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php b/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php index 48163a9b..9ed88d96 100644 --- a/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php +++ b/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php @@ -3426,7 +3426,7 @@ BASH; } try { - $this->shellyRelayOptionsCache = (new shelly_relay_inventory())->listRelayOptions(); + $this->shellyRelayOptionsCache = (new shelly_relay_inventory())->listRelayOptions(true); } catch (\Throwable) { $this->shellyRelayOptionsCache = []; } diff --git a/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php b/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php index 01152c31..91d339a7 100644 --- a/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php @@ -40,7 +40,7 @@ it('keeps relay dispatch and discovery queueing on the edge gateway manager', fu expect($managerSource)->toContain('private function backfillRelayBindingLocalIpFromInventory'); expect($managerSource)->toContain('$bindingObject->local_ip->set($localIp);'); expect($managerSource)->toContain("'local_ip' => \$localIp"); - expect($managerSource)->toContain('(new shelly_relay_inventory())->listRelayOptions()'); + expect($managerSource)->toContain('(new shelly_relay_inventory())->listRelayOptions(true)'); expect($managerSource)->toContain('public function buildUpdateOperationRequest'); expect($managerSource)->toContain('public function rotateGatewayCredentials'); expect($operationServiceSource)->toContain('public function queueOperation'); diff --git a/services/nginx/app/tests/Unit/Selfserve/ShellyRelayInventoryTest.php b/services/nginx/app/tests/Unit/Selfserve/ShellyRelayInventoryTest.php index d73db9f9..58e09390 100644 --- a/services/nginx/app/tests/Unit/Selfserve/ShellyRelayInventoryTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/ShellyRelayInventoryTest.php @@ -106,7 +106,6 @@ it('normalizes owned Shelly devices into relay select options', function (): voi 'device_generation' => 2, 'control_type' => 'Switch', 'control_name' => 'Entrance Relay', - 'local_ip' => '10.32.0.11', 'status_color' => 'Green', 'online' => true, ], @@ -122,7 +121,6 @@ it('normalizes owned Shelly devices into relay select options', function (): voi 'device_generation' => 2, 'control_type' => 'Relay', 'control_name' => 'Program Picker', - 'local_ip' => '10.32.0.12', 'status_color' => 'Red', 'online' => false, ], @@ -138,7 +136,6 @@ it('normalizes owned Shelly devices into relay select options', function (): voi 'device_generation' => 1, 'control_type' => 'Relay', 'control_name' => null, - 'local_ip' => null, 'status_color' => 'Red', 'online' => false, ], @@ -194,7 +191,6 @@ it('falls back to local device and control names when Shelly cloud list metadata 'device_generation' => 2, 'control_type' => 'Switch', 'control_name' => 'Entrance Relay', - 'local_ip' => '10.32.0.21', 'status_color' => 'Green', 'online' => true, ], @@ -257,12 +253,55 @@ it('keeps Shelly 1 Mini Gen3 type, model, and generation aligned with Shelly met 'device_generation' => 3, 'control_type' => 'Switch', 'control_name' => null, - 'local_ip' => '192.168.1.2', 'status_color' => 'Green', 'online' => true, ]]); }); +it('only includes local IP addresses for internal relay option lookups', function (): void { + $inventory = (new shelly_relay_inventory()) + ->setInventoryFetcher(static function (): array { + return [ + 'isok' => true, + 'data' => [ + 'devices_status' => [ + 'device-key-1' => [ + '_dev_info' => [ + 'id' => 'shelly-plus-01', + 'code' => 'SPSW-001PE16EU', + 'model' => 'Shelly Plus 1PM', + 'online' => 1, + ], + 'wifi_sta' => [ + 'ip' => '10.32.0.11', + ], + 'status' => [ + 'switch:0' => [ + 'output' => false, + ], + ], + ], + ], + ], + ]; + }) + ->setDeviceListFetcher(static function (): array { + return [ + 'isok' => true, + 'data' => [ + 'devices' => [], + ], + ]; + }); + + $publicOptions = $inventory->listRelayOptions(); + $internalOptions = $inventory->listRelayOptions(true); + + expect($publicOptions[0])->not->toHaveKey('local_ip'); + expect($internalOptions[0])->toHaveKey('local_ip'); + expect($internalOptions[0]['local_ip'])->toBe('10.32.0.11'); +}); + it('fails fast when Shelly inventory does not include owned devices status', function (): void { $inventory = (new shelly_relay_inventory())->setInventoryFetcher(static function (): array { return [