update fix deadcode, security

This commit is contained in:
baodev0
2026-09-28 08:26:29 +07:00
parent 3af59bd9c5
commit 82080b5b84
452 changed files with 4857 additions and 26289 deletions
@@ -2,533 +2,57 @@
namespace App\Http\Controllers\Api\Service;
use App\Actions\VM\ChangePortVMAction;
use App\Actions\VM\PowerOffVMAction;
use App\Actions\VM\PowerOnVMAction;
use App\Http\Controllers\Api\Service\Concerns\ResolvesVm;
use App\Http\Controllers\Controller;
use App\Http\Services\vCenter;
use App\Jobs\VirtualMachine\ChangeIpVMJob;
use App\Jobs\VirtualMachine\ChangeVmPasswordJob;
use App\Models\IpPool;
use App\Models\ModuleVmware;
use App\Models\Os;
use App\Models\Service;
use App\Services\ActivityLogger;
use App\Services\VirtualMachine\VmIpReservationService;
use App\Services\VmProvisionService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmPower;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmNetwork;
use App\Http\Controllers\Api\Service\Concerns\ManagesVmAccess;
class VmController extends Controller
{
use ResolvesVm;
use ManagesVmPower;
use ManagesVmNetwork;
use ManagesVmAccess;
public function __construct(protected ActivityLogger $activityLogger) {}
// -------------------------------------------------------------------------
// Power ON
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-power-on
*
* Body: { "product": <ModuleVmware.id|vmware_id|service_uuid> }
*/
public function powerOn(Request $request, PowerOnVMAction $action): JsonResponse
{
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$lock = $this->acquireVmLock($vm);
if (!$lock) {
return $this->vmLocked();
}
try {
$result = $action->execute($vm);
} finally {
$lock->release();
}
$this->activityLogger->log(
event: 'power_on',
logName: 'VM',
description: "Customer {$request->user()->email} bật VM: {$vm->name} (ID: {$vm->vmware_id})",
subject: $vm,
);
return $this->vmResponse($result, 'Đã gửi lệnh bật VM.');
}
// -------------------------------------------------------------------------
// Power OFF
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-power-off
*
* Body: { "product": <ModuleVmware.id|vmware_id|service_uuid> }
*/
public function powerOff(Request $request, PowerOffVMAction $action): JsonResponse
{
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$lock = $this->acquireVmLock($vm);
if (!$lock) {
return $this->vmLocked();
}
try {
$result = $action->execute($vm);
} finally {
$lock->release();
}
$this->activityLogger->log(
event: 'power_off',
logName: 'VM',
description: "Customer {$request->user()->email} tắt VM: {$vm->name} (ID: {$vm->vmware_id})",
subject: $vm,
);
return $this->vmResponse($result, 'Đã gửi lệnh tắt VM.');
}
// -------------------------------------------------------------------------
// Change IP
// -------------------------------------------------------------------------
/**
* GET /v1/system/vm-ip-options
*
* Query: server_name string Service.server_name
* Lists free IP pool entries in the same network as the VM's current IP.
*/
public function ipOptions(Request $request): JsonResponse
{
$validated = $request->validate([
'server_name' => ['required', 'string', 'max:255'],
]);
$service = $this->findOwnedServiceByServerName($validated['server_name'], $request);
if ($service instanceof JsonResponse) {
return $service;
}
$vm = $service->orderItem?->order?->vmWare?->first();
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM cho dịch vụ này.'], 404);
}
$networkId = $vm->ipAddress?->module_network_id;
if (!$networkId) {
return response()->json(['success' => false, 'message' => 'Không xác định được mạng của VM.'], 422);
}
$ips = app(VmIpReservationService::class)->freeIpsInNetwork($networkId);
return response()->json([
'success' => true,
'data' => $ips->map(fn (IpPool $ip) => [
'ip_address' => $ip->ip_address,
'gateway' => $ip->gateway,
])->values(),
]);
}
/**
* POST /v1/system/vm-change-ip
*
* Body:
* server_name string Service.server_name (e.g. "xasdf.com.vnx")
* ip_address string New IP to assign — must be a free IP from the VM's network pool
*/
/**
* Dispatches ChangeIpVMJob — returns immediately, runs async.
*/
public function changeIp(Request $request, VmIpReservationService $ipReservation): JsonResponse
{
$validated = $request->validate([
'server_name' => ['required', 'string', 'max:255'],
'ip_address' => ['required', 'ip'],
'dns' => ['nullable', 'ip'],
'guest_user' => ['required', 'string', 'max:255'],
'guest_password' => ['required', 'string', 'max:255'],
]);
$service = $this->findOwnedServiceByServerName($validated['server_name'], $request);
if ($service instanceof JsonResponse) {
return $service;
}
$vm = $service->orderItem?->order?->vmWare?->first();
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM cho dịch vụ này.'], 404);
}
$networkId = $vm->ipAddress?->module_network_id;
if (!$networkId) {
return response()->json(['success' => false, 'message' => 'Không xác định được mạng của VM.'], 422);
}
$reservation = $ipReservation->reserveInNetwork($networkId, $validated['ip_address'], $service->id);
if (!$reservation) {
return response()->json(['success' => false, 'message' => 'IP không thuộc pool khả dụng hoặc đã được sử dụng.'], 422);
}
$oldIpPoolId = IpPool::where('module_network_id', $networkId)
->where('service_id', $service->id)
->where('status', 'assigned')
->where('id', '!=', $reservation['ip_pool_id'])
->value('id');
// Auto-detect OS type from the service's order item OS group
$osGroup = strtolower($service->orderItem?->os?->osGroup?->name ?? '');
$osType = (str_contains($osGroup, 'linux') || str_contains($osGroup, 'ubuntu') || str_contains($osGroup, 'centos'))
? 'linux'
: 'windows';
$dns = $validated['dns'] ?? '8.8.8.8';
// ponytail: don't release — lock expires after 5 min while the async job runs
$lock = $this->acquireVmLock($vm, 300);
if (!$lock) {
$ipReservation->releaseByPoolId($reservation['ip_pool_id']);
return $this->vmLocked();
}
ChangeIpVMJob::dispatch(
$vm->id,
$validated['ip_address'],
$reservation['gateway'],
$osType,
$dns,
$validated['guest_user'],
$validated['guest_password'],
$reservation['ip_pool_id'],
$oldIpPoolId,
);
$this->activityLogger->log(
event: 'change_ip',
logName: 'VM',
description: "Customer {$request->user()->email} yêu cầu đổi IP VM: {$vm->name} → {$validated['ip_address']}",
subject: $vm,
newValues: ['ip_address' => $validated['ip_address'], 'gateway' => $reservation['gateway'], 'os_type' => $osType],
);
return response()->json([
'success' => true,
'message' => 'Yêu cầu đổi IP đã được ghi nhận. Hệ thống sẽ xử lý trong vài phút.',
]);
}
/**
* Look up a Service by server_name, verify ownership and active status.
* Returns JsonResponse on error, Service on success.
*/
private function findOwnedServiceByServerName(string $serverName, Request $request): Service|JsonResponse
{
$customer = $request->user();
// VM chain: Service → orderItem → order → vmWare (same as ServiceClientDetailResource)
$service = Service::with([
'orderItem.order.vmWare.vCenter',
'orderItem.order.vmWare.ipAddress.host.datacenter.vCenter',
'orderItem.os.osGroup',
])
->where('server_name', $serverName)
->where('customer_id', $customer->id)
->first();
if (!$service) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy dịch vụ.'], 404);
}
if ($service->status !== 'active') {
return response()->json(['success' => false, 'message' => 'Dịch vụ không ở trạng thái hoạt động, không thể thao tác.'], 403);
}
return $service;
}
// -------------------------------------------------------------------------
// Change Port (RDP)
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-change-port
*
* Body:
* product int|string ModuleVmware.id|vmware_id|service_uuid
* new_port int New RDP port (1024–65535)
*/
public function changePort(Request $request, ChangePortVMAction $action): JsonResponse
{
$validated = $request->validate([
'product' => ['required'],
'new_port' => ['required', 'integer', 'min:1024', 'max:65535'],
]);
$vm = $this->resolveVmById($validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$lock = $this->acquireVmLock($vm);
if (!$lock) {
return $this->vmLocked();
}
try {
$result = $action->execute($vm, (string) $validated['new_port']);
} finally {
$lock->release();
}
// changeRDPPortWindows returns array on success/failure or string on login fail
if ($result === 'Login_Again' || $result === 'Login_Fail') {
return response()->json([
'success' => false,
'message' => 'Không thể đăng nhập vào vCenter.',
], 400);
}
if (is_array($result)) {
$ok = isset($result['success'])
? (bool) $result['success']
: !($result['error'] ?? true);
if ($ok) {
$this->activityLogger->log(
event: 'change_port',
logName: 'VM',
description: "Customer {$request->user()->email} đổi cổng RDP VM: {$vm->name} → port {$validated['new_port']}",
subject: $vm,
newValues: ['new_port' => $validated['new_port']],
);
}
return response()->json([
'success' => $ok,
'message' => $ok ? 'Đổi cổng RDP thành công.' : ($result['message'] ?? 'Đổi cổng thất bại.'),
'data' => $result,
], $ok ? 200 : 400);
}
\Illuminate\Support\Facades\Log::error('changePort: unexpected vCenter response', [
'vm_id' => $vm->id,
'result' => $result,
]);
return response()->json([
'success' => false,
'message' => 'Phản hồi không hợp lệ từ vCenter.',
], 400);
}
// -------------------------------------------------------------------------
// Console Ticket
// -------------------------------------------------------------------------
public function consoleTicket(Request $request, vCenter $serviceVCenter): JsonResponse
{
$productId = $request->get('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Please choose product']);
}
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$result = $this->getConsoleTicket($serviceVCenter, $vm);
if ($result instanceof JsonResponse) {
return $result;
}
$this->activityLogger->log(
event: 'console_access',
logName: 'VM',
description: "Customer {$request->user()->email} truy cập console VM: {$vm->name} (ID: {$vm->vmware_id})",
subject: $vm,
newValues: ['product_id' => $productId, 'vm_name' => $vm->name],
);
return response()->json([
'error' => false,
'data' => $result['data'],
]);
}
// -------------------------------------------------------------------------
// Restart VM
// -------------------------------------------------------------------------
public function restart(Request $request, vCenter $serviceVCenter): JsonResponse
{
$productId = $request->input('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Thiếu product id'], 422);
}
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$lock = $this->acquireVmLock($vm);
if (!$lock) {
return $this->vmLocked();
}
try {
$result = $serviceVCenter->restartVM($vm, $vm->vmware_id);
} finally {
$lock->release();
}
if (is_string($result) || ($result['error'] ?? true)) {
return response()->json([
'error' => true,
'message' => is_string($result) ? $result : ($result['data'] ?? 'Không thể restart VM'),
], 400);
}
$this->activityLogger->log(
event: 'restart',
logName: 'VM',
description: "Customer {$request->user()->email} restart VM: {$vm->name} (ID: {$vm->vmware_id})",
subject: $vm,
);
return response()->json([
'error' => false,
'message' => 'Đang restart máy ảo vui lòng chờ!',
'data' => $result['data'] ?? 'success',
]);
}
// -------------------------------------------------------------------------
// Change Password
// -------------------------------------------------------------------------
/**
* POST /v1/system/vm-change-password
*
* Body: { product, current_password, new_password }
* Dispatches ChangeVmPasswordJob — returns immediately, runs async.
*/
public function changePassword(Request $request): JsonResponse
{
$validated = $request->validate([
'product' => ['required'],
'current_password' => ['required', 'string'],
'new_password' => ['required', 'string', 'min:8'],
]);
$vm = $this->resolveVmById($validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
// ponytail: don't release — lock expires after 5 min while the async job runs
$lock = $this->acquireVmLock($vm, 300);
if (!$lock) {
return $this->vmLocked();
}
// Detect OS family from VM's os_id record
$os = $vm->os_id ? Os::with('osGroup')->find($vm->os_id) : null;
$osGroupName = strtolower($os?->osGroup?->name ?? '');
$osFamily = str_contains($osGroupName, 'windows')
? VmProvisionService::OS_WINDOWS
: VmProvisionService::OS_LINUX;
ChangeVmPasswordJob::dispatch(
$vm->id,
$vm->vmware_id,
$osFamily,
$validated['new_password'],
$validated['current_password'],
);
$this->activityLogger->log(
event: 'change_password',
logName: 'VM',
description: "Customer {$request->user()->email} yêu cầu đổi mật khẩu VM: {$vm->name}",
subject: $vm,
newValues: ['os_family' => $osFamily],
);
return response()->json([
'success' => true,
'message' => 'Yêu cầu đổi mật khẩu đã được ghi nhận. Hệ thống sẽ xử lý trong vài phút.',
]);
}
// -------------------------------------------------------------------------
// Power State
// -------------------------------------------------------------------------
/**
* GET /v1/system/vm-power-state?product={id|uuid|vmware_id}
*
* Calls vSphere REST API directly: GET /rest/vcenter/vm/{vmware_id}/power
* Returns: { success: true, data: { state: "POWERED_ON"|"POWERED_OFF"|"SUSPENDED", clean_power_off: bool } }
*/
public function powerState(Request $request, vCenter $serviceVCenter): JsonResponse
{
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVmById($productId, $request, requireActiveService: false);
if ($vm instanceof JsonResponse) {
return $vm;
}
$result = $serviceVCenter->getVmPowerState($vm, $vm->vmware_id);
if ($result === 'Login_Again') {
return response()->json(['success' => false, 'message' => 'Không thể đăng nhập vào vCenter.'], 400);
}
if (is_array($result) && ($result['error'] ?? true)) {
return response()->json([
'success' => false,
'message' => $result['data'] ?? 'Không thể lấy trạng thái VM.',
], 400);
}
return response()->json([
'success' => true,
'data' => [
'state' => $result['data']['state'] ?? null,
'clean_power_off' => $result['data']['clean_power_off'] ?? null,
],
]);
}
// -------------------------------------------------------------------------
// Shared helpers
// -------------------------------------------------------------------------