feat: implementar sistema de validación de IPs de 4 capas v1.6.0
- Agregar validación multi-capa para garantizar IPs 100% disponibles - Capa 1: Filtrar IPs de dispositivos registrados en UISP - Capa 2: Filtrar IPs de servicios CRM suspendidos/finalizados (449 IPs) - Capa 3: Validar IPs usando búsqueda de sitios con type=subscriber - Capa 4: Verificación opcional por ping para dispositivos de terceros Nuevos Componentes: - src/IpValidator.php: Clase de validación por búsqueda de sitios - Validación progresiva en frontend JavaScript - Endpoint action=validate en public.php Mejoras: - CrmService.php: Filtrado mejorado de servicios suspendidos/finalizados - IpSearchService.php: Extraer todas las IPs de ipAddressList - Frontend: Validación progresiva de IPs con feedback visual - Cambiar suspended=false a suspended=true en API de UISP Correcciones: - Corregir variable $config indefinida en handler de validación - Corregir falsos positivos agregando parámetro type=subscriber - Corregir IPs secundarias no detectadas - Corregir IPs de servicios suspendidos/finalizados mostrándose disponibles Resultados de Pruebas: - IP 172.16.54.70 (servicio finalizado): Filtrada correctamente por capa CRM - IP 172.16.54.58 (cliente activo): Filtrada correctamente por búsqueda de sitios - Todas las IPs mostradas verificadas como 100% disponibles Documentación: - Actualizado README.md con detalles del sistema de 4 capas - Actualizado CHANGELOG.md con notas de versión 1.6.0 - Creado walkthrough.md completo
This commit is contained in:
parent
7c14f47ff3
commit
ecee38cf96
51
CHANGELOG.md
51
CHANGELOG.md
@ -7,6 +7,57 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.6.0] - 2025-12-06
|
||||||
|
|
||||||
|
### ✨ Añadido
|
||||||
|
- **Sistema de Validación Multi-Capa de IPs**: Implementación completa de 4 capas de validación para garantizar IPs 100% disponibles.
|
||||||
|
- **Capa 1 - Filtrado UISP**: Filtra IPs de dispositivos registrados en UISP
|
||||||
|
- **Capa 2 - Filtrado CRM**: Filtra IPs de servicios suspendidos/finalizados
|
||||||
|
- **Capa 3 - Validación por Búsqueda de Sitios**: Detecta IPs de clientes activos usando `/sites/search?type=subscriber`
|
||||||
|
- **Capa 4 - Verificación por Ping**: Detecta dispositivos de terceros (opcional)
|
||||||
|
- **IpValidator.php**: Nueva clase para validación de IPs usando búsqueda de sitios en UISP.
|
||||||
|
- **Validación Progresiva en Frontend**: Las IPs se validan una por una antes de mostrarse en la tabla.
|
||||||
|
- **Endpoint API `action=validate`**: Nuevo endpoint para validar IPs individuales.
|
||||||
|
- **Parámetro `type=subscriber`**: Búsqueda específica en sitios de clientes para evitar falsos positivos.
|
||||||
|
|
||||||
|
### 🔧 Mejorado
|
||||||
|
- **CrmService.php**: Filtrado mejorado de servicios suspendidos y finalizados.
|
||||||
|
- Mapeo de IPs a sitios bloqueados
|
||||||
|
- Extracción de `unmsClientSiteId` de servicios
|
||||||
|
- Logging detallado del proceso de filtrado
|
||||||
|
- **IpSearchService.php**:
|
||||||
|
- Integración con CrmService para filtrado de servicios
|
||||||
|
- Parámetro `suspended=true` en API de UISP para incluir dispositivos suspendidos
|
||||||
|
- Extracción de todas las IPs de `ipAddressList` además de IP principal
|
||||||
|
- **public.php**:
|
||||||
|
- Handler para `action=validate` con carga correcta de configuración
|
||||||
|
- Integración de validación progresiva en frontend
|
||||||
|
- **Frontend JavaScript**:
|
||||||
|
- Función `runProgressiveValidation()` para validar IPs progresivamente
|
||||||
|
- Función `updateRowStatus()` para actualizar estado de filas
|
||||||
|
- Solo renderiza IPs que pasan todas las validaciones
|
||||||
|
- Mejor feedback visual durante validación
|
||||||
|
|
||||||
|
### 🐛 Corregido
|
||||||
|
- **Bug de variable `$config` indefinida**: Corregido cargando configuración dentro del handler de validación.
|
||||||
|
- **Falsos positivos en búsqueda de sitios**: Solucionado agregando `type=subscriber` al endpoint.
|
||||||
|
- **IPs secundarias no detectadas**: Ahora se extraen todas las IPs de `ipAddressList`.
|
||||||
|
- **Servicios suspendidos/finalizados mostrando IPs disponibles**: Filtrado completo por estado de servicio CRM.
|
||||||
|
|
||||||
|
### 📝 Documentación
|
||||||
|
- **README.md**: Sección completa sobre "Sistema de Validación Multi-Capa de IPs" con:
|
||||||
|
- Descripción detallada de cada capa
|
||||||
|
- Diagrama de flujo completo de validación
|
||||||
|
- Casos de uso y ejemplos
|
||||||
|
- **Logs mejorados**: Mensajes detallados en cada capa de validación para debugging.
|
||||||
|
|
||||||
|
### 🎯 Casos de Uso Resueltos
|
||||||
|
- ✅ IPs de servicios finalizados ya no aparecen como disponibles
|
||||||
|
- ✅ IPs de servicios suspendidos ya no aparecen como disponibles
|
||||||
|
- ✅ IPs de clientes activos (no en `/devices/ips`) son detectadas y filtradas
|
||||||
|
- ✅ IPs secundarias de dispositivos son detectadas correctamente
|
||||||
|
- ✅ Dispositivos de terceros son detectados con ping opcional
|
||||||
|
|
||||||
## [1.5.1] - 2025-12-01
|
## [1.5.1] - 2025-12-01
|
||||||
|
|
||||||
### ✨ Añadido
|
### ✨ Añadido
|
||||||
|
|||||||
139
README.md
139
README.md
@ -164,7 +164,112 @@ Si está habilitada en la configuración, aparecerá un checkbox "🔍 Verificar
|
|||||||
- ✅ **Disponible**: La IP está libre en UISP y no responde a ping.
|
- ✅ **Disponible**: La IP está libre en UISP y no responde a ping.
|
||||||
- ⚠️ **En uso (Ping)**: La IP está libre en UISP pero responde a ping (posible conflicto).
|
- ⚠️ **En uso (Ping)**: La IP está libre en UISP pero responde a ping (posible conflicto).
|
||||||
|
|
||||||
### Filtrado de IPs
|
### Sistema de Validación Multi-Capa de IPs
|
||||||
|
|
||||||
|
El plugin implementa un **sistema de validación de 4 capas** para garantizar que solo se muestren IPs 100% disponibles:
|
||||||
|
|
||||||
|
#### 🔍 Capa 1: Filtrado UISP (Dispositivos Registrados)
|
||||||
|
- **Endpoint**: `/nms/api/v2.1/devices/ips`
|
||||||
|
- **Función**: Filtra IPs de dispositivos registrados en UISP
|
||||||
|
- **Incluye**:
|
||||||
|
- Dispositivos activos y suspendidos
|
||||||
|
- Dispositivos de gestión
|
||||||
|
- Dispositivos obsoletos
|
||||||
|
- **Resultado**: Elimina IPs que ya están asignadas a dispositivos en UISP
|
||||||
|
|
||||||
|
#### 🏢 Capa 2: Filtrado CRM (Servicios Suspendidos/Finalizados)
|
||||||
|
- **Endpoint**: `/crm/api/v1.0/clients/services`
|
||||||
|
- **Función**: Filtra IPs de clientes con servicios problemáticos
|
||||||
|
- **Estados filtrados**:
|
||||||
|
- `status=2`: Servicios finalizados (Ended)
|
||||||
|
- `status=3`: Servicios suspendidos (Suspended)
|
||||||
|
- **Proceso**:
|
||||||
|
1. Obtiene servicios con estados 2 y 3
|
||||||
|
2. Extrae `unmsClientSiteId` de cada servicio
|
||||||
|
3. Mapea IPs de UISP a sitios
|
||||||
|
4. Filtra IPs que pertenecen a sitios bloqueados
|
||||||
|
- **Resultado**: Elimina IPs de clientes con servicios suspendidos o finalizados (aunque el dispositivo aún esté en UISP)
|
||||||
|
|
||||||
|
#### 🌐 Capa 3: Validación por Búsqueda de Sitios (Clientes Activos)
|
||||||
|
- **Endpoint**: `/nms/api/v2.1/sites/search?type=subscriber`
|
||||||
|
- **Función**: Detecta IPs de clientes activos que no aparecen en `/devices/ips`
|
||||||
|
- **Proceso**:
|
||||||
|
1. Para cada IP "disponible", busca sitios de tipo `subscriber`
|
||||||
|
2. Si encuentra un sitio, obtiene sus dispositivos
|
||||||
|
3. Verifica si algún dispositivo tiene exactamente esa IP
|
||||||
|
4. Si encuentra coincidencia exacta, marca la IP como "en uso"
|
||||||
|
- **Casos detectados**:
|
||||||
|
- IPs secundarias de dispositivos (no aparecen en IP principal)
|
||||||
|
- Dispositivos con IPs en `ipAddressList`
|
||||||
|
- Clientes activos con dispositivos no sincronizados
|
||||||
|
- **Resultado**: Elimina IPs que están en uso por clientes activos pero no detectadas por las capas anteriores
|
||||||
|
|
||||||
|
#### 🏓 Capa 4: Verificación por Ping (Opcional)
|
||||||
|
- **Función**: Detecta dispositivos de terceros no registrados en UISP
|
||||||
|
- **Proceso**:
|
||||||
|
1. Usuario marca checkbox "Verificar con ping"
|
||||||
|
2. Selecciona cantidad de IPs a verificar (10, 20, 50, Todas)
|
||||||
|
3. El sistema hace ping a cada IP progresivamente
|
||||||
|
4. Marca como "En uso (Ping)" las IPs que responden
|
||||||
|
- **Casos detectados**:
|
||||||
|
- Dispositivos de terceros (no clientes)
|
||||||
|
- Equipos de red no registrados
|
||||||
|
- Dispositivos temporales
|
||||||
|
- **Resultado**: Solo muestra IPs que NO responden a ping
|
||||||
|
|
||||||
|
### Flujo Completo de Validación
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Usuario busca IPs en segmento 172.16.54.x │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 1: Filtrado UISP │
|
||||||
|
│ ✓ Obtiene IPs de /devices/ips │
|
||||||
|
│ ✓ Filtra IPs en uso por dispositivos registrados │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 2: Filtrado CRM │
|
||||||
|
│ ✓ Obtiene servicios suspendidos/finalizados │
|
||||||
|
│ ✓ Mapea IPs a sitios bloqueados │
|
||||||
|
│ ✓ Filtra IPs de servicios problemáticos │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 3: Validación por Búsqueda de Sitios │
|
||||||
|
│ ✓ Para cada IP "disponible": │
|
||||||
|
│ - Busca sitio con /sites/search?type=subscriber │
|
||||||
|
│ - Verifica dispositivos del sitio │
|
||||||
|
│ - Comprueba coincidencia exacta de IP │
|
||||||
|
│ ✓ Filtra IPs de clientes activos │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 4: Verificación por Ping (Opcional) │
|
||||||
|
│ ✓ Usuario marca "Verificar con ping" │
|
||||||
|
│ ✓ Hace ping a cada IP progresivamente │
|
||||||
|
│ ✓ Filtra IPs que responden │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ RESULTADO: Solo IPs 100% Disponibles │
|
||||||
|
│ ✅ No están en UISP │
|
||||||
|
│ ✅ No pertenecen a servicios suspendidos/finalizados │
|
||||||
|
│ ✅ No están en uso por clientes activos │
|
||||||
|
│ ✅ No responden a ping (si está habilitado) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuración de Filtros
|
||||||
|
|
||||||
|
#### Filtrado Administrativo
|
||||||
- **Ocultar Admin IPs**: Checkbox para ocultar/mostrar instantáneamente las IPs reservadas para administración (x.x.x.1-30 y x.x.x.254).
|
- **Ocultar Admin IPs**: Checkbox para ocultar/mostrar instantáneamente las IPs reservadas para administración (x.x.x.1-30 y x.x.x.254).
|
||||||
|
|
||||||
### Cancelar Verificación
|
### Cancelar Verificación
|
||||||
@ -368,6 +473,38 @@ Verifica si una IP específica está disponible o en uso.
|
|||||||
**Parámetros**:
|
**Parámetros**:
|
||||||
- `type` (string, requerido): Tipo de evento, debe ser `"event.ip_check"`
|
- `type` (string, requerido): Tipo de evento, debe ser `"event.ip_check"`
|
||||||
- `ip` (string, requerido): Dirección IP completa en formato `172.16.X.X`
|
- `ip` (string, requerido): Dirección IP completa en formato `172.16.X.X`
|
||||||
|
- `verify_ping` (boolean, opcional): Si es `true`, verifica si la IP responde a ping (default: `false`)
|
||||||
|
|
||||||
|
**Ejemplo con verificación de ping**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "event.ip_check",
|
||||||
|
"ip": "172.16.5.70",
|
||||||
|
"verify_ping": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response con ping**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"event": "event.ip_check",
|
||||||
|
"ip": "172.16.5.70",
|
||||||
|
"status": "available",
|
||||||
|
"available": true,
|
||||||
|
"used": false,
|
||||||
|
"ip_type": {
|
||||||
|
"type": "client",
|
||||||
|
"label": "Apta para cliente",
|
||||||
|
"recommended": true,
|
||||||
|
"description": "Recomendada para asignar a clientes"
|
||||||
|
},
|
||||||
|
"ping_verified": true,
|
||||||
|
"ping_responds": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Nota**: Si `ping_responds` es `true` pero `available` también es `true`, se incluirá un campo `warning` indicando que la IP responde a ping pero no está registrada en UISP (posible conflicto).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1 @@
|
|||||||
{
|
{"ipserver":"sistema.siip.mx","apitoken":"72VoFACForJQzveorR1sTLrXXwrnnK/oy6Bp9luwFTGC/dRdeQWNmFZqJeHuUzqK","unmsApiToken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","debugMode":true,"logging_level":true,"enablePingVerification":true,"adminRangeStart":"2","adminRangeEnd":"50","adminRangeFinalStart":"254","adminRangeFinalEnd":"255","useCustomAdminRanges":true,"customAdminRangesJson":"{\"segmentos\":[{\"segmento\":\"13\",\"administrativas_iniciales\":[{\"inicio\":2,\"hasta\":50}],\"administrativas_finales\":[{\"inicio\":253,\"hasta\":254}]},{\"segmento\":\"100\",\"administrativas_iniciales\":[{\"inicio\":2,\"hasta\":50}],\"administrativas_finales\":[{\"inicio\":254,\"hasta\":254}]}]}","adminPassword":"Siip.2963"}
|
||||||
"ipserver": "sistema.siip.mx",
|
|
||||||
"apitoken": "393eb3d0-9b46-4a47-b9b4-473e4e24a89c",
|
|
||||||
"unmsApiToken": "393eb3d0-9b46-4a47-b9b4-473e4e24a89c",
|
|
||||||
"debugMode": true,
|
|
||||||
"logging_level": true,
|
|
||||||
"enablePingVerification": true,
|
|
||||||
"adminRangeStart": "2",
|
|
||||||
"adminRangeEnd": "50",
|
|
||||||
"adminRangeFinalStart": "254",
|
|
||||||
"adminRangeFinalEnd": "255",
|
|
||||||
"useCustomAdminRanges": true,
|
|
||||||
"customAdminRangesJson": "{\"segmentos\":[{\"segmento\":\"13\",\"administrativas_iniciales\":[{\"inicio\":2,\"hasta\":50}],\"administrativas_finales\":[{\"inicio\":253,\"hasta\":254}]},{\"segmento\":\"100\",\"administrativas_iniciales\":[{\"inicio\":2,\"hasta\":50}],\"administrativas_finales\":[{\"inicio\":254,\"hasta\":254}]}]}",
|
|
||||||
"adminPassword": "Siip.2963"
|
|
||||||
}
|
|
||||||
1
data/devices_debug.json
Normal file
1
data/devices_debug.json
Normal file
File diff suppressed because one or more lines are too long
33522
data/plugin.log
33522
data/plugin.log
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
|||||||
"displayName": "SIIP - Buscador de IP's Disponibles UISP",
|
"displayName": "SIIP - Buscador de IP's Disponibles UISP",
|
||||||
"description": "Este plugin permite buscar IP's disponibles en UISP (UNMS) y asignarlas a los clientes en UCRM. Evitando así la asignación de IP's duplicadas y mejorando la gestión de direcciones IP en la red.",
|
"description": "Este plugin permite buscar IP's disponibles en UISP (UNMS) y asignarlas a los clientes en UCRM. Evitando así la asignación de IP's duplicadas y mejorando la gestión de direcciones IP en la red.",
|
||||||
"url": "https://siip.mx",
|
"url": "https://siip.mx",
|
||||||
"version": "1.5.1",
|
"version": "1.6.0",
|
||||||
"ucrmVersionCompliancy": {
|
"ucrmVersionCompliancy": {
|
||||||
"min": "1.0.0",
|
"min": "1.0.0",
|
||||||
"max": null
|
"max": null
|
||||||
|
|||||||
151
public.php
151
public.php
@ -29,6 +29,8 @@ chdir(__DIR__);
|
|||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
require_once __DIR__ . '/src/IpSearchService.php';
|
require_once __DIR__ . '/src/IpSearchService.php';
|
||||||
require_once __DIR__ . '/src/PingService.php';
|
require_once __DIR__ . '/src/PingService.php';
|
||||||
|
require_once __DIR__ . '/src/CrmService.php';
|
||||||
|
require_once __DIR__ . '/src/IpValidator.php';
|
||||||
require_once __DIR__ . '/src/ApiHandlers.php';
|
require_once __DIR__ . '/src/ApiHandlers.php';
|
||||||
require_once __DIR__ . '/src/AdminRangeHelper.php';
|
require_once __DIR__ . '/src/AdminRangeHelper.php';
|
||||||
|
|
||||||
@ -170,13 +172,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
// URL de la API de UISP - Usar HTTPS
|
// URL de la API de UISP - Usar HTTPS
|
||||||
|
|
||||||
// URL de la API de UISP - Usar HTTPS
|
// URL de la API de UISP - Usar HTTPS
|
||||||
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true";
|
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=true&management=true&includeObsolete=true";
|
||||||
$apiToken = $config['unmsApiToken'];
|
$apiToken = $config['unmsApiToken'];
|
||||||
|
|
||||||
$log->appendLog("URL de API: $apiUrl");
|
$log->appendLog("URL de API: $apiUrl");
|
||||||
|
|
||||||
// Crear instancia del servicio y buscar IPs (SIN ping, el ping se hace después)
|
// Crear instancia del servicio y buscar IPs (SIN ping, el ping se hace después)
|
||||||
$ipService = new IpSearchService($apiUrl, $apiToken, $log);
|
// IMPORTANTE: Pasar $config completo para que tenga acceso a apitoken e ipserver para CRM
|
||||||
|
$ipService = new IpSearchService($apiUrl, $apiToken, $log, $config);
|
||||||
$resultado = $ipService->buscarIpsDisponibles($segmento, false);
|
$resultado = $ipService->buscarIpsDisponibles($segmento, false);
|
||||||
|
|
||||||
$log->appendLog('Resultado de búsqueda: ' . json_encode([
|
$log->appendLog('Resultado de búsqueda: ' . json_encode([
|
||||||
@ -258,6 +261,48 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
|
} else if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'validate') {
|
||||||
|
// Handler para validación individual de IP con búsqueda de sitios
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$ip = $_POST['ip'] ?? '';
|
||||||
|
|
||||||
|
if (empty($ip)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing IP']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar configuración
|
||||||
|
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||||
|
$config = $configManager->loadConfig();
|
||||||
|
|
||||||
|
// Crear instancia del validador
|
||||||
|
$baseUrl = 'https://' . $config['ipserver'];
|
||||||
|
$ipValidator = new \SiipAvailableIps\IpValidator(
|
||||||
|
$baseUrl,
|
||||||
|
$config['unmsApiToken'],
|
||||||
|
$log
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validar si la IP está en uso
|
||||||
|
$isInUse = $ipValidator->isIpInUse($ip);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'ip' => $ip,
|
||||||
|
'in_use' => $isInUse,
|
||||||
|
'available' => !$isInUse
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$log->appendLog('Error en validación: ' . $e->getMessage());
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$log->appendLog('Petición POST recibida pero sin action=search o action no válida');
|
$log->appendLog('Petición POST recibida pero sin action=search o action no válida');
|
||||||
$log->appendLog('Action recibida: ' . ($_POST['action'] ?? 'NO DEFINIDA'));
|
$log->appendLog('Action recibida: ' . ($_POST['action'] ?? 'NO DEFINIDA'));
|
||||||
@ -1820,17 +1865,18 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
const clientIps = displayResults(data);
|
const clientIps = displayResults(data);
|
||||||
|
|
||||||
// Iniciar verificación progresiva si corresponde
|
// Validación progresiva con búsqueda de sitios
|
||||||
<?php if ($pingEnabled): ?>
|
<?php if ($pingEnabled): ?>
|
||||||
if (shouldVerifyPing) {
|
if (shouldVerifyPing) {
|
||||||
runProgressiveVerification(clientIps, pingLimit);
|
// Si ping está marcado, validar con site search y luego con ping
|
||||||
|
runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing);
|
||||||
} else {
|
} else {
|
||||||
// Si no se verifica con ping, mostrar todas las IPs de cliente de inmediato
|
// Si ping NO está marcado, solo validar con site search
|
||||||
clientIps.forEach(ip => renderRow(ip, 'Sólo validada con UISP', 'pending'));
|
runProgressiveValidation(clientIps, 0, false);
|
||||||
}
|
}
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
// Si ping no está habilitado en config, mostrar todas
|
// Si ping no está habilitado en config, validar con site search
|
||||||
clientIps.forEach(ip => renderRow(ip, 'Sólo validada con UISP', 'pending'));
|
runProgressiveValidation(clientIps, 0, false);
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
} else {
|
} else {
|
||||||
showError(data.message || 'Error al buscar IPs disponibles');
|
showError(data.message || 'Error al buscar IPs disponibles');
|
||||||
@ -2013,6 +2059,95 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
|||||||
return 'Cliente';
|
return 'Cliente';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validación progresiva con búsqueda de sitios (event.ip_validate)
|
||||||
|
* Valida cada IP antes de mostrarla en la tabla
|
||||||
|
*/
|
||||||
|
async function runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing) {
|
||||||
|
console.log(`Iniciando validación progresiva de ${clientIps.length} IPs`);
|
||||||
|
console.log(`Ping habilitado: ${shouldVerifyPing}, Límite: ${pingLimit}`);
|
||||||
|
|
||||||
|
// Mostrar botón de cancelar
|
||||||
|
verificationCancelled = false;
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.style.display = 'inline-flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
let validatedIps = [];
|
||||||
|
|
||||||
|
// Procesar cada IP secuencialmente
|
||||||
|
for (const ip of clientIps) {
|
||||||
|
// Verificar si el usuario canceló
|
||||||
|
if (verificationCancelled) {
|
||||||
|
console.log('Validación cancelada por el usuario');
|
||||||
|
showError('Validación cancelada. Mostrando resultados parciales.', 'warning');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO renderizar aún, primero validar
|
||||||
|
|
||||||
|
// Scroll al final de la tabla
|
||||||
|
const tableContainer = document.querySelector('.table-container');
|
||||||
|
if (tableContainer) {
|
||||||
|
tableContainer.scrollTop = tableContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Llamar a validación de IP con búsqueda de sitios
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('action', 'validate');
|
||||||
|
formData.append('ip', ip);
|
||||||
|
|
||||||
|
const response = await fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && !data.in_use) {
|
||||||
|
// IP disponible según site search
|
||||||
|
validatedIps.push(ip);
|
||||||
|
|
||||||
|
if (shouldVerifyPing) {
|
||||||
|
// Si ping está habilitado, renderizar y verificar con ping
|
||||||
|
renderRow(ip, '⏳ Verificando ping...', 'verifying');
|
||||||
|
await verifyBatch([ip]);
|
||||||
|
} else {
|
||||||
|
// Si ping NO está habilitado, renderizar como disponible
|
||||||
|
renderRow(ip, '✅ Disponible', 'available');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// IP en uso según site search - NO RENDERIZAR
|
||||||
|
// Solo agregar log en consola
|
||||||
|
console.log(`IP ${ip} filtrada (en uso en UISP)`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error validando IP ${ip}:`, error);
|
||||||
|
// En caso de error, renderizar con error
|
||||||
|
renderRow(ip, '❌ Error validación', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ocultar botón de cancelar
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Validación completada. ${validatedIps.length} IPs disponibles de ${clientIps.length} totales`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actualizar el estado de una fila existente
|
||||||
|
*/
|
||||||
|
function updateRowStatus(ip, statusText, statusClass) {
|
||||||
|
const statusCell = document.getElementById(`status-${ip.replace(/\./g, '-')}`);
|
||||||
|
if (statusCell) {
|
||||||
|
statusCell.innerHTML = `<span class="status-badge status-${statusClass}">${statusText}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function runProgressiveVerification(clientIps, limit) {
|
async function runProgressiveVerification(clientIps, limit) {
|
||||||
let ipsToVerify = [];
|
let ipsToVerify = [];
|
||||||
|
|
||||||
|
|||||||
983
public_plugin_buscador_passwords.php
Executable file
983
public_plugin_buscador_passwords.php
Executable file
@ -0,0 +1,983 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// --- SERVIR IMÁGENES DESDE LA URL ---
|
||||||
|
if (isset($_GET['img'])) {
|
||||||
|
$imageName = basename($_GET['img']);
|
||||||
|
$imagePath = __DIR__ . '/img/' . $imageName;
|
||||||
|
if (file_exists($imagePath)) {
|
||||||
|
$mimeType = mime_content_type($imagePath);
|
||||||
|
header('Content-Type: ' . $mimeType);
|
||||||
|
readfile($imagePath);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
echo 'Imagen no encontrada';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- SERVIR CONTRASEÑA GENERADA POR AJAX ---
|
||||||
|
if (isset($_GET['generate_password'])) {
|
||||||
|
echo generate_strong_password();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ENDPOINT PARA ACTUALIZAR CONTRASEÑA DEL CLIENTE ---
|
||||||
|
if (isset($_POST['update_password']) && isset($_POST['clientId']) && isset($_POST['password'])) {
|
||||||
|
// log
|
||||||
|
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
$logger->appendLog('POST update_password recibido: ' . var_export($_POST, true));
|
||||||
|
// lógica PATCH
|
||||||
|
$result = updateClientPasswordAttribute($_POST['clientId'], $_POST['password']);
|
||||||
|
$logger->appendLog('Resultado updateClientPasswordAttribute: ' . var_export($result, true));
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($result);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
chdir(__DIR__);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
// --- ENDPOINT PARA ACTUALIZAR CONTRASEÑA DEL CLIENTE ---
|
||||||
|
if (isset($_POST['update_password']) && isset($_POST['clientId']) && isset($_POST['password'])) {
|
||||||
|
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
$logger->appendLog('POST update_password recibido: ' . var_export($_POST, true));
|
||||||
|
$clientId = $_POST['clientId'];
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$result = updateClientPasswordAttribute($clientId, $password);
|
||||||
|
$logger->appendLog('Resultado updateClientPasswordAttribute: ' . var_export($result, true));
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($result);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get UCRM log manager.
|
||||||
|
$log = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
// $log->appendLog(
|
||||||
|
// sprintf(
|
||||||
|
// 'Executed from public URL: %s',
|
||||||
|
// file_get_contents('php://input')
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
// Variable para almacenar la respuesta de la API, mensajes de error y estado
|
||||||
|
|
||||||
|
$respuestaAPI = '';
|
||||||
|
$esError = false;
|
||||||
|
|
||||||
|
$clientFullName = '';
|
||||||
|
$IpAddressClientId = '';
|
||||||
|
$clientIdentifierType = 'ID'; // Puede ser 'ID', 'IP', 'MAC'
|
||||||
|
|
||||||
|
//variables para mostrar info del cliente
|
||||||
|
$clientFullName = '';
|
||||||
|
$IpAddressClientId = '';
|
||||||
|
|
||||||
|
// Ensure global variables are initialized at the top of the script
|
||||||
|
$clientFullName = $clientFullName ?? '';
|
||||||
|
$IpAddressClientId = $IpAddressClientId ?? '';
|
||||||
|
|
||||||
|
// Verificar si se ha enviado el formulario
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
/**
|
||||||
|
* Actualiza el atributo personalizado 'passwordAntenaCliente' de un cliente en UCRM.
|
||||||
|
*
|
||||||
|
* @param int|string $clientId ID del cliente
|
||||||
|
* @param string $newPassword Nueva contraseña a setear
|
||||||
|
* @return array [success => bool, message => string]
|
||||||
|
*/
|
||||||
|
function updateClientPasswordAttribute($clientId, $newPassword) {
|
||||||
|
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||||
|
$config = $configManager->loadConfig();
|
||||||
|
$baseUri = $config['ipserver'];
|
||||||
|
$crmAuthToken = $config['apitoken']; // O $config['ucrmApiToken'] si así lo tienes
|
||||||
|
$clientUcrm = new \GuzzleHttp\Client([
|
||||||
|
'base_uri' => 'https://' . $baseUri . '/crm/api/v1.0/',
|
||||||
|
'verify' => false
|
||||||
|
]);
|
||||||
|
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Obtener el cliente actual
|
||||||
|
$response = $clientUcrm->get('clients/' . $clientId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $crmAuthToken,
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
if ($response->getStatusCode() !== 200) {
|
||||||
|
return ['success' => false, 'message' => 'No se pudo obtener el cliente.'];
|
||||||
|
}
|
||||||
|
$clientData = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
// 2. Buscar el atributo personalizado 'passwordAntenaCliente'
|
||||||
|
$found = false;
|
||||||
|
$attributes = [];
|
||||||
|
if (isset($clientData['attributes']) && is_array($clientData['attributes'])) {
|
||||||
|
foreach ($clientData['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'passwordAntenaCliente') {
|
||||||
|
$attr['value'] = $newPassword;
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
$attributes[] = $attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
return ['success' => false, 'message' => 'No se encontró el atributo personalizado passwordAntenaCliente.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Actualizar el cliente completo usando PATCH
|
||||||
|
// Solo enviar el array attributes con customAttributeId y value
|
||||||
|
$logger->appendLog('PATCH endpoint: clients/' . $clientId);
|
||||||
|
$patchBody = [
|
||||||
|
'attributes' => [
|
||||||
|
[
|
||||||
|
'customAttributeId' => 17,
|
||||||
|
'value' => $newPassword
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$logger->appendLog('PATCH body: ' . json_encode($patchBody));
|
||||||
|
try {
|
||||||
|
$patchResponse = $clientUcrm->patch('clients/' . $clientId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $crmAuthToken,
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
],
|
||||||
|
'json' => $patchBody
|
||||||
|
]);
|
||||||
|
$logger->appendLog('PATCH status: ' . $patchResponse->getStatusCode());
|
||||||
|
$logger->appendLog('PATCH response: ' . $patchResponse->getBody());
|
||||||
|
if ($patchResponse->getStatusCode() === 200) {
|
||||||
|
return ['success' => true, 'message' => 'Contraseña actualizada correctamente.'];
|
||||||
|
} else {
|
||||||
|
return ['success' => false, 'message' => 'Error al actualizar el cliente.'];
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$logger->appendLog('PATCH exception: ' . $e->getMessage());
|
||||||
|
return ['success' => false, 'message' => 'Excepción: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$logger->appendLog('PATCH attributes exception: ' . $e->getMessage());
|
||||||
|
return ['success' => false, 'message' => 'Excepción: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entrada = trim($_POST['identificador']);
|
||||||
|
|
||||||
|
// Validar si es una dirección IP válida
|
||||||
|
if (filter_var($entrada, FILTER_VALIDATE_IP)) {
|
||||||
|
// Es una dirección IP válida
|
||||||
|
$respuestaAPI = getVaultCredentials($entrada);
|
||||||
|
}
|
||||||
|
// Validar si es una dirección MAC válida
|
||||||
|
elseif (preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $entrada)) {
|
||||||
|
// Es una dirección MAC válida
|
||||||
|
$respuestaAPI = getVaultCredentials($entrada);
|
||||||
|
}
|
||||||
|
// Validar si es un ID numérico menor a 10,000
|
||||||
|
elseif (ctype_digit($entrada) && intval($entrada) < 10000) {
|
||||||
|
// Es un ID válido
|
||||||
|
$respuestaAPI = getVaultCredentials($entrada);
|
||||||
|
} else {
|
||||||
|
// Entrada no válida
|
||||||
|
$mensajeError = "La entrada no es válida. Por favor, ingresa una dirección IP, una dirección MAC o un ID menor a 10,000.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si la respuesta de la API indica un error
|
||||||
|
if (strpos($respuestaAPI, "Error:") === 0) {
|
||||||
|
$esError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNewPassword($clientId, $newPassword)
|
||||||
|
{
|
||||||
|
// Aquí iría la lógica para actualizar la contraseña del cliente en UCRM
|
||||||
|
// Usando la API de UCRM y el cliente Guzzle
|
||||||
|
// Retornar true si se actualizó correctamente, false en caso contrario
|
||||||
|
global $clientFullName, $IpAddressClientId;
|
||||||
|
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||||
|
$config = $configManager->loadConfig();
|
||||||
|
$baseUri = $config['ipserver'];
|
||||||
|
$crmAuthToken = $config['apitoken'];
|
||||||
|
$unmsAuthToken = $config['unmsApiToken'];
|
||||||
|
$ucrmBaseUri = 'https://' . $baseUri . '/crm/api/v1.0/';
|
||||||
|
|
||||||
|
$clientUcrm = new Client([
|
||||||
|
'base_uri' => $ucrmBaseUri,
|
||||||
|
'verify' => false // Deshabilitar la verificación del certificado SSL
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$responseClient = $clientUcrm->patch('clients/' . $clientId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $crmAuthToken,
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
],
|
||||||
|
'json' => [
|
||||||
|
'attributes' => [
|
||||||
|
[
|
||||||
|
'customAttributeId' => 17, // ID del atributo personalizado 'passwordAntenaCliente'
|
||||||
|
'value' => $newPassword
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($responseClient->getStatusCode() === 200) {
|
||||||
|
$logger->appendLog('Contraseña actualizada correctamente para el cliente: ' . $clientFullName . ' (ID: ' . $IpAddressClientId . ')');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$logger->appendLog('Error al actualizar la contraseña. Código de estado HTTP: ' . $responseClient->getStatusCode());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
$logger->appendLog('Excepción al actualizar la contraseña: ' . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
function getVaultCredentials($dataToSearch): string
|
||||||
|
{
|
||||||
|
global $clientFullName, $IpAddressClientId, $clientIdentifierType;
|
||||||
|
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
|
||||||
|
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||||
|
$config = $configManager->loadConfig();
|
||||||
|
$baseUri = $config['ipserver'];
|
||||||
|
$crmAuthToken = $config['apitoken'];
|
||||||
|
$unmsAuthToken = $config['unmsApiToken'];
|
||||||
|
|
||||||
|
|
||||||
|
$unmsBaseUri = 'https://' . $baseUri . '/nms/api/v2.1/';
|
||||||
|
$ucrmBaseUri = 'https://' . $baseUri . '/crm/api/v1.0/';
|
||||||
|
//$authToken = '7adc9198-50b1-41d0-9bfa-d4946902ed89';
|
||||||
|
|
||||||
|
// Crear una instancia del cliente Guzzle
|
||||||
|
$clientUnms = new Client([
|
||||||
|
'base_uri' => $unmsBaseUri,
|
||||||
|
'verify' => false // Deshabilitar la verificación del certificado SSL
|
||||||
|
]);
|
||||||
|
|
||||||
|
$clientUcrm = new Client([
|
||||||
|
'base_uri' => $ucrmBaseUri,
|
||||||
|
'verify' => false // Deshabilitar la verificación del certificado SSL
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
//validar si dataToSearch es una dirección IP o una dirección MAC
|
||||||
|
if (filter_var($dataToSearch, FILTER_VALIDATE_IP)) {
|
||||||
|
$clientIdentifierType = 'IP';
|
||||||
|
// La variable es una dirección IP válida
|
||||||
|
$logger->appendLog('Consulta por dirección IP: ' . $dataToSearch);
|
||||||
|
|
||||||
|
$IpAddressClientId = filter_var($dataToSearch, FILTER_VALIDATE_IP);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$responseSitesByIP = $clientUnms->request('GET', 'sites/search?query=' . $dataToSearch . '&count=1&page=1', [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]); //Sites por IP
|
||||||
|
|
||||||
|
if ($responseSitesByIP->getStatusCode() === 200) {
|
||||||
|
|
||||||
|
$datasSitesByIP = json_decode($responseSitesByIP->getBody(), true);
|
||||||
|
$jsonDevicesBySite = json_encode($datasSitesByIP, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
|
||||||
|
|
||||||
|
if (isset($datasSitesByIP[0])) {
|
||||||
|
$siteId = $datasSitesByIP[0]['id'];
|
||||||
|
// print_r('ID DEL SITIO: ' . $siteId . PHP_EOL); // ID del sitio
|
||||||
|
} else {
|
||||||
|
$logger->appendLog('No se encontró ningún sitio para la IP proporcionada: ' . $dataToSearch);
|
||||||
|
return 'Error: No se encontró ningún sitio para la IP proporcionada: ' . $dataToSearch; // Return early if no site is found
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//echo "Error en la solicitud. Código de estado HTTP: " . $responseSitesByIP->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseSitesByIP->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responseSitesByIP->getStatusCode(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
$devicesBySiteId = $clientUnms->request('GET', 'devices?siteId=' . $siteId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]); //Devices por ID del sitio
|
||||||
|
|
||||||
|
if ($devicesBySiteId->getStatusCode() === 200) {
|
||||||
|
$dataDevicesBySite = json_decode($devicesBySiteId->getBody(), true);
|
||||||
|
$jsonDevicesBySite = json_encode($dataDevicesBySite, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
|
||||||
|
$deviceID = null;
|
||||||
|
|
||||||
|
foreach ($dataDevicesBySite as $device) {
|
||||||
|
if (isset($device['ipAddress'])) {
|
||||||
|
$ipAddress = explode('/', $device['ipAddress'])[0]; // Obtener solo la IP sin la máscara
|
||||||
|
if ($ipAddress === $IpAddressClientId) {
|
||||||
|
$deviceID = $device['identification']['id']; // ID del dispositivo
|
||||||
|
break; // Salir del ciclo si se encuentra la IP
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$logger->appendLog('No se encontró la IP del dispositivo en la respuesta de la API.');
|
||||||
|
return 'Error: No se encontró la IP del dispositivo en la respuesta de la API.'; // Return early if the IP is not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deviceID == null) {
|
||||||
|
//echo "No se encontró el dispositivo con la IP proporcionada." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró el dispositivo con la IP proporcionada: ' . $IpAddressClientId);
|
||||||
|
return 'Error: No se encontró el dispositivo con la IP proporcionada: ' . $IpAddressClientId; // Return early if no device is found
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $devicesBySiteId->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $devicesBySiteId->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $devicesBySiteId->getStatusCode(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $deviceID . '/credentials', [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($responsePasswordVault->getStatusCode() === 200) {
|
||||||
|
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
|
||||||
|
// $jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonPasswordVault . PHP_EOL); //Cred
|
||||||
|
|
||||||
|
if (isset($dataPasswordVault['credentials'][0]['password'])) {
|
||||||
|
//asignar el campo password a la variable dataPasswordVault
|
||||||
|
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
|
||||||
|
} else {
|
||||||
|
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
|
||||||
|
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
|
||||||
|
}
|
||||||
|
return $dataPasswordVault;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responsePasswordVault->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (RequestException $requestException) {
|
||||||
|
// Manejar errores de la solicitud, si hay error 404 entonces responder que no se encontró niguna IP asociada a alguna antena o dispositivo de red
|
||||||
|
if ($requestException->hasResponse()) {
|
||||||
|
$response = $requestException->getResponse();
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
$reason = $response->getReasonPhrase();
|
||||||
|
// echo "Error: {$statusCode} - {$reason}" . PHP_EOL;
|
||||||
|
// echo $response->getBody();
|
||||||
|
if ($statusCode == 404) {
|
||||||
|
// echo "No se encontró el cliente con la dirección IP proporcionada." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró el cliente con la dirección IP proporcionada: ' . $IpAddressClientId);
|
||||||
|
return 'Error: No se encontró el cliente con la dirección IP proporcionada: ' . $IpAddressClientId; // Return early if the client is not found
|
||||||
|
}
|
||||||
|
return 'Error: ' . $reason; // Return early if the request fails
|
||||||
|
} else {
|
||||||
|
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error: ' . $requestException->getMessage());
|
||||||
|
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else if (preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $dataToSearch)) {
|
||||||
|
$clientIdentifierType = 'MAC';
|
||||||
|
// La variable es una dirección MAC válida
|
||||||
|
$logger->appendLog('Consulta por dirección MAC: ' . $dataToSearch);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// //para mandarla al endpoint de dispositivos por MAC se necesita convertir la cadena de la direccion mac en un formato como el siguiente ejemplo: para la dirección mac 60:22:32:c8:b2:c3 quedaría como 60%3A22%3A32%3Ac8%3Ab2%3Ac3
|
||||||
|
// $dataToSearch = str_replace(':', '%3A', $dataToSearch);
|
||||||
|
// $logger->appendLog('Consulta por dirección MAC: ' . $dataToSearch );
|
||||||
|
$responseDeviceByMAC = $clientUnms->request('GET', 'devices/mac/' . $dataToSearch, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($responseDeviceByMAC->getStatusCode() === 200) {
|
||||||
|
|
||||||
|
$dataDeviceByMAC = json_decode($responseDeviceByMAC->getBody(), true);
|
||||||
|
$jsonDeviceByMac = json_encode($dataDeviceByMAC, JSON_PRETTY_PRINT);
|
||||||
|
// print_r($jsonDeviceByMac . PHP_EOL); //Devices por ID del sitio
|
||||||
|
//$logger->appendLog($jsonDeviceByMac.PHP_EOL);
|
||||||
|
|
||||||
|
//Ejemplo de $responseDeviceByMac en json: {"id":"84c8a581-154c-41db-81d5-a1b1c9ede411","type":"airMax","series":"AC"}
|
||||||
|
//obtener el id del dispositivo y si no existe el id del dispositivo entonces devolver un mensaje de error
|
||||||
|
if (isset($dataDeviceByMAC['id'])) {
|
||||||
|
$deviceId = $dataDeviceByMAC['id'];
|
||||||
|
// print_r('ID DEL DISPOSITIVO: ' . $deviceId . PHP_EOL); // ID del dispositivo
|
||||||
|
} else {
|
||||||
|
// echo "No se encontró el dispositivo con la dirección MAC proporcionada." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró el dispositivo con la dirección MAC proporcionada: ' . $dataToSearch);
|
||||||
|
return 'Error: No se encontró el dispositivo con la dirección MAC proporcionada: ' . $dataToSearch; // Return early if no device is found
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $deviceId . '/credentials', [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($responsePasswordVault->getStatusCode() === 200) {
|
||||||
|
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
|
||||||
|
// $jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonPasswordVault . PHP_EOL); //Credenciales del dispositivo
|
||||||
|
|
||||||
|
if (isset($dataPasswordVault['credentials'][0]['password'])) {
|
||||||
|
//asignar el campo password a la variable dataPasswordVault
|
||||||
|
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
|
||||||
|
} else {
|
||||||
|
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
|
||||||
|
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
|
||||||
|
}
|
||||||
|
return $dataPasswordVault;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responsePasswordVault->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (RequestException $requestException) {
|
||||||
|
// Manejar errores de la solicitud, si hay error 404 entonces responder que no se encontró niguna MAC asociada a alguna antena o dispositivo de red
|
||||||
|
if ($requestException->hasResponse()) {
|
||||||
|
$response = $requestException->getResponse();
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
$reason = $response->getReasonPhrase();
|
||||||
|
// echo "Error: {$statusCode} - {$reason}" . PHP_EOL;
|
||||||
|
// echo $response->getBody();
|
||||||
|
if ($statusCode == 404) {
|
||||||
|
// echo "No se encontró el cliente con la dirección MAC proporcionada." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró ninguna antena de cliente o dispositivo en la red con la dirección MAC proporcionada: ' . $dataToSearch);
|
||||||
|
return 'Error: No se encontró ninguna antena de cliente o dispositivo en la red con la dirección MAC proporcionada: ' . $dataToSearch; // Return early if the client is not found
|
||||||
|
}
|
||||||
|
return 'Error: ' . $reason; // Return early if the request fails
|
||||||
|
} else {
|
||||||
|
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error: ' . $requestException->getMessage());
|
||||||
|
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$clientIdentifierType = 'ID';
|
||||||
|
// La variable no es una dirección IP válida, se asume que es un ID
|
||||||
|
$IpAddressClientId = filter_var($dataToSearch, FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
//print ('Consulta por ID: ' . $dataToSearch . PHP_EOL);
|
||||||
|
// $logger->appendLog('Consulta por ID' . $dataToSearch);
|
||||||
|
// $logger->appendLog('endpoint que se envía: '.$ucrmBaseUri.'clients/services?clientId=' . $IpAddressClientId);
|
||||||
|
// $logger->appendLog('apiToken que se usa: '.$crmAuthToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Obtener id del sitio por medio del servicio
|
||||||
|
$responseServices = $clientUcrm->get('clients/services?clientId=' . $IpAddressClientId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $crmAuthToken,
|
||||||
|
'Content-Type: application/json'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Obtener el nombre del cliente por medio del ID
|
||||||
|
$responseClientName = $clientUcrm->get('clients/' . $IpAddressClientId, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $crmAuthToken,
|
||||||
|
'Content-Type: application/json'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
//ejemplo de responseClientName en json: {"id":3201,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"CALLE CHIAPAS #31, ZONA CENTRO","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"CALLE CHIAPAS #31, ZONA CENTRO, Dolores Hidalgo Cuna de la Independencia Nacional, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"Soy un teibolero :) programador en SIIP INTERNET","sendInvoiceByPost":null,"invoiceMaturityDays":17,"stopServiceDue":false,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-04-19T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"DANIEL HUMBERTO","lastName":"SOTO VILLEGAS","username":"dhsv.141089@gmail.com","contacts":[{"id":4188,"clientId":3201,"email":"dhsv.141089@gmail.com","phone":"5214181878106","name":null,"isBilling":false,"isContact":false,"types":[{"id":4,"name":"WhatsApp"}]},{"id":5242,"clientId":3201,"email":null,"phone":"5214181148783","name":"WA Bussiness","isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":21180,"clientId":3201,"customAttributeId":35,"name":"Site","key":"site","value":"0LOCS","clientZoneVisible":true},{"id":18147,"clientId":3201,"customAttributeId":1,"name":"ip","key":"ip","value":"172.16.13.64","clientZoneVisible":false},{"id":19859,"clientId":3201,"customAttributeId":29,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_RKiSN955qKhmch","clientZoneVisible":true},{"id":19860,"clientId":3201,"customAttributeId":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180650741646979","clientZoneVisible":true},{"id":21181,"clientId":3201,"customAttributeId":36,"name":"Antena/Sectorial","key":"antenaSectorial","value":"Sectorial-4b 172.16.13.16/24","clientZoneVisible":true},{"id":27184,"clientId":3201,"customAttributeId":21,"name":"Chat de CallBell","key":"chatDeCallbell","value":"https://dash.callbell.eu/chat/90611886ac204c56a75240f951c53874","clientZoneVisible":false},{"id":28687,"clientId":3201,"customAttributeId":22,"name":"uuid","key":"uuid","value":"2038d2d99ae543f3b56f1ae54f4cc82b","clientZoneVisible":false},{"id":29853,"clientId":3201,"customAttributeId":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","value":"gYAIEK:Be}SK*01z5+/V","clientZoneVisible":false}],"accountBalance":-300,"accountCredit":0,"accountOutstanding":300,"currencyCode":"MXN","organizationName":"SIIP","bankAccounts":[{"id":2,"accountNumber":"124180650741646979"}],"tags":[{"id":1,"name":"NS EXENTO","colorBackground":"#00a0df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#4caf50","addressGpsLat":21.1564209,"addressGpsLon":-100.9384185,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":true,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}
|
||||||
|
|
||||||
|
$clientName = json_decode($responseClientName->getBody(), true);
|
||||||
|
$clientFullName = $clientName['firstName'] . ' ' . $clientName['lastName'];
|
||||||
|
$logger->appendLog('CLIENTE: ' . $clientFullName . ' (ID: ' . $IpAddressClientId . ')');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (RequestException $requestException) {
|
||||||
|
// Manejar errores de la solicitud
|
||||||
|
if ($requestException->hasResponse()) {
|
||||||
|
$response = $requestException->getResponse();
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
$reason = $response->getReasonPhrase();
|
||||||
|
//si el statusCode es 404 significa que no se encontró el cliente
|
||||||
|
if ($statusCode == 404) {
|
||||||
|
// echo "No se encontró el cliente con el ID proporcionado." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontró el cliente con el ID proporcionado: ' . $IpAddressClientId);
|
||||||
|
return 'Error: No se encontró el cliente con el ID proporcionado: ' . $IpAddressClientId; // Return early if the client is not found
|
||||||
|
}
|
||||||
|
return 'Error: ' . $reason; // Return early if the request fails
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error: ' . $requestException->getMessage());
|
||||||
|
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($responseServices->getStatusCode() === 200) {
|
||||||
|
|
||||||
|
$dataServices = json_decode($responseServices->getBody(), true);
|
||||||
|
// $jsonServices = json_encode($dataServices, JSON_PRETTY_PRINT);
|
||||||
|
// print_r($jsonServices . PHP_EOL);
|
||||||
|
if (isset($dataServices[0])) {
|
||||||
|
$unmsSiteID = $dataServices[0]['unmsClientSiteId']; // Example: 9c6798f3-0254-4e5b-bc3b-9da82fe16e46
|
||||||
|
} else {
|
||||||
|
// echo "No se encontraron servicios para el cliente proporcionado." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontraron servicios para el cliente proporcionado: ' . $IpAddressClientId);
|
||||||
|
return "Error: No se encontraron servicios para el cliente proporcionado: " . $IpAddressClientId; // Return early if no services are found
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responseServices->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseServices->getStatusCode());
|
||||||
|
return "Error: En la solicitud. Código de estado HTTP: " . $responseServices->getStatusCode();
|
||||||
|
}
|
||||||
|
// $logger->appendLog('ID del sitio obtenido: ' . $unmsSiteID);
|
||||||
|
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'devices?siteId=' . $unmsSiteID);
|
||||||
|
// $logger->appendLog('apiToken que se usa: '.$crmAuthToken);
|
||||||
|
|
||||||
|
$responseDevicesBySite = $clientUnms->request('GET', 'devices?siteId=' . $unmsSiteID, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ($responseDevicesBySite->getStatusCode() === 200) {
|
||||||
|
|
||||||
|
$dataDevicesBySite = json_decode($responseDevicesBySite->getBody(), true);
|
||||||
|
$jsonDevicesBySite = json_encode($dataDevicesBySite, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
|
||||||
|
|
||||||
|
//id del device al que está conectado el cliente
|
||||||
|
if (isset($dataDevicesBySite[0])) {
|
||||||
|
$idClientDevice = $dataDevicesBySite[0]['identification']['id'];
|
||||||
|
$deviceID = $dataDevicesBySite[0]['attributes']['apDevice']['id'];
|
||||||
|
} else {
|
||||||
|
// echo "No se encontraron dispositivos para el sitio proporcionado." . PHP_EOL;
|
||||||
|
$logger->appendLog('No se encontraron dispositivos para el sitio proporcionado: ' . $unmsSiteID);
|
||||||
|
return "Error: No se encontraron dispositivos para el sitio proporcionado."; // Return early if no devices are found
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode());
|
||||||
|
return "Error: Falla en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// $logger->appendLog('ID del device al que está conectado el cliente: ' . $deviceID);
|
||||||
|
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'devices/' . $deviceID);
|
||||||
|
// $logger->appendLog('apiToken que se usa: '.$unmsAuthToken);
|
||||||
|
|
||||||
|
$responseDevicesBySite = $clientUnms->request('GET', 'devices/' . $deviceID, [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($responseDevicesBySite->getStatusCode() === 200) {
|
||||||
|
|
||||||
|
$dataDevices = json_decode($responseDevicesBySite->getBody(), true);
|
||||||
|
$jsonDevices = json_encode($dataDevices, JSON_PRETTY_PRINT);
|
||||||
|
//print_r($jsonDevices . PHP_EOL);
|
||||||
|
|
||||||
|
|
||||||
|
// $logger->appendLog('ID del device al que está conectado el cliente: ' . $idDevice);
|
||||||
|
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'vault/' . $idDevice . '/credentials');
|
||||||
|
// $logger->appendLog('apiToken que se usa: '.$unmsAuthToken);
|
||||||
|
|
||||||
|
//print_r('ID del device al que está conectado el cliente: ' . $idDevice . PHP_EOL);
|
||||||
|
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $idClientDevice . '/credentials', [
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-Token' => $unmsAuthToken
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
if ($responsePasswordVault->getStatusCode() === 200) {
|
||||||
|
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
|
||||||
|
$jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
|
||||||
|
// $logger->appendLog($jsonPasswordVault); //Credenciales del dispositivo
|
||||||
|
//ejemplo de $responsePasswordVault en json: {"credentials":[{"username":"ubnt","readOnly":false,"password":"gYAIEK:Be}SK*01z5+/V","createdAt":"2025-03-27T23:29:54.948Z"}],"isPassphraseMissing":false}
|
||||||
|
|
||||||
|
//verificar si existe la contraseña en la respuesta de la API
|
||||||
|
if (isset($dataPasswordVault['credentials'][0]['password'])) {
|
||||||
|
//asignar el campo password a la variable dataPasswordVault
|
||||||
|
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
|
||||||
|
} else {
|
||||||
|
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
|
||||||
|
$logger->appendLog('Id device: ' . $idClientDevice);
|
||||||
|
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
|
||||||
|
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
|
||||||
|
}
|
||||||
|
return $dataPasswordVault;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
|
||||||
|
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode());
|
||||||
|
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode(); // Return early if the request fails
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global $clientFullName, $IpAddressClientId; // Variables globales para usar en la función getVaultCredentials
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera una contraseña aleatoria y fuerte (CSPRNG).
|
||||||
|
*
|
||||||
|
* - Garantiza al menos 1 minúscula, 1 mayúscula, 1 dígito y 1 símbolo.
|
||||||
|
* - Usa random_int() / random_bytes() (CSPRNG).
|
||||||
|
* - Evita sesgos al seleccionar y mezcla la contraseña con Fisher-Yates.
|
||||||
|
*
|
||||||
|
* @param int $length Longitud deseada (mínimo 8). Default 20.
|
||||||
|
* @param bool $avoid_ambiguous Si true evita caracteres ambiguos como l, I, 0, O.
|
||||||
|
* @return string Contraseña generada.
|
||||||
|
* @throws InvalidArgumentException Si $length < 8.
|
||||||
|
*/
|
||||||
|
function generate_strong_password(int $length = 20, bool $avoid_ambiguous = true): string
|
||||||
|
{
|
||||||
|
if ($length < 8) {
|
||||||
|
throw new InvalidArgumentException('La longitud mínima recomendada es 8 caracteres.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conjuntos de caracteres
|
||||||
|
$lower = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
$digits = '0123456789';
|
||||||
|
// Conjunto de símbolos seguros; puedes ampliarlo si lo deseas
|
||||||
|
$symbols = '!@#$%&*()-_=+[]{};:,.<>?';
|
||||||
|
|
||||||
|
if ($avoid_ambiguous) {
|
||||||
|
// Eliminar caracteres ambiguos
|
||||||
|
$amb = ['l', 'I', '1', '0', 'O'];
|
||||||
|
$lower = str_replace($amb, '', $lower);
|
||||||
|
$upper = str_replace($amb, '', $upper);
|
||||||
|
$digits = str_replace($amb, '', $digits);
|
||||||
|
// los símbolos raramente confunden, los mantenemos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asegurar al menos uno de cada tipo
|
||||||
|
$required = [];
|
||||||
|
$required[] = $lower[random_int(0, strlen($lower) - 1)];
|
||||||
|
$required[] = $upper[random_int(0, strlen($upper) - 1)];
|
||||||
|
$required[] = $digits[random_int(0, strlen($digits) - 1)];
|
||||||
|
$required[] = $symbols[random_int(0, strlen($symbols) - 1)];
|
||||||
|
|
||||||
|
// Relleno con una mezcla de todos los conjuntos
|
||||||
|
$all = $lower . $upper . $digits . $symbols;
|
||||||
|
$remaining = $length - count($required);
|
||||||
|
$pwChars = $required;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $remaining; $i++) {
|
||||||
|
$pwChars[] = $all[random_int(0, strlen($all) - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mezclar usando Fisher-Yates con random_int (sin sesgo)
|
||||||
|
$n = count($pwChars);
|
||||||
|
for ($i = $n - 1; $i > 0; $i--) {
|
||||||
|
$j = random_int(0, $i);
|
||||||
|
$tmp = $pwChars[$i];
|
||||||
|
$pwChars[$i] = $pwChars[$j];
|
||||||
|
$pwChars[$j] = $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', $pwChars);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Buscador de Contraseñas de la bóveda para dispositivos de la red UISP</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #2955CD;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2955CD;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
/* Margen de 10px en cada lado */
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* Asegura que el ancho incluya padding y borde */
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
/* Centra el botón */
|
||||||
|
width: auto;
|
||||||
|
/* Ancho automático */
|
||||||
|
margin: 0 auto;
|
||||||
|
/* Centrado horizontal */
|
||||||
|
padding: 10px 20px;
|
||||||
|
/* Tamaño acorde al texto */
|
||||||
|
background-color: #52BFFF;
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #327BAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultado {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #e6ffed;
|
||||||
|
border: 1px solid #39cc64;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultado h3 {
|
||||||
|
text-align: center;
|
||||||
|
/* Centrar el texto del título */
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #219e44;
|
||||||
|
/* Color del texto en la caja de resultado */
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultado h3.error {
|
||||||
|
color: red;
|
||||||
|
/* Color rojo para errores */
|
||||||
|
}
|
||||||
|
|
||||||
|
.caja-contrasena {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caja-contrasena input {
|
||||||
|
width: calc(100% - 22px);
|
||||||
|
/* Ajuste para el padding y el borde */
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #53535A;
|
||||||
|
/* Color del texto en la caja de resultado */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* Asegura que el ancho incluya padding y borde */
|
||||||
|
}
|
||||||
|
|
||||||
|
.caja-contrasena button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #39cc64;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caja-contrasena button:hover {
|
||||||
|
background-color: #2ab653;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Buscador de contraseñas de dispositivos almacenados en la bóveda de UISP</h1>
|
||||||
|
|
||||||
|
<!-- Formulario -->
|
||||||
|
<form method="POST" action="">
|
||||||
|
<label for="identificador">Ingrese el ID del cliente, dirección IP o dirección MAC de la antena del cliente,
|
||||||
|
sectorial o AP:</label>
|
||||||
|
<input type="text" id="identificador" name="identificador" required
|
||||||
|
placeholder="Ejemplo: 3201 o 172.16.13.64 o 00:1A:2B:3C:4D:5E">
|
||||||
|
<button type="submit">
|
||||||
|
<img src="public.php?img=search-icon.png" alt="Obtener" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Buscar contraseña
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Resultado -->
|
||||||
|
<?php if ($respuestaAPI) { ?>
|
||||||
|
<script>
|
||||||
|
window.clientFullName = '<?php echo addslashes(!empty($clientFullName) ? $clientFullName : ''); ?>';
|
||||||
|
window.IpAddressClientId = '<?php echo addslashes(!empty($IpAddressClientId) ? $IpAddressClientId : ''); ?>';
|
||||||
|
window.clientIdentifierType = '<?php echo addslashes($clientIdentifierType); ?>';
|
||||||
|
</script>
|
||||||
|
<div class="resultado">
|
||||||
|
<h3 class="<?php echo $esError ? 'error' : ''; ?>">
|
||||||
|
<?php
|
||||||
|
$errorText = $esError ? htmlspecialchars(substr($respuestaAPI, 6)) : 'Contraseña obtenida';
|
||||||
|
echo $errorText;
|
||||||
|
?>
|
||||||
|
</h3>
|
||||||
|
<?php
|
||||||
|
// Mostrar info de cliente si la contraseña fue encontrada
|
||||||
|
if (!$esError) {
|
||||||
|
$displayName = !empty($clientFullName) ? htmlspecialchars($clientFullName) : 'Cliente';
|
||||||
|
$displayId = !empty($IpAddressClientId) ? htmlspecialchars($IpAddressClientId) : '';
|
||||||
|
?>
|
||||||
|
<div style="text-align:center; color:#53535A; font-size:15px; margin-bottom:10px;">
|
||||||
|
<b><?php echo $displayName; ?></b> <?php if($displayId) { echo "(" . $clientIdentifierType . ": <b>" . $displayId . "</b>)"; } ?>
|
||||||
|
</div>
|
||||||
|
<?php }
|
||||||
|
if ($esError && strpos($respuestaAPI, 'No se encontró una contraseña') !== false) { ?>
|
||||||
|
<div id="generar-contenedor" style="text-align:center; margin-top:20px;">
|
||||||
|
<div style="margin-bottom:10px; color: #2c1ee9ff; font-size:16px;">¿Desea intentar generar una contraseña para la antena de este cliente?</div>
|
||||||
|
<button id="btn-generar" type="button" style="background: #04bb75ff; color:white; font-weight:bold; border:none; border-radius:4px; padding:10px 20px; font-size:15px; cursor:pointer;">
|
||||||
|
<img src="public.php?img=generate-password-icon.png" alt="Generar" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Generar una contraseña
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('btn-generar').onclick = function() {
|
||||||
|
// Generar la contraseña y actualizar el atributo en el cliente
|
||||||
|
fetch('public.php?generate_password=1')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(password => {
|
||||||
|
document.getElementById('generar-contenedor').innerHTML = '';
|
||||||
|
let resultadoDiv = document.querySelector('.resultado');
|
||||||
|
resultadoDiv.querySelector('h3').textContent = 'Nueva contraseña generada';
|
||||||
|
|
||||||
|
// Mostrar info de cliente
|
||||||
|
let displayId = window.IpAddressClientId || '';
|
||||||
|
let displayName = window.clientFullName || 'Cliente';
|
||||||
|
let infoDiv = document.createElement('div');
|
||||||
|
infoDiv.style = 'text-align:center; color:#53535A; font-size:15px; margin-bottom:10px;';
|
||||||
|
infoDiv.innerHTML = `<b>${displayName}</b> ${displayId ? `(${window.clientIdentifierType || 'ID'}: <b>${displayId}</b>)` : ''}`;
|
||||||
|
resultadoDiv.appendChild(infoDiv);
|
||||||
|
|
||||||
|
let caja = document.createElement('div');
|
||||||
|
caja.className = 'caja-contrasena';
|
||||||
|
caja.innerHTML = `<input type="text" id="contrasena" value="${password}" readonly>
|
||||||
|
<button onclick="copiarAlPortapapeles()">
|
||||||
|
<img src='public.php?img=clipboard-icon.png' alt='Copiar' style='width:16px; height:16px; vertical-align:middle; margin-right:5px;'>Copiar Contraseña
|
||||||
|
</button>`;
|
||||||
|
resultadoDiv.appendChild(caja);
|
||||||
|
|
||||||
|
// Llamar al endpoint para actualizar la contraseña en el cliente
|
||||||
|
if (displayId) {
|
||||||
|
fetch('public.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: `update_password=1&clientId=${encodeURIComponent(clientId)}&password=${encodeURIComponent(password)}`
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.success ? 'Contraseña actualizada' : 'Error: ' + data.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<?php } else if (!$esError) { ?>
|
||||||
|
<div class="caja-contrasena">
|
||||||
|
<input type="text" id="contrasena" value="<?php echo htmlspecialchars($respuestaAPI); ?>" readonly>
|
||||||
|
<button onclick="copiarAlPortapapeles()">
|
||||||
|
<img src="public.php?img=clipboard-icon.png" alt="Copiar" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Copiar Contraseña
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Función para copiar la contraseña al portapapeles
|
||||||
|
function copiarAlPortapapeles() {
|
||||||
|
const contrasenaInput = document.getElementById('contrasena');
|
||||||
|
contrasenaInput.select();
|
||||||
|
contrasenaInput.setSelectionRange(0, 99999); // Para dispositivos móviles
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('Contraseña copiada al portapapeles: ' + contrasenaInput.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html><q></q>
|
||||||
@ -34,6 +34,10 @@ function handleApiRequest($data, $log) {
|
|||||||
handleIpCheck($data, $log);
|
handleIpCheck($data, $log);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'event.ip_validate':
|
||||||
|
handleIpValidate($data, $log);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
@ -41,7 +45,8 @@ function handleApiRequest($data, $log) {
|
|||||||
'message' => "Event type '$eventType' is not supported",
|
'message' => "Event type '$eventType' is not supported",
|
||||||
'supported_events' => [
|
'supported_events' => [
|
||||||
'event.ip_request',
|
'event.ip_request',
|
||||||
'event.ip_check'
|
'event.ip_check',
|
||||||
|
'event.ip_validate'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -94,7 +99,7 @@ function handleIpRequest($data, $log) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buscar IPs
|
// Buscar IPs
|
||||||
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true";
|
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=true&management=true&includeObsolete=true";
|
||||||
$apiToken = $config['unmsApiToken'];
|
$apiToken = $config['unmsApiToken'];
|
||||||
|
|
||||||
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log, $config);
|
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log, $config);
|
||||||
@ -198,60 +203,208 @@ function handleIpCheck($data, $log) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true";
|
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=true&management=true&includeObsolete=true";
|
||||||
$apiToken = $config['unmsApiToken'];
|
$apiToken = $config['unmsApiToken'];
|
||||||
|
|
||||||
// Verificar si se solicita ping
|
// Verificar si se solicita ping
|
||||||
$verifyPing = isset($data['verify_ping']) && $data['verify_ping'] === true;
|
$verifyPing = isset($data['verify_ping']) && $data['verify_ping'] === true;
|
||||||
|
|
||||||
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log);
|
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log, $config);
|
||||||
$resultado = $ipService->buscarIpsDisponibles($segment, $verifyPing);
|
|
||||||
|
|
||||||
if ($resultado['success']) {
|
// Si NO se solicita ping, buscar normalmente
|
||||||
$isAvailable = in_array($ipToCheck, $resultado['data']);
|
if (!$verifyPing) {
|
||||||
$isUsed = in_array($ipToCheck, $resultado['used']);
|
$resultado = $ipService->buscarIpsDisponibles($segment, false);
|
||||||
$ipType = \SiipAvailableIps\IpSearchService::getIpType($ipToCheck, $config);
|
|
||||||
|
|
||||||
// Determinar status con información de tipo de IP
|
if ($resultado['success']) {
|
||||||
$status = 'unknown';
|
$isAvailable = in_array($ipToCheck, $resultado['data']);
|
||||||
if ($isAvailable) {
|
$isUsed = in_array($ipToCheck, $resultado['used']);
|
||||||
$status = $ipType['type'] === 'admin'
|
$ipType = \SiipAvailableIps\IpSearchService::getIpType($ipToCheck, $config);
|
||||||
? 'IP disponible pero para uso administrativo'
|
|
||||||
: 'available';
|
|
||||||
} elseif ($isUsed) {
|
|
||||||
$status = $ipType['type'] === 'admin'
|
|
||||||
? 'IP en uso y además para uso administrativo'
|
|
||||||
: 'used';
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = [
|
|
||||||
'success' => true,
|
|
||||||
'event' => 'event.ip_check',
|
|
||||||
'ip' => $ipToCheck,
|
|
||||||
'status' => $status,
|
|
||||||
'available' => $isAvailable,
|
|
||||||
'used' => $isUsed,
|
|
||||||
'ip_type' => $ipType,
|
|
||||||
'ping_verified' => $resultado['ping_verified']
|
|
||||||
];
|
|
||||||
|
|
||||||
// Si se verificó con ping, agregar información
|
|
||||||
if ($resultado['ping_verified'] && isset($resultado['ping_responding'])) {
|
|
||||||
$pingResponds = in_array($ipToCheck, $resultado['ping_responding']);
|
|
||||||
$response['ping_responds'] = $pingResponds;
|
|
||||||
|
|
||||||
// Si la IP responde a ping pero está marcada como disponible, es una advertencia
|
// Debug logging
|
||||||
if ($pingResponds && $isAvailable) {
|
$log->appendLog("API Check (sin ping) - IP: $ipToCheck");
|
||||||
$response['warning'] = 'IP responde a ping pero no está registrada en UISP';
|
$log->appendLog(" - En lista disponibles: " . ($isAvailable ? 'SÍ' : 'NO'));
|
||||||
|
$log->appendLog(" - En lista usadas: " . ($isUsed ? 'SÍ' : 'NO'));
|
||||||
|
$log->appendLog(" - Total disponibles: " . count($resultado['data']));
|
||||||
|
$log->appendLog(" - Total usadas: " . count($resultado['used']));
|
||||||
|
|
||||||
|
|
||||||
|
// Determinar status con información de tipo de IP
|
||||||
|
$status = 'unknown';
|
||||||
|
if ($isAvailable) {
|
||||||
|
$status = $ipType['type'] === 'admin'
|
||||||
|
? 'IP disponible pero para uso administrativo'
|
||||||
|
: 'available';
|
||||||
|
} elseif ($isUsed) {
|
||||||
|
$status = $ipType['type'] === 'admin'
|
||||||
|
? 'IP en uso y además para uso administrativo'
|
||||||
|
: 'used';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'event' => 'event.ip_check',
|
||||||
|
'ip' => $ipToCheck,
|
||||||
|
'status' => $status,
|
||||||
|
'available' => $isAvailable,
|
||||||
|
'used' => $isUsed,
|
||||||
|
'ip_type' => $ipType,
|
||||||
|
'ping_verified' => false
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'IP check failed',
|
||||||
|
'message' => $resultado['message']
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
echo json_encode($response);
|
}
|
||||||
} else {
|
|
||||||
|
// Si SÍ se solicita ping, hacer ping SOLO a esta IP específica
|
||||||
|
$log->appendLog("API: Verificación por ping habilitada para IP $ipToCheck");
|
||||||
|
|
||||||
|
// Primero verificar si está en UISP
|
||||||
|
$resultado = $ipService->buscarIpsDisponibles($segment, false);
|
||||||
|
|
||||||
|
if (!$resultado['success']) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'IP check failed',
|
'error' => 'IP check failed',
|
||||||
'message' => $resultado['message']
|
'message' => $resultado['message']
|
||||||
]);
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isAvailable = in_array($ipToCheck, $resultado['data']);
|
||||||
|
$isUsed = in_array($ipToCheck, $resultado['used']);
|
||||||
|
$ipType = \SiipAvailableIps\IpSearchService::getIpType($ipToCheck, $config);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
$log->appendLog("API Check - IP: $ipToCheck");
|
||||||
|
$log->appendLog(" - En lista disponibles: " . ($isAvailable ? 'SÍ' : 'NO'));
|
||||||
|
$log->appendLog(" - En lista usadas: " . ($isUsed ? 'SÍ' : 'NO'));
|
||||||
|
$log->appendLog(" - Total disponibles: " . count($resultado['data']));
|
||||||
|
$log->appendLog(" - Total usadas: " . count($resultado['used']));
|
||||||
|
|
||||||
|
|
||||||
|
// Hacer ping solo a esta IP
|
||||||
|
$pingService = new \SiipAvailableIps\PingService($log);
|
||||||
|
$pingResult = $pingService->pingIp($ipToCheck, 1); // 1 segundo de timeout
|
||||||
|
$pingResponds = $pingResult['responding'];
|
||||||
|
|
||||||
|
$log->appendLog("Ping a $ipToCheck: " . ($pingResponds ? 'RESPONDE' : 'NO RESPONDE'));
|
||||||
|
|
||||||
|
// Determinar status
|
||||||
|
$status = 'unknown';
|
||||||
|
if ($isAvailable) {
|
||||||
|
$status = $ipType['type'] === 'admin'
|
||||||
|
? 'IP disponible pero para uso administrativo'
|
||||||
|
: 'available';
|
||||||
|
} elseif ($isUsed) {
|
||||||
|
$status = $ipType['type'] === 'admin'
|
||||||
|
? 'IP en uso y además para uso administrativo'
|
||||||
|
: 'used';
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'success' => true,
|
||||||
|
'event' => 'event.ip_check',
|
||||||
|
'ip' => $ipToCheck,
|
||||||
|
'status' => $status,
|
||||||
|
'available' => $isAvailable,
|
||||||
|
'used' => $isUsed,
|
||||||
|
'ip_type' => $ipType,
|
||||||
|
'ping_verified' => true,
|
||||||
|
'ping_responds' => $pingResponds
|
||||||
|
];
|
||||||
|
|
||||||
|
// Si la IP responde a ping pero está marcada como disponible, es una advertencia
|
||||||
|
if ($pingResponds && $isAvailable) {
|
||||||
|
$response['warning'] = 'IP responde a ping pero no está registrada en UISP';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log de la respuesta antes de enviarla
|
||||||
|
$log->appendLog("Respuesta JSON: " . json_encode($response));
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maneja el evento event.ip_validate - Validar IP individual usando búsqueda de sitios
|
||||||
|
*/
|
||||||
|
function handleIpValidate($data, $log) {
|
||||||
|
// Validar IP
|
||||||
|
if (!isset($data['ip'])) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Missing IP',
|
||||||
|
'message' => 'The "ip" field is required for event.ip_validate'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $data['ip'];
|
||||||
|
|
||||||
|
// Validar formato de IP
|
||||||
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Invalid IP',
|
||||||
|
'message' => "The IP '$ip' is not valid"
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener configuración
|
||||||
|
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||||
|
$config = $configManager->loadConfig();
|
||||||
|
|
||||||
|
// Verificar que tengamos las credenciales necesarias
|
||||||
|
if (empty($config['unmsApiToken']) || empty($config['ipserver'])) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Missing configuration',
|
||||||
|
'message' => 'UNMS API token or server not configured'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Crear instancia del validador
|
||||||
|
$baseUrl = 'https://' . $config['ipserver'];
|
||||||
|
$ipValidator = new \SiipAvailableIps\IpValidator(
|
||||||
|
$baseUrl,
|
||||||
|
$config['unmsApiToken'],
|
||||||
|
$log
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validar si la IP está en uso
|
||||||
|
$isInUse = $ipValidator->isIpInUse($ip);
|
||||||
|
|
||||||
|
// Preparar respuesta
|
||||||
|
$response = [
|
||||||
|
'success' => true,
|
||||||
|
'event' => 'event.ip_validate',
|
||||||
|
'ip' => $ip,
|
||||||
|
'in_use' => $isInUse,
|
||||||
|
'available' => !$isInUse,
|
||||||
|
'validation_method' => 'site_search'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($isInUse) {
|
||||||
|
$response['message'] = 'IP encontrada en UISP (búsqueda de sitios)';
|
||||||
|
} else {
|
||||||
|
$response['message'] = 'IP no encontrada en UISP';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$log->appendLog('Error en validación de IP: ' . $e->getMessage());
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Validation error',
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
135
src/CrmService.php
Normal file
135
src/CrmService.php
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SiipAvailableIps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servicio para interactuar con la API de UCRM
|
||||||
|
* Permite consultar servicios y filtrar sitios bloqueados
|
||||||
|
*/
|
||||||
|
class CrmService
|
||||||
|
{
|
||||||
|
private $apiUrl;
|
||||||
|
private $apiToken;
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $apiUrl URL base de la API de CRM (ej: https://servidor/crm/api/v1.0)
|
||||||
|
* @param string $apiToken Token de API de CRM (X-Auth-App-Key)
|
||||||
|
* @param Logger|null $logger Logger opcional para debugging
|
||||||
|
*/
|
||||||
|
public function __construct($apiUrl, $apiToken, $logger = null)
|
||||||
|
{
|
||||||
|
$this->apiUrl = rtrim($apiUrl, '/');
|
||||||
|
$this->apiToken = $apiToken;
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener lista de Site IDs de UISP que tienen servicios bloqueados
|
||||||
|
* (suspendidos o finalizados) en CRM
|
||||||
|
*
|
||||||
|
* @return array Lista de UUIDs de sitios bloqueados
|
||||||
|
*/
|
||||||
|
public function getBlockedSiteIds()
|
||||||
|
{
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('CRM: Consultando servicios suspendidos/finalizados...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener servicios con status 2 (Ended) y 3 (Suspended)
|
||||||
|
$services = $this->getServicesByStatus([2, 3]);
|
||||||
|
|
||||||
|
if ($services === false) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('CRM: Error al consultar servicios');
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockedSites = [];
|
||||||
|
foreach ($services as $service) {
|
||||||
|
// Solo agregar si tiene vinculación con UISP
|
||||||
|
if (!empty($service['unmsClientSiteId'])) {
|
||||||
|
$blockedSites[] = $service['unmsClientSiteId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockedSites = array_unique($blockedSites);
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog(sprintf(
|
||||||
|
'CRM: Encontrados %d sitios bloqueados por servicios suspendidos/finalizados',
|
||||||
|
count($blockedSites)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $blockedSites;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener servicios filtrados por estado
|
||||||
|
*
|
||||||
|
* @param array $statuses Array de estados a filtrar (ej: [2, 3])
|
||||||
|
* @return array|false Array de servicios o false en caso de error
|
||||||
|
*/
|
||||||
|
private function getServicesByStatus($statuses)
|
||||||
|
{
|
||||||
|
// Construir parámetros de query
|
||||||
|
$params = [];
|
||||||
|
foreach ($statuses as $status) {
|
||||||
|
$params[] = 'statuses[]=' . intval($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $this->apiUrl . '/clients/services?' . implode('&', $params);
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("CRM: Consultando $url");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hacer request HTTP
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'X-Auth-App-Key: ' . $this->apiToken,
|
||||||
|
'Content-Type: application/json'
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Para desarrollo local
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("CRM: Error cURL: $error");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("CRM: HTTP $httpCode - $response");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('CRM: Error al decodificar JSON');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog(sprintf('CRM: Obtenidos %d servicios', count($data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -133,6 +133,72 @@ class IpSearchService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NUEVO: Filtrar IPs de servicios CRM suspendidos/finalizados
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog(sprintf(
|
||||||
|
'DEBUG CRM: apitoken=%s, ipserver=%s',
|
||||||
|
!empty($this->config['apitoken']) ? 'SET' : 'EMPTY',
|
||||||
|
!empty($this->config['ipserver']) ? 'SET' : 'EMPTY'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->config['apitoken']) && !empty($this->config['ipserver'])) {
|
||||||
|
try {
|
||||||
|
// Construir URL de la API de CRM
|
||||||
|
$crmApiUrl = 'https://' . $this->config['ipserver'] . '/crm/api/v1.0';
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("CRM: Iniciando filtrado con URL: $crmApiUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
$crmService = new \SiipAvailableIps\CrmService(
|
||||||
|
$crmApiUrl,
|
||||||
|
$this->config['apitoken'],
|
||||||
|
$this->logger
|
||||||
|
);
|
||||||
|
|
||||||
|
$blockedSites = $crmService->getBlockedSiteIds();
|
||||||
|
|
||||||
|
if (!empty($blockedSites)) {
|
||||||
|
// Obtener datos completos de IPs con siteId
|
||||||
|
$ipsWithSites = $this->obtenerIpsConSitios();
|
||||||
|
|
||||||
|
if ($ipsWithSites !== false) {
|
||||||
|
// Filtrar IPs que pertenecen a sitios bloqueados
|
||||||
|
$ipsFiltradasPorCrm = [];
|
||||||
|
foreach ($ipsEnUso as $ip) {
|
||||||
|
$siteId = $ipsWithSites[$ip] ?? null;
|
||||||
|
if ($siteId && in_array($siteId, $blockedSites)) {
|
||||||
|
$ipsFiltradasPorCrm[] = $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($ipsFiltradasPorCrm)) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog(sprintf(
|
||||||
|
'CRM: Filtrando %d IPs de sitios con servicios suspendidos/finalizados: %s',
|
||||||
|
count($ipsFiltradasPorCrm),
|
||||||
|
implode(', ', array_slice($ipsFiltradasPorCrm, 0, 5)) . (count($ipsFiltradasPorCrm) > 5 ? '...' : '')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar IPs filtradas a la lista de IPs en uso
|
||||||
|
$ipsEnUso = array_unique(array_merge($ipsEnUso, $ipsFiltradasPorCrm));
|
||||||
|
} else {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('CRM: No se encontraron IPs en uso que coincidan con sitios bloqueados');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('CRM: Error al filtrar servicios: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
// Continuar sin filtrado de CRM en caso de error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filtrar IPs del segmento solicitado
|
// Filtrar IPs del segmento solicitado
|
||||||
$segmentoPrefix = "172.16.$segmento.";
|
$segmentoPrefix = "172.16.$segmento.";
|
||||||
$segmentIps = [];
|
$segmentIps = [];
|
||||||
@ -157,6 +223,10 @@ class IpSearchService
|
|||||||
// Calcular IPs disponibles
|
// Calcular IPs disponibles
|
||||||
$ipsDisponibles = array_values(array_diff($todasLasIps, $segmentIps));
|
$ipsDisponibles = array_values(array_diff($todasLasIps, $segmentIps));
|
||||||
|
|
||||||
|
// NOTA: La validación por búsqueda de sitios se hace de forma progresiva en el frontend
|
||||||
|
// No se puede hacer en lote aquí porque causa timeout
|
||||||
|
// El frontend llamará al endpoint API para validar IPs individuales
|
||||||
|
|
||||||
// Verificar con ping si está habilitado
|
// Verificar con ping si está habilitado
|
||||||
$pingVerified = false;
|
$pingVerified = false;
|
||||||
$pingStats = null;
|
$pingStats = null;
|
||||||
@ -384,4 +454,89 @@ class IpSearchService
|
|||||||
|
|
||||||
return $ipsEnUso;
|
return $ipsEnUso;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene mapeo de IPs a Site IDs desde la API de UISP
|
||||||
|
*
|
||||||
|
* @return array|false Array asociativo [ip => siteId] o false en caso de error
|
||||||
|
*/
|
||||||
|
private function obtenerIpsConSitios()
|
||||||
|
{
|
||||||
|
// La API /devices/ips devuelve solo strings de IPs
|
||||||
|
// Necesitamos consultar /devices para obtener la relación IP => siteId
|
||||||
|
|
||||||
|
// Extraer la URL base sin parámetros
|
||||||
|
$urlParts = parse_url($this->apiUrl);
|
||||||
|
$baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'];
|
||||||
|
$devicesUrl = $baseUrl . '/nms/api/v2.1/devices';
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog('Obteniendo dispositivos con siteId: ' . $devicesUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $devicesUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'accept: application/json',
|
||||||
|
"x-auth-token: {$this->apiToken}"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||||
|
|
||||||
|
$result = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200 || $result === false) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("Error al obtener dispositivos: HTTP $httpCode");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$devices = json_decode($result, true);
|
||||||
|
if (!is_array($devices)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear mapeo IP => siteId
|
||||||
|
$ipToSite = [];
|
||||||
|
foreach ($devices as $device) {
|
||||||
|
$siteId = $device['identification']['site']['id'] ?? null;
|
||||||
|
|
||||||
|
if (!$siteId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener IP principal del dispositivo
|
||||||
|
$primaryIp = $device['ipAddress'] ?? null;
|
||||||
|
if ($primaryIp) {
|
||||||
|
// Remover la máscara de red si existe (ej: 172.16.55.73/24 -> 172.16.55.73)
|
||||||
|
$cleanIp = explode('/', $primaryIp)[0];
|
||||||
|
$ipToSite[$cleanIp] = $siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener TODAS las IPs del dispositivo desde ipAddressList
|
||||||
|
$ipList = $device['ipAddressList'] ?? [];
|
||||||
|
if (is_array($ipList)) {
|
||||||
|
foreach ($ipList as $ip) {
|
||||||
|
if ($ip && filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||||
|
$ipToSite[$ip] = $siteId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog(sprintf(
|
||||||
|
'Mapeadas %d IPs a sitios',
|
||||||
|
count($ipToSite)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ipToSite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
156
src/IpValidator.php
Normal file
156
src/IpValidator.php
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SiipAvailableIps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validador de IPs usando búsqueda de sitios en UISP
|
||||||
|
* Utiliza el endpoint /sites/search para encontrar dispositivos por IP
|
||||||
|
*/
|
||||||
|
class IpValidator
|
||||||
|
{
|
||||||
|
private $baseUrl;
|
||||||
|
private $apiToken;
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct($baseUrl, $apiToken, $logger = null)
|
||||||
|
{
|
||||||
|
$this->baseUrl = rtrim($baseUrl, '/');
|
||||||
|
$this->apiToken = $apiToken;
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si una IP está en uso en UISP usando búsqueda de sitios
|
||||||
|
*
|
||||||
|
* @param string $ip IP a verificar
|
||||||
|
* @return bool True si la IP está en uso, false si está disponible
|
||||||
|
*/
|
||||||
|
public function isIpInUse($ip)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 1. Buscar sitio por IP usando el endpoint de búsqueda
|
||||||
|
// IMPORTANTE: type=subscriber solo busca en sitios de clientes (endpoints)
|
||||||
|
$searchUrl = $this->baseUrl . '/nms/api/v2.1/sites/search?query=' . urlencode($ip) . '&count=1&page=1&type=subscriber';
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: Buscando sitio para IP $ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $searchUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'X-Auth-Token: ' . $this->apiToken
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: Error HTTP $httpCode al buscar sitio");
|
||||||
|
}
|
||||||
|
return false; // Si hay error, asumir que no está en uso
|
||||||
|
}
|
||||||
|
|
||||||
|
$sites = json_decode($response, true);
|
||||||
|
|
||||||
|
// Si no hay sitios, la IP no está en uso
|
||||||
|
if (empty($sites) || !isset($sites[0]['id'])) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: No se encontró sitio para IP $ip - DISPONIBLE");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$siteId = $sites[0]['id'];
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: Sitio encontrado: $siteId");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Obtener dispositivos del sitio
|
||||||
|
$devicesUrl = $this->baseUrl . '/nms/api/v2.1/devices?siteId=' . $siteId;
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $devicesUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'X-Auth-Token: ' . $this->apiToken
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: Error HTTP $httpCode al obtener dispositivos");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$devices = json_decode($response, true);
|
||||||
|
|
||||||
|
// 3. Verificar si algún dispositivo tiene la IP
|
||||||
|
foreach ($devices as $device) {
|
||||||
|
// Verificar IP principal
|
||||||
|
if (isset($device['ipAddress'])) {
|
||||||
|
$deviceIp = explode('/', $device['ipAddress'])[0];
|
||||||
|
if ($deviceIp === $ip) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: IP $ip encontrada en dispositivo (ipAddress) - EN USO");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar lista de IPs
|
||||||
|
if (isset($device['ipAddressList']) && is_array($device['ipAddressList'])) {
|
||||||
|
if (in_array($ip, $device['ipAddressList'])) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: IP $ip encontrada en dispositivo (ipAddressList) - EN USO");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: IP $ip no encontrada en dispositivos del sitio - DISPONIBLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->appendLog("IpValidator: Excepción: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
return false; // En caso de error, asumir disponible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida un lote de IPs y devuelve las que están en uso
|
||||||
|
*
|
||||||
|
* @param array $ips Array de IPs a validar
|
||||||
|
* @return array Array de IPs que están en uso
|
||||||
|
*/
|
||||||
|
public function getIpsInUse(array $ips)
|
||||||
|
{
|
||||||
|
$ipsInUse = [];
|
||||||
|
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
if ($this->isIpInUse($ip)) {
|
||||||
|
$ipsInUse[] = $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ipsInUse;
|
||||||
|
}
|
||||||
|
}
|
||||||
90014
unms-swagger.json
Normal file
90014
unms-swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
8392
unmscrm.apib
Normal file
8392
unmscrm.apib
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user