Files
device-management/app/Jobs/SyncDeviceBaseCredential.php
Wian Drs 60946ddf37
tests / ci (push) Has been cancelled
update lisensi prosess
2026-08-25 13:43:55 +07:00

267 lines
16 KiB
PHP

<?php
namespace App\Jobs;
use App\Models\Device;
use App\Models\TenantDeviceSetting;
use App\Network\Clients\Hisfocus\HisfocusSshClient;
use App\Network\Clients\Hisfocus\HisfocusWebClient;
use App\Network\Clients\Hsgq\HsgqSshClient;
use App\Network\Clients\Hsgq\HsgqTelnetClient;
use App\Network\Clients\Hsgq\HsgqWebClient;
use App\Network\Clients\RouterOs\RouterOsApiClient;
use App\Network\Clients\Zte\ZteSshClient;
use App\Network\Drivers\Hisfocus\HisfocusOltDriver;
use App\Network\Drivers\Hsgq\HsgqOltDriver;
use App\Network\Drivers\Mikrotik\MikrotikDriver;
use App\Network\Drivers\Zte\ZteC3xxDriver;
use App\Services\LicenseEntitlementService;
use App\Support\TenantContext;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;
class SyncDeviceBaseCredential implements ShouldQueue
{
use Queueable;
public int $tries = 1;
public int $timeout = 55;
public function __construct(
public readonly int $tenantId,
public readonly int $deviceId,
public readonly ?string $oldUsername = null,
) {}
public function handle(TenantContext $context): void
{
$context->set($this->tenantId);
setPermissionsTeamId($this->tenantId);
try {
if (! app(LicenseEntitlementService::class)->allowsUse($this->tenantId)) {
Device::withoutGlobalScope('tenant')->whereKey($this->deviceId)->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'LICENSE_INACTIVE', 'base_sync_message' => 'Menunggu lisensi tenant aktif.']);
return;
}
$device = Device::with(['vendor', 'model', 'credentials'])->find($this->deviceId);
$setting = TenantDeviceSetting::where('tenant_id', $this->tenantId)->first();
if (! $device || ! $setting) {
return;
}
$device->update(['base_sync_status' => 'processing', 'base_sync_attempted_at' => now(), 'base_sync_error_code' => null]);
if ($device->vendor?->slug === 'zte' && $device->hasConnection('ssh') && preg_match('/C3(?:00|20)/i', $device->model?->name ?? '')) {
$this->syncZte($device, $setting);
return;
}
if ($device->vendor?->slug === 'hsgq' && ($device->hasConnection('ssh') || $device->hasConnection('telnet'))) {
$this->syncHsgq($device, $setting);
return;
}
if ($device->vendor?->slug === 'hisfocus' && ($device->hasConnection('ssh') || $device->hasConnection('telnet'))) {
$this->syncHisfocus($device, $setting);
return;
}
if ($device->vendor?->slug !== 'mikrotik' || ! $device->hasConnection('routeros_api')) {
$device->update(['base_sync_status' => 'unsupported', 'base_sync_error_code' => 'DRIVER_NOT_AVAILABLE', 'base_sync_message' => "Driver rotasi Base User untuk {$device->vendor?->name} / {$device->connection_type} belum tersedia.", 'activation_status' => 'failed', 'activation_message' => 'DRIVER_NOT_AVAILABLE']);
return;
}
$credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'Base User RADIQ' ? 2 : (int) $item->is_master)->first();
if (! $credential) {
$device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential aktif untuk masuk ke perangkat.']);
return;
}
$driver = new MikrotikDriver($this->client($device, $credential->username, $credential->password, $setting));
$driver->provisionBaseAccess($setting->base_username, $setting->base_password);
$newDriver = new MikrotikDriver($this->client($device, $setting->base_username, $setting->base_password, $setting));
$newDriver->testConnection();
$previousUsername = $this->oldUsername ?? ($credential->name === 'Base User RADIQ' ? $credential->username : null);
if ($previousUsername && $previousUsername !== $setting->base_username) {
$newDriver->deleteUser($previousUsername);
}
$device->credentials()->update(['is_master' => false]);
$device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [
'tenant_id' => $device->tenant_id, 'username' => $setting->base_username,
'password' => $setting->base_password, 'connection_type' => $device->connection_type,
'privilege_type' => 'master', 'is_master' => true, 'is_active' => true,
'last_verified_at' => now(),
]);
$device->update(['base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => 'Base User berhasil diperbarui pada perangkat.', 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now()]);
} catch (Throwable $exception) {
report($exception);
$code = str($exception->getMessage())->before(':')->limit(64)->toString() ?: 'UNKNOWN_ERROR';
$pending = in_array($code, ['DEVICE_UNREACHABLE', 'CONNECTION_TIMEOUT', 'CONNECTION_FAILED'], true);
Device::whereKey($this->deviceId)->update([
'base_sync_status' => $pending ? 'pending' : 'failed',
'base_sync_error_code' => $code,
'base_sync_message' => $pending ? 'Perangkat belum terhubung; scheduler akan mencoba kembali.' : 'Rotasi Base User gagal dan perlu diperiksa.',
'status' => $pending ? 'offline' : 'unknown',
'activation_status' => $pending ? 'pending' : 'failed',
'activation_message' => $code,
]);
} finally {
$context->clear();
setPermissionsTeamId(null);
}
}
private function client(Device $device, string $username, string $password, TenantDeviceSetting $setting): RouterOsApiClient
{
return new RouterOsApiClient($device->management_address, $device->portFor('routeros_api'), $username, $password, $setting->connection_timeout, $setting->use_tls, $setting->verify_tls);
}
private function syncZte(Device $device, TenantDeviceSetting $setting): void
{
$credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'Base User RADIQ' ? 2 : (int) $item->is_master)->first();
if (! $credential) {
$device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential login dan enable ZTE.', 'activation_status' => 'failed', 'activation_message' => 'CREDENTIAL_REQUIRED']);
return;
}
$driver = new ZteC3xxDriver(new ZteSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout));
$driver->provisionBaseAccess($credential->username, $credential->password, $credential->enable_password, $setting->base_username, $setting->base_password);
$driver->testLogin($setting->base_username, $setting->base_password, $credential->enable_password);
$previousUsername = $this->oldUsername ?? ($credential->name === 'Base User RADIQ' ? $credential->username : null);
if ($previousUsername && $previousUsername !== $setting->base_username) {
$driver->deleteUser($setting->base_username, $setting->base_password, $credential->enable_password, $previousUsername);
}
$device->credentials()->update(['is_master' => false]);
$device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [
'tenant_id' => $device->tenant_id, 'username' => $setting->base_username,
'password' => $setting->base_password, 'enable_password' => $credential->enable_password,
'connection_type' => 'ssh', 'privilege_type' => 'master',
'is_master' => true, 'is_active' => true, 'last_verified_at' => now(),
]);
$device->update(['base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => 'Base User privilege 15 berhasil diperbarui pada ZTE.', 'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(), 'activation_status' => 'active', 'activation_message' => 'Base User ZTE privilege 15 berhasil dibuat dan diverifikasi.', 'activated_at' => now()]);
}
public function failed(?Throwable $exception): void
{
Device::withoutGlobalScope('tenant')->whereKey($this->deviceId)->update([
'base_sync_status' => 'failed',
'base_sync_error_code' => 'JOB_TIMEOUT',
'base_sync_message' => 'Proses koneksi perangkat melewati batas waktu queue.',
'activation_status' => 'failed',
'activation_message' => 'JOB_TIMEOUT',
]);
}
private function syncHsgq(Device $device, TenantDeviceSetting $setting): void
{
$credential = $device->credentials->where('is_active', true)->sortByDesc(fn ($item) => $item->name === 'HSGQ Root' ? 2 : (int) $item->is_master)->first();
if (! $credential || $credential->username !== 'root') {
$device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'ROOT_CREDENTIAL_REQUIRED', 'base_sync_message' => 'HSGQ membutuhkan credential root aktif.', 'activation_status' => 'failed', 'activation_message' => 'ROOT_CREDENTIAL_REQUIRED']);
return;
}
$webClient = new HsgqWebClient($device->management_address, $setting->connection_timeout);
$hsgqPrimary = $device->hasConnection('ssh')
? new HsgqSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout)
: new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout);
$driver = new HsgqOltDriver(
$hsgqPrimary,
$device->hasConnection('ssh') && $device->hasConnection('telnet') ? new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout) : null,
$webClient,
);
$rootPassword = $credential->password;
if ($device->remove_legacy_users_on_activation && $rootPassword !== $setting->base_password) {
$driver->rotateRootPassword($rootPassword, $setting->base_password, $credential->enable_password);
$rootPassword = $setting->base_password;
} else {
$driver->testLogin('root', $rootPassword, $credential->enable_password);
}
$device->credentials()->update(['is_master' => false]);
$device->credentials()->updateOrCreate(['name' => 'HSGQ Root'], [
'tenant_id' => $device->tenant_id, 'username' => 'root', 'password' => $rootPassword,
'enable_password' => $credential->enable_password,
'connection_type' => 'ssh', 'privilege_type' => 'master', 'is_master' => true,
'is_active' => true, 'last_verified_at' => now(),
]);
$message = $device->remove_legacy_users_on_activation
? 'Password root HSGQ telah disinkronkan dengan Base Password tenant.'
: 'Credential root awal dipertahankan sesuai opsi perangkat.';
$webClient->login('root', $rootPassword);
$facts = $webClient->boardInfo();
$device->update([
'serial_number' => $facts['sn'] ?? $device->serial_number,
'firmware_version' => $facts['fw_ver'] ?? $device->firmware_version,
'software_version' => $facts['sys_ver'] ?? $device->software_version,
'device_facts' => array_merge($device->device_facts ?? [], $facts, ['management_transports' => ['ssh', 'telnet', 'webgui']]),
'base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => $message,
'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(),
'activation_status' => 'active', 'activation_message' => $message, 'activated_at' => now(),
]);
}
private function syncHisfocus(Device $device, TenantDeviceSetting $setting): void
{
$credential = $device->credentials->where('is_active', true)->sortByDesc('is_master')->first();
if (! $credential) {
$device->update(['base_sync_status' => 'pending', 'base_sync_error_code' => 'CREDENTIAL_REQUIRED', 'base_sync_message' => 'Menunggu credential awal Hisfocus.', 'activation_status' => 'failed', 'activation_message' => 'CREDENTIAL_REQUIRED']);
return;
}
$client = $device->hasConnection('telnet')
? new HsgqTelnetClient($device->management_address, $device->portFor('telnet'), $setting->connection_timeout)
: new HisfocusSshClient($device->management_address, $device->portFor('ssh'), $setting->connection_timeout);
$driver = new HisfocusOltDriver($client);
$facts = $driver->probe($credential->username, $credential->password, $credential->enable_password);
$driver->provisionBaseAccess($credential->username, $credential->password, $credential->enable_password, $setting->base_username, $setting->base_password);
$webPort = $device->portFor('web_http');
if ($webPort) {
$web = new HisfocusWebClient($device->management_address, $webPort, $setting->connection_timeout);
$webUsers = $web->users($credential->username, $credential->password);
if (! isset($webUsers[$setting->base_username])) {
$web->addUser($credential->username, $credential->password, $setting->base_username, $setting->base_password, 'Administrator');
} elseif (! $web->canLogin($setting->base_username, $setting->base_password)) {
$web->deleteUser($credential->username, $credential->password, $setting->base_username);
$web->addUser($credential->username, $credential->password, $setting->base_username, $setting->base_password, 'Administrator');
}
if (! $web->canLogin($setting->base_username, $setting->base_password)) {
throw new \RuntimeException('HISFOCUS_WEB_LOGIN_FAILED: Base User belum dapat login ke WebGUI.');
}
}
if ($device->remove_legacy_users_on_activation && $credential->username !== $setting->base_username) {
$driver->deleteUser($setting->base_username, $setting->base_password, $credential->enable_password, $credential->username);
}
$device->credentials()->update(['is_master' => false]);
$device->credentials()->updateOrCreate(['name' => 'Base User RADIQ'], [
'tenant_id' => $device->tenant_id, 'username' => $setting->base_username,
'password' => $setting->base_password, 'enable_password' => $credential->enable_password ?: $credential->password,
'connection_type' => $device->connection_type, 'privilege_type' => 'master',
'is_master' => true, 'is_active' => true, 'last_verified_at' => now(),
]);
$message = $device->remove_legacy_users_on_activation
? 'Base User Hisfocus dibuat dan user login awal dihapus.'
: 'Base User Hisfocus dibuat; user login awal dipertahankan.';
$device->update([
'serial_number' => $facts['serial_number'] ?? $device->serial_number,
'firmware_version' => $facts['firmware_version'] ?? $device->firmware_version,
'device_facts' => array_merge($device->device_facts ?? [], $facts, ['user_management' => 'multi_user', 'roles' => ['administrator', 'operator', 'guest'], 'web_management_port' => $webPort, 'user_stores' => $webPort ? ['cli', 'webgui'] : ['cli']]),
'base_sync_status' => 'synced', 'base_sync_error_code' => null, 'base_sync_message' => $message,
'base_synced_at' => now(), 'status' => 'online', 'last_seen_at' => now(),
'activation_status' => 'active', 'activation_message' => $message, 'activated_at' => now(),
]);
}
}