261 lines
15 KiB
PHP
261 lines
15 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\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 {
|
|
$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(),
|
|
]);
|
|
}
|
|
}
|