update change password

This commit is contained in:
pbaodev
2025-08-24 22:41:15 +07:00
parent 6b358afd4b
commit 197fe413e5
19 changed files with 699 additions and 193 deletions
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\frontend;
use App\Http\Controllers\Controller;
use App\Http\Requests\Vm\SnapShotRequest;
use App\Jobs\VirtualMachine\Action\ResetVmPasswordJob;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Repositories\Invoice\InvoiceRepositoryInterface;
@@ -552,109 +553,50 @@ class ServiceController extends Controller
/**
* Reset VM password (Windows/Linux) via vCenter helpers
*/
public function resetVMPassword(Request $request, vCenter $serviceVCenter)
public function resetVMPassword(Request $request)
{
try {
$user = customer();
if (!$user) {
return response()->json([
'error' => true,
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'),
], 401);
}
$validated = FacadesValidator::make($request->all(), [
'product_id' => 'required|string|max:255',
'new_password' => 'required|string|min:8|max:128',
]);
if ($validated->fails()) {
return response()->json([
'error' => true,
'message' => __('Dữ liệu không hợp lệ'),
'errors' => $validated->errors(),
], 422);
}
$productId = $this->sanitizeInput($request->product_id);
$newPassword = (string) $request->new_password;
// Load product and ensure ownership
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')
->where('vmware_id', $productId)
->where('customer_id', $user->id)
->first();
if (!$product) {
return response()->json([
'error' => true,
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập'),
], 404);
}
$vcenter = $product->ipAddress->host->datacenter->vCenter ?? null;
if (!$vcenter) {
return response()->json([
'error' => true,
'message' => __('Cấu hình vCenter không hợp lệ'),
], 400);
}
// Login to vCenter
if (!$serviceVCenter->loginServer($vcenter)) {
return response()->json([
'error' => true,
'message' => __('Không thể kết nối đến vCenter'),
], 500);
}
// Determine OS family
$osFamily = strtoupper($product->os->osGroup->name ?? '');
$isWindows = str_contains($osFamily, 'WINDOWS');
// Prepare data
$ip = $product->ipAddress->ip ?? null;
if (!$ip) {
return response()->json([
'error' => true,
'message' => __('Không tìm thấy IP của máy ảo'),
], 400);
}
$payload = [
'ip' => $ip,
'user' => $isWindows ? 'administrator' : 'root',
'password' => $product->password ?? 'TGS@123', // fallback if field exists; adjust per model
'new_password' => $newPassword,
];
$ok = $isWindows
? $serviceVCenter->change_password_windows($payload)
: $serviceVCenter->change_password_linux($payload);
if (!$ok) {
return response()->json([
'error' => true,
'message' => __('Không thể đặt lại mật khẩu. Vui lòng thử lại sau.'),
], 500);
}
return response()->json([
'error' => false,
'message' => __('Đặt lại mật khẩu thành công'),
]);
} catch (\Exception $e) {
Log::error('Lỗi reset VM password', [
'product_id' => $request->product_id ?? null,
'error' => $e->getMessage(),
]);
return response()->json([
'error' => true,
'message' => __('Có lỗi xảy ra khi đặt lại mật khẩu'),
], 500);
$user = customer();
if (!$user) {
return response()->json(['error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')], 401);
}
$validated = Validator::make($request->all(), [
'product_id' => 'required|string|max:255',
'new_password' => 'required|string|min:8|max:128',
'old_password' => 'required|string|min:8|max:128',
]);
if ($validated->fails()) {
return response()->json(['error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors()], 422);
}
$productId = $this->sanitizeInput($request->product_id);
$newPassword = (string) $request->new_password;
$oldPassword = (string) $request->old_password;
// load & ownership check nhanh (không cần toàn bộ nặng nề vì Job sẽ retry)
$product = ModuleVmware::where('vmware_id', $productId)
->where('customer_id', $user->id)
->first();
if (!$product) {
return response()->json(['error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')], 404);
}
// Dispatch job với delay 5s
ResetVmPasswordJob::dispatch(
productId: $productId,
newPassword: $newPassword,
oldPassword: $oldPassword,
userId: $user->id,
email: $user->email
)->delay(now()->addSeconds(5));
// trả accepted để UI biết đang xử lý nền
return redirect()->back()->with('success', 'Đã gửi yêu cầu tạo đặt lại mật khẩu. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.');
}
/**
* Change Remote Desktop port (Windows only)
*/
@@ -827,8 +769,8 @@ class ServiceController extends Controller
// Fetch product price based on invoice item link
$invoiceItem = InvoiceItem::whereHas('invoice', function ($q) use ($user) {
$q->where('customer_id', $user->id);
})
$q->where('customer_id', $user->id);
})
->where('product_type', 4) // VPS
->whereHas('invoice', fn($q) => $q->where('status', '!=', 0))
->first();
@@ -1131,7 +1073,7 @@ class ServiceController extends Controller
], 401);
}
$validator = \Validator::make($request->all(), [
$validator = Validator::make($request->all(), [
'product_id' => 'required|string|max:255',
]);
if ($validator->fails()) {
@@ -1158,7 +1100,7 @@ class ServiceController extends Controller
$result = $vCenterService->ShutdownGuestVirtualMachine($product, $product->vmware_id);
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
// Clear VM info cache after stopping VM
\Cache::forget('vm_info_' . $product->vmware_id);
Cache::forget('the_gioi_so_database_the_gioi_so_cache:vm_info_' . $product->vmware_id);
return response()->json([
'error' => false,
'message' => __('Yêu cầu tắt máy ảo đã được gửi thành công.')
@@ -1287,9 +1229,9 @@ class ServiceController extends Controller
// Debug: Log invoice lookup
Log::info('Looking for invoice', ['invoice_id' => $request->invoice_id]);
$invoice = Invoice::find($request->invoice_id);
// Debug: Log invoice result
Log::info('Invoice lookup result', [
'invoice_id' => $request->invoice_id,
+157 -18
View File
@@ -15,7 +15,6 @@ use phpseclib3\Net\SSH2;
class vCenter
{
protected $process = null;
public ?string $token = null;
public string $apiUri = '10.10.10.6';
private string $username = 'administrator@vsphere.local';
@@ -23,7 +22,8 @@ class vCenter
private int $diskCapacity = 1073741824;
private int $memory = 1024;
protected string $os;
private ?string $os = null;
private SSH2|WinRMClient|null $process = null;
public function loginServer(\App\Models\Vcenter $vcenterModel): bool|string
{
@@ -325,6 +325,142 @@ class vCenter
}
}
// Login SSH
public function loginSSH(): bool
{
$conn = $this->LoginAuth();
if ($conn) {
Log::info("✅ Connected to vCenter via SSH successfully");
return true;
}
Log::error("❌ Failed to connect to vCenter via SSH");
return false;
}
public function runPsCommand(string $psCommand): string
{
$auth = $this->LoginAuth();
if (!$auth)
return "Login_Fail";
// gọi trực tiếp -Command, thu cả STDERR
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
try {
$stream = ssh2_exec($auth, $action);
if (!$stream)
throw new \RuntimeException("ssh2_exec failed");
$err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
stream_set_blocking($stream, true);
stream_set_blocking($err, true);
$out = stream_get_contents($stream) ?: '';
$errOut = stream_get_contents($err) ?: '';
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
protected function CommandRSP(string $cmd)
{
$auth = $this->LoginAuth();
if (!$auth)
return "Login_Fail";
// Escape để dùng trong chuỗi single-quoted của PowerShell
$esc = fn(string $v) => str_replace("'", "''", $v);
$server = $esc($this->apiUri ?? '');
$user = $esc($this->username ?? '');
$pass = $esc($this->password ?? '');
// Ghép script PowerShell hoàn chỉnh, chạy trong 1 scriptblock
// Lưu ý: dùng placeholder rồi strtr để tránh lẫn dấu ngoặc
$psTemplate = <<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
# Thực thi lệnh công việc (đã được xây dựng sẵn, ví dụ: & 'C:\path\script.ps1' -Param 'Value' ... )
& {
<CMD>
} | Out-String
PS;
// Chèn dữ liệu
$psScript = strtr($psTemplate, [
'<SERVER>' => $server,
'<USER>' => $user,
'<PASS>' => $pass,
'<CMD>' => $cmd, // ở đây $cmd KHÔNG cần ngoặc kép bao ngoài
]);
// Encode UTF-16LE → Base64 cho -EncodedCommand
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
// Dùng -EncodedCommand để tránh mọi lỗi escape/quote
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$stream = ssh2_exec($auth, $action);
if (!$stream)
return "ssh2_exec failed";
$err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
stream_set_blocking($stream, true);
stream_set_blocking($err, true);
$out = stream_get_contents($stream) ?: '';
$errOut = stream_get_contents($err) ?: '';
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
{
// File ps1 chạy được ngoài PowerShell: bạn đã test ok
$psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1";
// Escape chuỗi cho PowerShell single-quoted
$q = fn(string $v) => str_replace("'", "''", $v);
// Xây args kiểu: -Key 'Value'
$args = " -VMName '" . $q($vmName) . "'";
$args .= " -GuestUser '" . $q($guestUser) . "'";
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
$args .= " -TargetUser '" . $q($targetUser) . "'";
$args .= " -NewPassword '" . $q($newPassword) . "'";
// Gọi file trong cùng phiên PS đã Connect-VIServer (nhờ Command())
// Thêm Out-String để ép JSON nằm trên STDOUT gọn
$cmd = "& '$psFile'$args | Out-String";
$raw = $this->CommandRSP($cmd); // <-- dùng lại Command() của bạn
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
return "Login_Fail";
}
// Thử parse JSON (có thể lẫn banner/log)
$trim = trim($raw);
$data = json_decode($trim, true);
if (json_last_error() !== JSON_ERROR_NONE) {
if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) {
$data = json_decode($m[1], true);
}
}
return $data ?: $raw; // nếu chưa parse được, trả raw để debug
}
private function getAllNetwork()
{
$check = $this->checkApiExits();
@@ -457,7 +593,7 @@ class vCenter
try {
Log::info('Bắt đầu clone VM với timeout 300 giây', ['template' => $template, 'target_name' => $body['spec']['name']]);
$response = Http::withoutVerifying()
->timeout(300) // Tăng timeout lên 5 phút cho quá trình clone
->connectTimeout(30) // Giữ connect timeout ngắn để phát hiện lỗi kết nối sớm
@@ -1854,8 +1990,11 @@ class vCenter
return false;
}
}
public function change_password_linux(array $data = ['ip' => '103.42.58.37', 'user' => 'root', 'password' => 'TGS@123', 'new_password' => '']): bool
/*
* ip = 103.42.58.37
* pwd = TGS@123
*/
public function change_password_linux(array $data = ['ip' => '10.10.10.173', 'user' => 'admin', 'password' => 'TGS@123', 'new_password' => '']): bool
{
try {
// Kiểm tra mật khẩu mới
@@ -2139,14 +2278,14 @@ class vCenter
$dns = '8.8.8.8';
$vmware_name = '';
$vCenter = null;
// Check if it's ModuleVmware (new structure)
if ($product && isset($product->ipAddress) && $product->ipAddress) {
Log::info('✅ Using ModuleVmware structure for assignIPWindows', [
'module_vmware_id' => $product->id,
'vmware_id' => $vmware_id
]);
// Extract from ModuleVmware->ipAddress relationship
$ipAddress = $product->ipAddress->ip ?? null;
$gateway = $product->ipAddress->gateway ?? null;
@@ -2154,13 +2293,13 @@ class vCenter
$dns = $product->ipAddress->dns ?? '8.8.8.8';
$vmware_name = $product->name ?? $vmware_id;
$vCenter = $product->ipAddress->host->datacenter->vCenter ?? null;
} elseif ($product && isset($product->IpAddress) && $product->IpAddress) {
Log::info('✅ Using old Product structure for assignIPWindows', [
'product_id' => $product->id,
'vmware_id' => $vmware_id
]);
// Extract from old Product->IpAddress relationship
$ipAddress = $product->IpAddress->ip ?? null;
$gateway = $product->IpAddress->gateway ?? null;
@@ -2169,7 +2308,7 @@ class vCenter
$vmware_name = $product->name ?? $vmware_id;
$vCenter = $product->IpAddress->Host->Datacenter->vCenter ?? null;
}
// Validate required data
if (!$ipAddress || !$gateway || !$vCenter) {
Log::error('❌ assignIPWindows: Missing required data', [
@@ -2186,10 +2325,10 @@ class vCenter
}
$this->SetConfig($vCenter);
// Convert netmask to prefix length (e.g., 255.255.255.0 = 24)
$maskBits = $this->convertNetmaskToPrefixLength($netmask);
Log::channel('Custom')->info("===================================================================");
Log::channel('Custom')->info("Assign IP to Windows VM - $vmware_name");
Log::channel('Custom')->info("IP: $ipAddress, Gateway: $gateway, DNS: $dns, MaskBits: $maskBits");
@@ -2197,14 +2336,14 @@ class vCenter
// Create PowerShell command with dynamic values
$Command = "\$vmName = '$vmware_name'; \$IP = '$ipAddress'; \$MaskBits = $maskBits; \$Gateway = '$gateway'; \$DNS = '$dns'; cd /; C:\CLISave\AssignIP\assignIPWindows.ps1;";
Log::channel('Custom')->info("Executing Command: $Command");
$output = $this->Command($Command);
Log::channel('Custom')->info("Script output: " . $output);
Log::channel('Custom')->info("===================================================================");
// Check if execution was successful
if (strpos($output, '🎉') !== false || strpos($output, 'successfully') !== false) {
return [
@@ -2219,7 +2358,7 @@ class vCenter
'output' => $output
];
}
} catch (\Exception $e) {
Log::channel('Custom')->error("Exception in assignIPWindows: " . $e->getMessage());
return [
@@ -2261,7 +2400,7 @@ class vCenter
'255.128.0.0' => 9,
'255.0.0.0' => 8
];
return $netmaskMap[$netmask] ?? 24; // Default to /24 if not found
}