'guest', 'RADIQ-WRITE' => 'operator', 'RADIQ-NOC' => 'administrator']; public function __construct(private readonly HsgqCliClient $client) {} /** @return array */ public function probe(string $username, string $password, ?string $enablePassword): array { $this->client->connect($username, $password); try { $this->client->enterEnable($enablePassword ?: $password); $facts = ['management_transport' => str_contains($this->client::class, 'Ssh') ? 'ssh' : 'telnet', 'supported_probes' => []]; foreach (['show version', 'show system information', 'show system'] as $command) { try { $output = $this->client->command($command); $facts['supported_probes'][] = $command; $facts += $this->parseFacts($output); } catch (\Throwable) { // Firmware Hisfocus berbeda-beda; satu command unsupported tidak menggagalkan probe lain. } } return $facts; } finally { $this->client->disconnect(); } } public function provisionBaseAccess(string $loginUsername, string $loginPassword, ?string $enablePassword, string $newUsername, string $newPassword): array { $this->validateAccount($newUsername, $newPassword); $this->client->connect($loginUsername, $loginPassword); try { $this->client->enterEnable($enablePassword ?: $loginPassword); $users = $this->client->command('show users'); $this->client->command('configure terminal'); $this->client->command($this->userExists($users, $newUsername) ? "user change {$newUsername} {$newPassword}" : "user create administrator {$newUsername} {$newPassword}"); $this->save(); } finally { $this->client->disconnect(); } $this->testLogin($newUsername, $newPassword, $enablePassword ?: $loginPassword); return ['username' => $newUsername, 'role' => 'administrator']; } public function testLogin(string $username, string $password, ?string $enablePassword): array { $this->client->connect($username, $password); try { $this->client->enterEnable($enablePassword ?: $password); return ['users' => $this->client->command('show users')] + $this->probeFacts(); } finally { $this->client->disconnect(); } } public function syncUser(string $loginUsername, string $loginPassword, ?string $enablePassword, string $username, string $password, string $group, bool $enabled): array { $this->validateAccount($username, $password); $role = self::ROLES[$group] ?? throw new InvalidArgumentException('HISFOCUS_ROLE_INVALID: group RADIQ tidak didukung Hisfocus.'); $this->client->connect($loginUsername, $loginPassword); try { $this->client->enterEnable($enablePassword ?: $loginPassword); $users = $this->client->command('show users'); $exists = $this->userExists($users, $username); $this->client->command('configure terminal'); if (! $enabled) { if ($exists) { $this->client->command('user delete '.$username); $this->save(); } return ['remote_id' => null]; } $this->client->command($exists ? "user change {$username} {$password}" : "user create {$role} {$username} {$password}"); $this->save(); } finally { $this->client->disconnect(); } $result = $this->testLogin($loginUsername, $loginPassword, $enablePassword); if (! $this->userExists($result['users'], $username)) { throw new RuntimeException('COMMAND_NOT_APPLIED: user tidak ditemukan setelah sinkronisasi Hisfocus.'); } return ['remote_id' => $username, 'role' => $role]; } public function deleteUser(string $loginUsername, string $loginPassword, ?string $enablePassword, string $username): void { $this->validateUsername($username); $this->client->connect($loginUsername, $loginPassword); try { $this->client->enterEnable($enablePassword ?: $loginPassword); if (! $this->userExists($this->client->command('show users'), $username)) { return; } $this->client->command('configure terminal'); $this->client->command('user delete '.$username); $this->save(); } finally { $this->client->disconnect(); } } private function probeFacts(): array { foreach (['show system', 'show version'] as $command) { try { return $this->parseFacts($this->client->command($command)); } catch (\Throwable) { } } return []; } private function save(): void { $this->client->command('exit'); $this->client->command('write'); } private function userExists(string $users, string $username): bool { return preg_match('/User\s+\d+\s*:\s*'.preg_quote($username, '/').'\s+/mi', $users) === 1; } private function validateAccount(string $username, string $password): void { $this->validateUsername($username); if ($password === '' || strlen($password) > 64 || preg_match('/\s/', $password)) { throw new InvalidArgumentException('HISFOCUS_PASSWORD_INVALID: password wajib diisi, maksimal 64 karakter, dan tanpa spasi.'); } } private function validateUsername(string $username): void { if (! preg_match('/^[A-Za-z][A-Za-z0-9_]{3,15}$/', $username)) { throw new InvalidArgumentException('HISFOCUS_USERNAME_INVALID: username harus 4-16 karakter, diawali huruf, dan hanya alfanumerik/underscore.'); } } /** @return array */ private function parseFacts(string $output): array { $patterns = [ 'model' => '/(?:product\s*model|device\s*model|\bmodel)\s*[:=]\s*([^\r\n]+)/i', 'serial_number' => '/(?:serial\s*(?:number|no\.?|num)|\bSN)\s*[:=]\s*([^\r\n]+)/i', 'firmware_version' => '/(?:firmware|software|system)(?:\s*version)?\s*[:=]\s*([^\r\n]+)/i', 'hardware_version' => '/hardware(?:\s*version)?\s*[:=]\s*([^\r\n]+)/i', 'mac_address' => '/(?:base\s*)?mac(?:\s*address)?\s*[:=]\s*([0-9a-f:-]{12,17})/i', ]; $facts = []; foreach ($patterns as $key => $pattern) { if (preg_match($pattern, $output, $match)) { $facts[$key] = trim($match[1]); } } return $facts; } }