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_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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -111,6 +111,7 @@ Vue.component('Cpeprovisioning', {
|
||||
<tt-button text="In Radius anlegen"
|
||||
@click="createRadiusUser(item)"
|
||||
:disabled="!isValidMac(item.cpe_data.mac)"
|
||||
:loading="item.isCreatingRadius"
|
||||
sm
|
||||
additional-class="btn-primary" />
|
||||
<tt-button text="ACS Auto VLAN Zuweisung testen"
|
||||
@@ -244,6 +245,7 @@ Vue.component('Cpeprovisioning', {
|
||||
...item,
|
||||
isDirty: false,
|
||||
isSaving: false,
|
||||
isCreatingRadius: false,
|
||||
pop_name: item.pop_name || 'N/A',
|
||||
owner_address: `${item.owner_street || ''} ${item.owner_housenumber || ''}, ${item.owner_zip || ''} ${item.owner_city || ''}`,
|
||||
owner_phone: item.owner_phone || '',
|
||||
@@ -296,36 +298,27 @@ Vue.component('Cpeprovisioning', {
|
||||
isVlanSelected(item) {
|
||||
return item.vlans && Object.values(item.vlans).some(v => 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) {
|
||||
|
||||
Reference in New Issue
Block a user