Implement RADIUS user creation and update functionality in CPE provisioning
This commit is contained in:
@@ -530,6 +530,7 @@ class CpeprovisioningController extends mfBaseController {
|
|||||||
"CPE_PROV_API_GET_URL" => $this->getUrl("Cpeprovisioning", "apiGet"),
|
"CPE_PROV_API_GET_URL" => $this->getUrl("Cpeprovisioning", "apiGet"),
|
||||||
"CPE_PROV_API_SAVE_URL" => $this->getUrl("Cpeprovisioning", "apiSave"),
|
"CPE_PROV_API_SAVE_URL" => $this->getUrl("Cpeprovisioning", "apiSave"),
|
||||||
"CPE_PROV_API_TEST_ACS_VLAN_URL" => $this->getUrl("Cpeprovisioning", "getAcsVlan"),
|
"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"),
|
"CPE_PROV_PRINT_PDF_URL" => $this->getUrl("Cpeprovisioning", "printPDF"),
|
||||||
"ORDER_URL" => $this->getUrl("Order"),
|
"ORDER_URL" => $this->getUrl("Order"),
|
||||||
"NETWORKS" => NetworkModel::getAll(),
|
"NETWORKS" => NetworkModel::getAll(),
|
||||||
@@ -598,13 +599,17 @@ class CpeprovisioningController extends mfBaseController {
|
|||||||
|
|
||||||
$attrs = $product->product->attributes;
|
$attrs = $product->product->attributes;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// If no VLAN is explicitly saved, fall back to defaults
|
||||||
|
if (!$assignedVlan) {
|
||||||
$vlanPublicDefault = $term->getPop()->vlan_public ?? $attrs['vlan_default_public']->value ?? null;
|
$vlanPublicDefault = $term->getPop()->vlan_public ?? $attrs['vlan_default_public']->value ?? null;
|
||||||
$vlanNatDefault = $term->getPop()->vlan_nat ?? $attrs['vlan_default_nat']->value ?? null;
|
$vlanNatDefault = $term->getPop()->vlan_nat ?? $attrs['vlan_default_nat']->value ?? null;
|
||||||
$vlanIpv6Default = $term->getPop()->vlan_ipv6 ?? $attrs['vlan_default_ipv6']->value ?? null;
|
$vlanIpv6Default = $term->getPop()->vlan_ipv6 ?? $attrs['vlan_default_ipv6']->value ?? null;
|
||||||
|
|
||||||
// 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;
|
$assignedVlan = $vlanPublicDefault ?? $vlanNatDefault ?? $vlanIpv6Default;
|
||||||
|
}
|
||||||
|
|
||||||
if ($assignedVlan) {
|
if ($assignedVlan) {
|
||||||
self::returnJson(['success' => true, 'vlan_id' => $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) {
|
private function fixCpeData($data) {
|
||||||
if (!$data) return [];
|
if (!$data) return [];
|
||||||
$data->shipping = (bool)$data->shipping;
|
$data->shipping = (bool)$data->shipping;
|
||||||
|
|||||||
@@ -162,6 +162,14 @@ const TT_IBAN_VALIDATOR_BASEURL = "https://rest.sepatools.eu/validate_iban/";
|
|||||||
const TT_IBAN_VALIDATOR_USER = "";
|
const TT_IBAN_VALIDATOR_USER = "";
|
||||||
const TT_IBAN_VALIDATOR_PASS = "";
|
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
|
* Preorder settings
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ Vue.component('Cpeprovisioning', {
|
|||||||
<tt-button text="In Radius anlegen"
|
<tt-button text="In Radius anlegen"
|
||||||
@click="createRadiusUser(item)"
|
@click="createRadiusUser(item)"
|
||||||
:disabled="!isValidMac(item.cpe_data.mac)"
|
:disabled="!isValidMac(item.cpe_data.mac)"
|
||||||
|
:loading="item.isCreatingRadius"
|
||||||
sm
|
sm
|
||||||
additional-class="btn-primary" />
|
additional-class="btn-primary" />
|
||||||
<tt-button text="ACS Auto VLAN Zuweisung testen"
|
<tt-button text="ACS Auto VLAN Zuweisung testen"
|
||||||
@@ -244,6 +245,7 @@ Vue.component('Cpeprovisioning', {
|
|||||||
...item,
|
...item,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
isCreatingRadius: false,
|
||||||
pop_name: item.pop_name || 'N/A',
|
pop_name: item.pop_name || 'N/A',
|
||||||
owner_address: `${item.owner_street || ''} ${item.owner_housenumber || ''}, ${item.owner_zip || ''} ${item.owner_city || ''}`,
|
owner_address: `${item.owner_street || ''} ${item.owner_housenumber || ''}, ${item.owner_zip || ''} ${item.owner_city || ''}`,
|
||||||
owner_phone: item.owner_phone || '',
|
owner_phone: item.owner_phone || '',
|
||||||
@@ -296,36 +298,27 @@ Vue.component('Cpeprovisioning', {
|
|||||||
isVlanSelected(item) {
|
isVlanSelected(item) {
|
||||||
return item.vlans && Object.values(item.vlans).some(v => v.checked);
|
return item.vlans && Object.values(item.vlans).some(v => v.checked);
|
||||||
},
|
},
|
||||||
createRadiusUser(item) {
|
async createRadiusUser(item) {
|
||||||
const message = {
|
// Disable button during request
|
||||||
type: "INITIATE_CREATE_RADIUS_USER",
|
this.$set(item, 'isCreatingRadius', true);
|
||||||
payload: {
|
|
||||||
customerName: item.customer,
|
|
||||||
address: item.owner_full_address,
|
|
||||||
servicePin: item.spin,
|
|
||||||
routerMac: item.cpe_data.mac,
|
|
||||||
customerNumber: item.owner_customer_number
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
|
||||||
try {
|
try {
|
||||||
chrome.runtime.sendMessage(this.extensionId, message, (response) => {
|
const { data } = await axios.post(window.TT_CONFIG.CPE_PROV_API_CREATE_RADIUS_USER_URL, {
|
||||||
if (chrome.runtime.lastError) {
|
mac: item.cpe_data.mac
|
||||||
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);
|
if (data.success) {
|
||||||
window.notify('error', 'Fehler beim Senden an die Erweiterung.');
|
window.notify('success', `RADIUS User erfolgreich angelegt! Kundennr: ${data.data.customer_number}`);
|
||||||
}
|
console.log('RADIUS User created:', data.data);
|
||||||
} else {
|
} else {
|
||||||
console.warn("Chrome Extension Messaging API nicht verfügbar.");
|
window.notify('error', data.message || 'Fehler beim Anlegen des RADIUS Users.');
|
||||||
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) {
|
async testAcsVlan(item) {
|
||||||
|
|||||||
Reference in New Issue
Block a user