Initial commit device-management
tests / ci (push) Has been cancelled

This commit is contained in:
Wian Drs
2026-08-25 09:42:00 +07:00
commit 1e80be180e
1159 changed files with 149545 additions and 0 deletions
@@ -0,0 +1,177 @@
<?php
namespace App\Network\Drivers\Hisfocus;
use App\Network\Clients\Hsgq\HsgqCliClient;
use InvalidArgumentException;
use RuntimeException;
class HisfocusOltDriver
{
private const ROLES = ['RADIQ-READ' => 'guest', 'RADIQ-WRITE' => 'operator', 'RADIQ-NOC' => 'administrator'];
public function __construct(private readonly HsgqCliClient $client) {}
/** @return array<string, mixed> */
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<string, string> */
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;
}
}