Files
virtfusion-whmcs-module/modules/servers/VirtFusionDirect/client.php
Claude cfb1ddb4e5 Fix firewall API endpoints to use correct {interface} path parameter
- Firewall endpoints now use /firewall/{interface}/ where interface is
  "primary" or "secondary" (was missing the interface segment)
- Add applyFirewallRulesets() method for applying predefined rulesets by ID
- Add firewallApplyRulesets client endpoint (comma-separated ruleset IDs)
- Add sanitizeFirewallInterface() helper for input validation
- All firewall methods now accept optional interface parameter (default: primary)
- Document that VirtFusion uses ruleset-based firewall (no individual rule CRUD)
- Update README with correct API paths and ruleset documentation

https://claude.ai/code/session_01TCsJ4WZCGuEX3zqh1tQ2zx
2026-02-07 12:51:36 +00:00

451 lines
15 KiB
PHP

<?php
require dirname(__DIR__, 3) . '/init.php';
use WHMCS\Module\Server\VirtFusionDirect\Module;
use WHMCS\Module\Server\VirtFusionDirect\ServerResource;
$vf = new Module();
$vf->isAuthenticated();
$action = $vf->validateAction(true);
switch ($action) {
/**
* Reset Password.
*/
case 'resetPassword':
$serviceID = $vf->validateServiceID(true);
$client = $vf->validateUserOwnsService($serviceID);
if (!$client) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$data = $vf->resetUserPassword($serviceID, $client);
if ($data) {
$vf->output(['success' => true, 'data' => $data->data], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
break;
/**
* Get server information.
*/
case 'serverData':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$data = $vf->fetchServerData($serviceID);
if ($data) {
(new Module())->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Unable to retrieve server data'], true, true, 500);
break;
/**
* Login as server owner.
*/
case 'loginAsServerOwner':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$token = $vf->fetchLoginTokens($serviceID);
if ($token) {
$vf->output(['success' => true, 'token_url' => $token], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Unable to generate login token'], true, true, 500);
break;
/**
* Power management actions: boot, shutdown, restart, poweroff
*/
case 'powerAction':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$powerAction = isset($_GET['powerAction']) ? preg_replace('/[^a-zA-Z]/', '', $_GET['powerAction']) : '';
$allowedActions = ['boot', 'shutdown', 'restart', 'poweroff'];
if (!in_array($powerAction, $allowedActions, true)) {
$vf->output(['success' => false, 'errors' => 'Invalid power action'], true, true, 400);
}
$result = $vf->serverPowerAction($serviceID, $powerAction);
if ($result) {
$vf->output(['success' => true, 'data' => ['action' => $powerAction, 'message' => 'Power action queued successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Power action failed. The server may be locked or unavailable.'], true, true, 500);
break;
/**
* Rebuild/reinstall server with new OS.
*/
case 'rebuild':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$osId = isset($_GET['osId']) ? (int) $_GET['osId'] : 0;
$hostname = isset($_GET['hostname']) ? preg_replace('/[^a-zA-Z0-9.\-]/', '', $_GET['hostname']) : null;
if ($osId <= 0) {
$vf->output(['success' => false, 'errors' => 'Invalid operating system ID'], true, true, 400);
}
$result = $vf->rebuildServer($serviceID, $osId, $hostname);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Server rebuild initiated successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Server rebuild failed. The server may be locked or unavailable.'], true, true, 500);
break;
/**
* Rename server.
*/
case 'rename':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$newName = isset($_GET['name']) ? trim($_GET['name']) : '';
$newName = htmlspecialchars($newName, ENT_QUOTES, 'UTF-8');
if (empty($newName) || strlen($newName) > 255) {
$vf->output(['success' => false, 'errors' => 'Invalid server name'], true, true, 400);
}
$result = $vf->renameServer($serviceID, $newName);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Server renamed successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Server rename failed'], true, true, 500);
break;
/**
* Get available OS templates for rebuild.
*/
case 'osTemplates':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$templates = $vf->fetchOsTemplates($serviceID);
if ($templates !== false) {
$vf->output(['success' => true, 'data' => $templates], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Unable to fetch OS templates'], true, true, 500);
break;
// =================================================================
// Firewall Management
//
// VirtFusion uses a ruleset-based system. Individual rules cannot
// be added/deleted via the API. Rulesets are created in admin panel
// and applied to servers by ID.
// =================================================================
/**
* Get firewall status, rules, and assigned rulesets.
*/
case 'firewallStatus':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary';
$result = $vf->getFirewallStatus($serviceID, $interface);
if ($result !== false) {
$vf->output(['success' => true, 'data' => $result], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Unable to retrieve firewall status'], true, true, 500);
break;
/**
* Enable firewall.
*/
case 'firewallEnable':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary';
$result = $vf->enableFirewall($serviceID, $interface);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Firewall enabled successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to enable firewall'], true, true, 500);
break;
/**
* Disable firewall.
*/
case 'firewallDisable':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary';
$result = $vf->disableFirewall($serviceID, $interface);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Firewall disabled successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to disable firewall'], true, true, 500);
break;
/**
* Apply/sync firewall rules (re-applies currently assigned rulesets).
*/
case 'firewallApplyRules':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary';
$result = $vf->applyFirewallRules($serviceID, $interface);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Firewall rules applied successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to apply firewall rules'], true, true, 500);
break;
/**
* Apply specific firewall rulesets by ID.
* Expects comma-separated ruleset IDs in the 'rulesets' parameter.
*/
case 'firewallApplyRulesets':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$rulesetsParam = isset($_GET['rulesets']) ? trim($_GET['rulesets']) : '';
if (empty($rulesetsParam)) {
$vf->output(['success' => false, 'errors' => 'No ruleset IDs provided'], true, true, 400);
}
$rulesetIds = array_values(array_filter(array_map('intval', explode(',', $rulesetsParam)), function ($id) {
return $id > 0;
}));
if (empty($rulesetIds)) {
$vf->output(['success' => false, 'errors' => 'Invalid ruleset IDs'], true, true, 400);
}
$interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary';
$result = $vf->applyFirewallRulesets($serviceID, $rulesetIds, $interface);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'Firewall rulesets applied successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to apply firewall rulesets'], true, true, 500);
break;
// =================================================================
// IP Address Management
// =================================================================
/**
* Get server IP addresses (from server data).
*/
case 'serverIPs':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$data = $vf->fetchServerData($serviceID);
if ($data) {
$resource = (new ServerResource())->process($data);
$vf->output(['success' => true, 'data' => [
'ipv4' => $resource['primaryNetwork']['ipv4Unformatted'],
'ipv6' => $resource['primaryNetwork']['ipv6Unformatted'],
]], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Unable to retrieve IP addresses'], true, true, 500);
break;
/**
* Add an IPv4 address.
*/
case 'addIPv4':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$result = $vf->addIPv4($serviceID);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'IPv4 address added successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to add IPv4 address. No available addresses or limit reached.'], true, true, 500);
break;
/**
* Remove an IPv4 address.
*/
case 'removeIPv4':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$ipAddress = isset($_GET['ip']) ? trim($_GET['ip']) : '';
if (!filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$vf->output(['success' => false, 'errors' => 'Invalid IPv4 address'], true, true, 400);
}
$result = $vf->removeIPv4($serviceID, $ipAddress);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'IPv4 address removed successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to remove IPv4 address'], true, true, 500);
break;
/**
* Add an IPv6 subnet.
*/
case 'addIPv6':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$result = $vf->addIPv6($serviceID);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'IPv6 subnet added successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to add IPv6 subnet. No available subnets or limit reached.'], true, true, 500);
break;
/**
* Remove an IPv6 subnet.
*/
case 'removeIPv6':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$subnet = isset($_GET['subnet']) ? trim($_GET['subnet']) : '';
if (empty($subnet)) {
$vf->output(['success' => false, 'errors' => 'Invalid IPv6 subnet'], true, true, 400);
}
$result = $vf->removeIPv6($serviceID, $subnet);
if ($result) {
$vf->output(['success' => true, 'data' => ['message' => 'IPv6 subnet removed successfully']], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'Failed to remove IPv6 subnet'], true, true, 500);
break;
// =================================================================
// VNC Console
// =================================================================
/**
* Get VNC console URL.
*/
case 'vnc':
$serviceID = $vf->validateServiceID(true);
if (!$vf->validateUserOwnsService($serviceID)) {
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
}
$result = $vf->getVncConsole($serviceID);
if ($result !== false) {
$vf->output(['success' => true, 'data' => $result], true, true, 200);
}
$vf->output(['success' => false, 'errors' => 'VNC console unavailable. The server may be powered off or VNC is not supported.'], true, true, 500);
break;
default:
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
}