From a5c82bbf2483a046f8c22c21e93630e3b44d4af3 Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Wed, 28 Jan 2026 22:50:16 +0100 Subject: [PATCH] initial commit api Investigator --- .../Api/v1/InvestigatorApicontroller.php | 176 ++++++++++++++++++ application/Radius/RadiusController.php | 107 +++++++++-- 2 files changed, 265 insertions(+), 18 deletions(-) create mode 100644 application/Api/v1/InvestigatorApicontroller.php diff --git a/application/Api/v1/InvestigatorApicontroller.php b/application/Api/v1/InvestigatorApicontroller.php new file mode 100644 index 000000000..396db8eeb --- /dev/null +++ b/application/Api/v1/InvestigatorApicontroller.php @@ -0,0 +1,176 @@ +addRoute("/investigator/customer/:id", "getCustomer", "GET"); + $this->addRoute("/investigator/customer/:id/contract", "getCustomerContract", "GET"); + $this->addRoute("/investigator/customer/:id/cpe", "getCustomerCpe", "GET"); + $this->addRoute("/investigator/customer/:id/address", "getCustomerAddress", "GET"); + $this->addRoute("/investigator/search/customer", "searchCustomer", "GET"); + } + + protected function authenticated() + { + $this->registerRoutes(); + } + + public function getCustomer($customerId) + { + if (!$customerId) return mfResponse::BadRequest(['message' => 'Customer ID is required']); + + $addresses = AddressModel::search(['customer_number' => $customerId]); + if (empty($addresses)) return mfResponse::NotFound(['message' => 'Customer not found']); + + $address = $addresses[0]; + return mfResponse::Ok([ + 'customer' => [ + 'id' => $address->id, + 'customerNumber' => $address->customer_number, + 'company' => $address->company, + 'firstName' => $address->firstname, + 'lastName' => $address->lastname, + 'email' => $address->email, + 'phone' => $address->phone, + 'street' => $address->street, + 'zip' => $address->zip, + 'city' => $address->city, + 'country' => $address->country, + ] + ]); + } + + public function getCustomerContract($customerId) + { + if (!$customerId) return mfResponse::BadRequest(['message' => 'Customer ID is required']); + + $addresses = AddressModel::search(['customer_number' => $customerId]); + if (empty($addresses)) return mfResponse::NotFound(['message' => 'Customer not found']); + + $contracts = ContractModel::search(['owner_id' => $addresses[0]->id]); + $contractData = []; + + foreach ($contracts as $contract) { + $contractData[] = [ + 'contractId' => $contract->contract_id, + 'productName' => $contract->product_name, + 'productExternal' => $contract->product_external, + 'price' => $contract->price, + 'billingPeriod' => $contract->billing_period, + 'orderDate' => $contract->order_date, + 'finishDate' => $contract->finish_date, + 'cancelDate' => $contract->cancel_date, + 'slaId' => $contract->sla_id, + 'status' => $contract->cancel_date ? 'Cancelled' : ($contract->finish_date ? 'Active' : 'Pending'), + ]; + } + + return mfResponse::Ok([ + 'customerId' => $customerId, + 'contractCount' => count($contractData), + 'contracts' => $contractData, + ]); + } + + public function getCustomerCpe($customerId) + { + if (!$customerId) return mfResponse::BadRequest(['message' => 'Customer ID is required']); + + $addresses = AddressModel::search(['customer_number' => $customerId]); + if (empty($addresses)) return mfResponse::NotFound(['message' => 'Customer not found']); + + $db = $this->db(); + $sql = "SELECT cp.* FROM Cpeprovisioning cp + INNER JOIN `Order` o ON cp.order_id = o.id + WHERE o.owner_id = " . intval($addresses[0]->id) . " + ORDER BY cp.id DESC LIMIT 10"; + + $res = $db->query($sql); + $cpeData = []; + + while ($row = $db->fetch_object($res)) { + $cpeData[] = [ + 'orderId' => $row->order_id ?? null, + 'routerType' => $row->routertype ?? null, + 'routerConfigFinished' => (bool)($row->routerconfig_finished ?? false), + 'mac' => $row->mac ?? null, + 'ontSerial' => $row->ont_sn ?? null, + 'wifiSsid' => $row->wifi_ssid ?? null, + 'wifiPasswordSet' => !empty($row->wifi_pass), + 'vlanPublic' => $row->vlan_public ?? null, + 'vlanNat' => $row->vlan_nat ?? null, + 'vlanIpv6' => $row->vlan_ipv6 ?? null, + 'shipping' => (bool)($row->shipping ?? false), + ]; + } + + return mfResponse::Ok([ + 'customerId' => $customerId, + 'cpeCount' => count($cpeData), + 'cpeProvisioning' => $cpeData, + ]); + } + + public function getCustomerAddress($customerId) + { + if (!$customerId) return mfResponse::BadRequest(['message' => 'Customer ID is required']); + + $addresses = AddressModel::search(['customer_number' => $customerId]); + if (empty($addresses)) return mfResponse::NotFound(['message' => 'Customer not found']); + + $address = $addresses[0]; + return mfResponse::Ok([ + 'customerId' => $customerId, + 'address' => [ + 'company' => $address->company, + 'name' => trim(($address->firstname ?? '') . ' ' . ($address->lastname ?? '')), + 'street' => $address->street, + 'zip' => $address->zip, + 'city' => $address->city, + 'country' => $address->country, + 'email' => $address->email, + 'phone' => $address->phone, + ] + ]); + } + + public function searchCustomer() + { + $searchTerm = $this->get['q'] ?? ''; + $limit = intval($this->get['limit'] ?? 10); + + if (empty($searchTerm)) return mfResponse::BadRequest(['message' => 'Search term required']); + + $db = $this->db(); + $searchTerm = $db->escape($searchTerm); + + $sql = "SELECT * FROM Address + WHERE customer_number LIKE '%$searchTerm%' + OR CONCAT(firstname, ' ', lastname) LIKE '%$searchTerm%' + OR company LIKE '%$searchTerm%' + OR email LIKE '%$searchTerm%' + OR street LIKE '%$searchTerm%' + LIMIT $limit"; + + $results = $db->queryRows($sql); + $customers = []; + + foreach ($results as $row) { + $customers[] = [ + 'customerId' => $row['customer_number'], + 'name' => trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? '')) ?: $row['company'], + 'email' => $row['email'], + 'city' => $row['city'], + ]; + } + + return mfResponse::Ok([ + 'searchTerm' => $searchTerm, + 'count' => count($customers), + 'customers' => $customers, + ]); + } +} diff --git a/application/Radius/RadiusController.php b/application/Radius/RadiusController.php index 54822e554..e39a5f6d8 100644 --- a/application/Radius/RadiusController.php +++ b/application/Radius/RadiusController.php @@ -4,9 +4,45 @@ use PHPMailer\PHPMailer\Exception; class RadiusController extends mfBaseController { private User $me; + private bool $isApiCall = false; + + private array $apiAllowedActions = [ + 'ProxyUnsecureHTTPRequestToRadius', + 'GenieacsRunSpeedtest', + 'GenieacsGetSpeedtestResult', + 'GenieacsGetDeviceByIp', + 'GenieacsGetDeviceByMac', + 'GenieacsRefreshDevice', + 'GenieacsRebootDevice', + 'GenieacsGetDeviceInfo', + 'GenieacsPing', + 'GenieacsRemoteAccess', + 'GenieacsEventLog', + 'GenieacsNetworkStructure', + ]; protected function init(): void { - $this->needlogin=true; + $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? null; + + if ($apiKey && in_array($this->action, $this->apiAllowedActions)) { + $me = new User(); + $me->loadByApikey($apiKey); + + if ($me->id) { + $this->me = $me; + $this->isApiCall = true; + $this->needlogin = false; + if (!defined('INTERNAL_USER_ID')) { + define('INTERNAL_USER_ID', $me->id); + } + header("Access-Control-Allow-Origin: *"); + header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); + header("Access-Control-Allow-Headers: Content-Type, X-API-Key"); + return; + } + } + + $this->needlogin = true; $me = new User(); $me->loadMe(); $this->layout()->set("me", $me); @@ -51,20 +87,32 @@ class RadiusController extends mfBaseController { $acs = $this->getGenieACS(); - // Set speedtest parameters on the device - $acs->setParameterValues($deviceId, [ + $resolvedId = $this->resolveDeviceId($deviceId, $acs); + if (!$resolvedId) self::sendError("Device not found in GenieACS"); + + $acs->getParameterValues($resolvedId, [ + 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.Start', + 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.StartBidirect', + 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.WANAccess', + 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.Result' + ]); + + sleep(2); + + $acs->setParameterValues($resolvedId, [ 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.Start' => 1, 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.StartBidirect' => 1, 'InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.WANAccess' => true ]); - // Get device and extract IP - $device = $acs->getDevice($deviceId); - $ip = GenieACS::getExternalIP($device); + sleep(3); + + $device = $acs->getDevice($resolvedId); + $managementIp = GenieACS::getManagementIP($device); + $externalIp = GenieACS::getExternalIP($device); + $ip = $externalIp ?: $managementIp; if (!$ip) self::sendError("Could not determine device IP"); - - // Trigger speedtest via external API $url = "http://acs.xinon.at:5000/run-speedtest"; $apiKey = "2H9zWrgxPEJL9MZ1yTGtWh16cPCu0AsQ"; $data = json_encode(['ip' => $ip]); @@ -84,9 +132,8 @@ class RadiusController extends mfBaseController { if ($response === false) self::sendError("Failed to connect to speedtest server"); - self::returnJson(['success' => true, 'message' => 'Speedtest started']); + self::returnJson(['success' => true, 'message' => 'Speedtest started', 'ip' => $ip, 'serverResponse' => json_decode($response, true)]); } catch (Exception $e) { - $this->log->debug("Speedtest Error", ['error' => $e->getMessage()]); self::sendError("Error running speedtest: " . $e->getMessage()); } } @@ -101,11 +148,12 @@ class RadiusController extends mfBaseController { $acs = $this->getGenieACS(); - // Request parameter refresh - $acs->getParameterValues($deviceId, ['InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.Result']); + $resolvedId = $this->resolveDeviceId($deviceId, $acs); + if (!$resolvedId) self::sendError("Device not found in GenieACS"); - // Get device info with full data - $device = $acs->getDevice($deviceId); + $acs->getParameterValues($resolvedId, ['InternetGatewayDevice.X_AVM-DE_SpeedtestServer.UDP.Result']); + + $device = $acs->getDevice($resolvedId); if (!$device) self::sendError("Device not found"); @@ -178,6 +226,19 @@ class RadiusController extends mfBaseController { return new GenieACS($host, $username, $password); } + private function resolveDeviceId(string $deviceId, GenieACS $acs): ?string { + if (strpos($deviceId, ':') !== false) { + $device = $acs->getDeviceByMac($deviceId); + if ($device) { + $resolvedId = GenieACS::getDeviceId($device); + if ($resolvedId) return $resolvedId; + if (isset($device['_id'])) return $device['_id']; + } + return null; + } + return $deviceId; + } + protected function genieacsGetDeviceByIpAction() { try { $ip = $_GET['ip'] ?? null; @@ -255,7 +316,11 @@ class RadiusController extends mfBaseController { if (!$deviceId) self::sendError("Device ID is required"); $acs = $this->getGenieACS(); - $acs->getParameterValues($deviceId, [ + + $resolvedId = $this->resolveDeviceId($deviceId, $acs); + if (!$resolvedId) self::sendError("Device not found in GenieACS"); + + $acs->getParameterValues($resolvedId, [ 'InternetGatewayDevice.DeviceInfo.HardwareVersion', 'InternetGatewayDevice.DeviceInfo.SoftwareVersion', 'InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress', @@ -267,7 +332,7 @@ class RadiusController extends mfBaseController { 'InternetGatewayDevice.LANDevice.*.Hosts.Host.*.MACAddress' ]); - $device = $acs->getDevice($deviceId); + $device = $acs->getDevice($resolvedId); self::returnJson([ 'success' => true, @@ -373,8 +438,11 @@ class RadiusController extends mfBaseController { if (!$deviceId) self::sendError("Device ID is required"); $acs = $this->getGenieACS(); - $creds = $acs->createRemoteUser($deviceId); + $resolvedId = $this->resolveDeviceId($deviceId, $acs); + if (!$resolvedId) self::sendError("Device not found in GenieACS"); + + $creds = $acs->createRemoteUser($resolvedId); if (!$creds) self::sendError("Could not obtain credentials for FritzBox"); $url = "http://acs.xinon.at:5000/read-fritz-eventlog"; @@ -425,8 +493,11 @@ class RadiusController extends mfBaseController { if (!$deviceId) self::sendError("Device ID is required"); $acs = $this->getGenieACS(); - $creds = $acs->createRemoteUser($deviceId); + $resolvedId = $this->resolveDeviceId($deviceId, $acs); + if (!$resolvedId) self::sendError("Device not found in GenieACS"); + + $creds = $acs->createRemoteUser($resolvedId); if (!$creds) self::sendError("Could not obtain credentials for FritzBox"); $url = "http://acs.xinon.at:5000/read-fritz";