diff --git a/application/Cpeprovisioning/CpeprovisioningController.php b/application/Cpeprovisioning/CpeprovisioningController.php
index 3ca148fe2..beec047a9 100644
--- a/application/Cpeprovisioning/CpeprovisioningController.php
+++ b/application/Cpeprovisioning/CpeprovisioningController.php
@@ -530,6 +530,7 @@ class CpeprovisioningController extends mfBaseController {
"CPE_PROV_API_GET_URL" => $this->getUrl("Cpeprovisioning", "apiGet"),
"CPE_PROV_API_SAVE_URL" => $this->getUrl("Cpeprovisioning", "apiSave"),
"CPE_PROV_API_TEST_ACS_VLAN_URL" => $this->getUrl("Cpeprovisioning", "getAcsVlan"),
+ "CPE_PROV_API_CREATE_RADIUS_USER_URL" => $this->getUrl("Cpeprovisioning", "createRadiusUser"),
"CPE_PROV_PRINT_PDF_URL" => $this->getUrl("Cpeprovisioning", "printPDF"),
"ORDER_URL" => $this->getUrl("Order"),
"NETWORKS" => NetworkModel::getAll(),
@@ -598,13 +599,17 @@ class CpeprovisioningController extends mfBaseController {
$attrs = $product->product->attributes;
- $vlanPublicDefault = $term->getPop()->vlan_public ?? $attrs['vlan_default_public']->value ?? null;
- $vlanNatDefault = $term->getPop()->vlan_nat ?? $attrs['vlan_default_nat']->value ?? null;
- $vlanIpv6Default = $term->getPop()->vlan_ipv6 ?? $attrs['vlan_default_ipv6']->value ?? null;
+ // First, check if any VLAN is explicitly saved (checked in frontend)
+ // The saved values take priority over defaults
+ $assignedVlan = $cpe->vlan_public ?? $cpe->vlan_nat ?? $cpe->vlan_ipv6;
- // For the test, we just return the first available VLAN that would be assigned.
- // The logic can be expanded if a specific type is requested.
- $assignedVlan = $vlanPublicDefault ?? $vlanNatDefault ?? $vlanIpv6Default;
+ // If no VLAN is explicitly saved, fall back to defaults
+ if (!$assignedVlan) {
+ $vlanPublicDefault = $term->getPop()->vlan_public ?? $attrs['vlan_default_public']->value ?? null;
+ $vlanNatDefault = $term->getPop()->vlan_nat ?? $attrs['vlan_default_nat']->value ?? null;
+ $vlanIpv6Default = $term->getPop()->vlan_ipv6 ?? $attrs['vlan_default_ipv6']->value ?? null;
+ $assignedVlan = $vlanPublicDefault ?? $vlanNatDefault ?? $vlanIpv6Default;
+ }
if ($assignedVlan) {
self::returnJson(['success' => true, 'vlan_id' => $assignedVlan]);
@@ -618,6 +623,282 @@ class CpeprovisioningController extends mfBaseController {
}
}
+ protected function createRadiusUserAction() {
+ $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? null;
+ $isApiCall = defined('TT_CPE_PROV_ACS_API_KEY') && $apiKey && $apiKey === TT_CPE_PROV_ACS_API_KEY;
+ $isLoggedInUser = $this->me && $this->me->id;
+
+ if (!$isApiCall && !$isLoggedInUser) {
+ http_response_code(403);
+ self::returnJson(['success' => false, 'message' => 'Forbidden']);
+ return;
+ }
+
+ try {
+ $p = json_decode(file_get_contents('php://input'), true);
+ $mac = $p['mac'] ?? null;
+
+ if (empty($mac)) {
+ throw new Exception("MAC address is required.");
+ }
+
+ // Normalize MAC address format to uppercase with colons
+ $mac = strtoupper(str_replace(['-', '.'], ':', $mac));
+
+ // Look up CPE provisioning entry
+ $cpe = CpeprovisioningModel::getFirst(['mac' => $mac]);
+ if (!$cpe || !$cpe->termination_id) {
+ throw new Exception("No active provisioning entry found for this MAC address.");
+ }
+
+ $term = new Termination($cpe->termination_id);
+ $product = $cpe->orderproduct;
+ $order = new Order($cpe->order_id);
+
+ if (!$term->id || !$product->id || !$order->id) {
+ throw new Exception("Could not load termination, product, or order details.");
+ }
+
+ // Gather all data needed for RADIUS user
+ $customerNumber = $order->owner->customer_number ?? $order->partner_number ?? '';
+ $ontSn = $term->getWorkflowValue("ont_sn", "string") ?? '';
+ $wifiKey = $cpe->wifi_pass ?? '';
+
+ // Check if RADIUS credentials are configured
+ if (!defined('TT_RADIUS_URL') || !defined('TT_RADIUS_USERNAME') || !defined('TT_RADIUS_PASSWORD')) {
+ throw new Exception("RADIUS server credentials are not configured.");
+ }
+
+ $radiusUrl = TT_RADIUS_URL;
+ $radiusUsername = TT_RADIUS_USERNAME;
+ $radiusPassword = TT_RADIUS_PASSWORD;
+
+ // Step 1: Check if RADIUS user already exists
+ $existingUser = $this->checkRadiusUserExists($mac);
+
+ if ($existingUser) {
+ $this->log->info("RADIUS user {$mac} already exists, skipping creation and updating details only.");
+ } else {
+ // Step 2: Login to RADIUS server
+ $cookieFile = tempnam(sys_get_temp_dir(), 'radius_cookie_');
+
+ // Generate random password for RADIUS user
+ $radiusUserPassword = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, 8);
+
+ $loginResult = $this->radiusHttpRequest(
+ $radiusUrl . '/login.php',
+ [
+ 'username' => $radiusUsername,
+ 'password' => $radiusPassword,
+ 'submit' => 'Login'
+ ],
+ $cookieFile
+ );
+
+ if (strpos($loginResult, 'Login') === false && strpos($loginResult, 'logout') === false) {
+ throw new Exception("Failed to login to RADIUS server.");
+ }
+
+ // Step 3: Create user with MAC address
+ $createResult = $this->radiusHttpRequest(
+ $radiusUrl . '/add_user.php',
+ [
+ 'user' => $mac,
+ 'pass' => $radiusUserPassword,
+ 'submit' => ' Anlegen '
+ ],
+ $cookieFile
+ );
+
+ // Check if user creation was successful
+ if (strpos($createResult, 'already exists') !== false) {
+ // Somehow it exists now (race condition?), continue to update
+ $this->log->info("RADIUS user {$mac} already exists, updating details.");
+ } elseif (strpos($createResult, 'error') !== false || strpos($createResult, 'Error') !== false) {
+ throw new Exception("Failed to create RADIUS user: User creation returned an error.");
+ } else {
+ $this->log->info("RADIUS user {$mac} created successfully.");
+ }
+ }
+
+ // Step 4: Login to RADIUS server for update (in case we skipped creation)
+ $cookieFile = tempnam(sys_get_temp_dir(), 'radius_cookie_');
+ $loginResult = $this->radiusHttpRequest(
+ $radiusUrl . '/login.php',
+ [
+ 'username' => $radiusUsername,
+ 'password' => $radiusPassword,
+ 'submit' => 'Login'
+ ],
+ $cookieFile
+ );
+
+ if (strpos($loginResult, 'Login') === false && strpos($loginResult, 'logout') === false) {
+ throw new Exception("Failed to login to RADIUS server for update.");
+ }
+
+ // Step 5: Update user details
+ $updateData = [
+ 'userid' => '',
+ 'user' => $mac,
+ 'Cleartext-Password' => '',
+ 'Custnum' => (strpos($customerNumber, '7000') === 0) ? $customerNumber : '',
+ 'Custnume' => (strpos($customerNumber, '7000') === 0) ? '' : $customerNumber,
+ 'Hotspot_Info' => '',
+ 'ont_sn' => $ontSn,
+ 'Wifikey' => $wifiKey,
+ 'Mikrotik-Group' => '',
+ 'Framed-Pool' => '',
+ 'Pool-Name' => '',
+ 'Framed-IP-Address' => '',
+ 'Framed-IP-Netmask' => '',
+ 'Framed-Route' => '',
+ 'MS-Primary-DNS-Server' => '195.191.252.62',
+ 'MS-Secondary-DNS-Server' => '193.105.204.194',
+ 'DHCP-IP-Address-Lease-Time' => '',
+ 'MaxLogins' => '',
+ 'Valid-From' => '',
+ 'Valid-To' => '',
+ 'Hotspot_Duration' => '',
+ 'Hotspot_Duration_Multiplicant' => '86400',
+ 'Rate-Limit-Down' => '',
+ 'Rate-Limit-Up' => '',
+ 'ContractDown' => '',
+ 'ContractUp' => '',
+ 'Rate-Limit-Down-Burst' => '',
+ 'Rate-Limit-Up-Burst' => '',
+ 'Rate-Limit-Burst-Sec' => '',
+ 'Rate-Limit-Down-Thresh' => '',
+ 'Rate-Limit-Up-Thresh' => '',
+ 'Session-Timeout' => '',
+ 'timeout_max' => '',
+ 'Mikrotik-Recv-Limit' => '',
+ 'transfer_max' => '',
+ 'cisco-avpair[vrf]' => '',
+ 'cisco-avpair[interface]' => '',
+ 'submit' => 'Update'
+ ];
+
+ $updateResult = $this->radiusHttpRequest(
+ $radiusUrl . '/edit_user.php',
+ $updateData,
+ $cookieFile
+ );
+
+ // Clean up cookie file
+ @unlink($cookieFile);
+
+ // Check if update was successful
+ if (strpos($updateResult, 'error') !== false || strpos($updateResult, 'Error') !== false) {
+ throw new Exception("Failed to update RADIUS user details.");
+ }
+
+ $this->log->info("Successfully created/updated RADIUS user for MAC: {$mac}, Customer: {$customerNumber}");
+
+ self::returnJson([
+ 'success' => true,
+ 'message' => 'RADIUS user created/updated successfully.',
+ 'data' => [
+ 'mac' => $mac,
+ 'customer_number' => $customerNumber,
+ 'ont_sn' => $ontSn,
+ 'wifi_key' => $wifiKey
+ ]
+ ]);
+
+ } catch (Exception $e) {
+ // Clean up cookie file on error
+ if (isset($cookieFile) && file_exists($cookieFile)) {
+ @unlink($cookieFile);
+ }
+
+ $this->log->error("Failed to create RADIUS user: " . $e->getMessage());
+ http_response_code(400);
+ self::returnJson(['success' => false, 'message' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * Check if a RADIUS user exists by username (MAC address)
+ *
+ * @param string $username The username/MAC address to check
+ * @return bool True if user exists, false otherwise
+ */
+ private function checkRadiusUserExists($username) {
+ try {
+ if (!defined('TT_RADIUS_API_URL')) {
+ // Fallback to default if not configured
+ $apiUrl = 'http://radius.xinon.at/api.php';
+ } else {
+ $apiUrl = TT_RADIUS_API_URL;
+ }
+
+ // Query the RADIUS API to check if user exists
+ $queryParams = http_build_query(['username' => $username]);
+ $url = $apiUrl . '?' . $queryParams;
+
+ $opts = [
+ "http" => [
+ "method" => "GET",
+ "header" => "Authorization: Basic " . base64_encode(TT_RADIUS_USERNAME . ":" . TT_RADIUS_PASSWORD),
+ "timeout" => 10
+ ]
+ ];
+
+ $context = stream_context_create($opts);
+ $response = @file_get_contents($url, false, $context);
+
+ if ($response === false) {
+ $this->log->warning("Could not check if RADIUS user exists: API request failed");
+ return false;
+ }
+
+ $data = json_decode($response, true);
+
+ // If we get results back, the user exists
+ if (is_array($data) && count($data) > 0) {
+ return true;
+ }
+
+ return false;
+
+ } catch (Exception $e) {
+ $this->log->error("Error checking RADIUS user existence: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ private function radiusHttpRequest($url, $postData, $cookieFile) {
+ $ch = curl_init($url);
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+
+ $result = curl_exec($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+ if (curl_errno($ch)) {
+ $error = curl_error($ch);
+ curl_close($ch);
+ throw new Exception("HTTP request failed: " . $error);
+ }
+
+ curl_close($ch);
+
+ if ($httpCode !== 200) {
+ throw new Exception("HTTP request returned status code: " . $httpCode);
+ }
+
+ return $result;
+ }
+
private function fixCpeData($data) {
if (!$data) return [];
$data->shipping = (bool)$data->shipping;
diff --git a/config/config.sample.php b/config/config.sample.php
index 42c653a8c..b9d39bc6b 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -162,6 +162,14 @@ const TT_IBAN_VALIDATOR_BASEURL = "https://rest.sepatools.eu/validate_iban/";
const TT_IBAN_VALIDATOR_USER = "";
const TT_IBAN_VALIDATOR_PASS = "";
+// CPE Provisioning ACS API
+const TT_CPE_PROV_ACS_API_KEY = "your-acs-api-key-here";
+const TT_CPE_PROV_ALLOW_COUNTS_IP = ["192.168.1.100"]; // IPs allowed to access count endpoint
+
+// RADIUS Server Configuration
+const TT_RADIUS_URL = "http://radius.xinon.at";
+const TT_RADIUS_USERNAME = "admin";
+const TT_RADIUS_PASSWORD = "savemanfb545aw";
/*
* Preorder settings
diff --git a/public/js/pages/Cpeprovisioning/Cpeprovisioning.js b/public/js/pages/Cpeprovisioning/Cpeprovisioning.js
index ac3150a99..306b0cb3b 100644
--- a/public/js/pages/Cpeprovisioning/Cpeprovisioning.js
+++ b/public/js/pages/Cpeprovisioning/Cpeprovisioning.js
@@ -111,6 +111,7 @@ Vue.component('Cpeprovisioning', {
v.checked);
},
- createRadiusUser(item) {
- const message = {
- type: "INITIATE_CREATE_RADIUS_USER",
- payload: {
- customerName: item.customer,
- address: item.owner_full_address,
- servicePin: item.spin,
- routerMac: item.cpe_data.mac,
- customerNumber: item.owner_customer_number
- }
- };
+ async createRadiusUser(item) {
+ // Disable button during request
+ this.$set(item, 'isCreatingRadius', true);
- if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
- try {
- chrome.runtime.sendMessage(this.extensionId, message, (response) => {
- if (chrome.runtime.lastError) {
- console.warn("Senden an Erweiterung fehlgeschlagen:", chrome.runtime.lastError.message);
- window.notify('warning', 'Daten konnten nicht an die Erweiterung gesendet werden. (Drücke STRG + ALT + E zum Konfigurieren)');
- } else {
- console.log("Erweiterung hat geantwortet:", response);
- window.notify('success', 'Radius Anlage an Erweiterung übergeben.');
- }
- });
- } catch (e) {
- console.error("Fehler beim Senden an die Erweiterung:", e);
- window.notify('error', 'Fehler beim Senden an die Erweiterung.');
+ try {
+ const { data } = await axios.post(window.TT_CONFIG.CPE_PROV_API_CREATE_RADIUS_USER_URL, {
+ mac: item.cpe_data.mac
+ });
+
+ if (data.success) {
+ window.notify('success', `RADIUS User erfolgreich angelegt! Kundennr: ${data.data.customer_number}`);
+ console.log('RADIUS User created:', data.data);
+ } else {
+ window.notify('error', data.message || 'Fehler beim Anlegen des RADIUS Users.');
}
- } else {
- console.warn("Chrome Extension Messaging API nicht verfügbar.");
- window.notify('warning', 'Chrome Messaging API nicht gefunden.');
+ } catch (error) {
+ const errorMsg = error.response?.data?.message || 'Ein unerwarteter Fehler ist aufgetreten.';
+ window.notify('error', errorMsg);
+ console.error('Error creating RADIUS user:', error);
+ } finally {
+ this.$set(item, 'isCreatingRadius', false);
}
},
async testAcsVlan(item) {