Files
device-management/app/Network/Drivers/Hsgq/HsgqOltDriver.php
T
Wian Drs 1e80be180e
tests / ci (push) Has been cancelled
Initial commit device-management
2026-08-25 09:42:00 +07:00

228 lines
8.8 KiB
PHP

<?php
namespace App\Network\Drivers\Hsgq;
use App\Network\Clients\Hsgq\HsgqCliClient;
use App\Network\Clients\Hsgq\HsgqWebClient;
use Closure;
use InvalidArgumentException;
class HsgqOltDriver
{
private const ROLES = ['RADIQ-READ' => 'user', 'RADIQ-WRITE' => 'admin', 'RADIQ-NOC' => 'admin'];
public function __construct(
private HsgqCliClient $client,
private readonly ?HsgqCliClient $writeFallback = null,
private readonly ?HsgqWebClient $webFallback = null,
) {}
public function testLogin(string $username, string $password, ?string $enablePassword = null): array
{
$this->client->connect($username, $password);
try {
$this->prepare($enablePassword ?: $password);
return ['users' => $this->client->command('show user')];
} finally {
$this->client->disconnect();
}
}
public function rotateRootPassword(string $currentPassword, string $newPassword, ?string $enablePassword = null): void
{
$this->validatePassword($newPassword);
try {
$this->withWriteFallback(function () use ($currentPassword, $newPassword, $enablePassword): void {
$this->client->connect('root', $currentPassword);
try {
$this->prepare($enablePassword ?: $currentPassword);
try {
$this->client->command('user password-self '.$newPassword);
} catch (\RuntimeException $exception) {
if (! str_starts_with($exception->getMessage(), 'COMMAND_REJECTED')) {
throw $exception;
}
$this->client->command('user password '.$newPassword);
}
$this->client->command('user save');
} finally {
$this->client->disconnect();
}
});
} catch (\Throwable $cliException) {
if (! $this->webFallback) {
throw $cliException;
}
$this->webFallback->login('root', $currentPassword);
$this->webFallback->changePassword('root', $newPassword, $currentPassword);
}
$this->testLogin('root', $newPassword, $enablePassword === $currentPassword ? $newPassword : $enablePassword);
}
public function syncUser(string $rootPassword, ?string $enablePassword, string $username, string $password, string $group, bool $enabled): array
{
$this->validateAccount($username, $password);
$role = self::ROLES[$group] ?? throw new InvalidArgumentException('HSGQ_ROLE_INVALID: group RADIQ tidak didukung HSGQ.');
try {
return $this->withWriteFallback(function () use ($rootPassword, $enablePassword, $username, $password, $role, $enabled): array {
$this->client->connect('root', $rootPassword);
try {
$this->prepare($enablePassword ?: $rootPassword);
$users = $this->client->command('show user');
$exists = $this->userExists($users, $username);
if (! $enabled) {
if ($exists) {
$this->removeUser($username);
}
return ['remote_id' => null];
}
$exists
? $this->client->command("user password {$username} {$password}")
: $this->client->command("user add {$username} {$password} {$role} reenter 4");
$this->client->command('user save');
if (! $this->userExists($this->client->command('show user'), $username)) {
throw new \RuntimeException('COMMAND_NOT_APPLIED: user tidak ditemukan setelah sinkronisasi HSGQ.');
}
return ['remote_id' => $username, 'role' => $role];
} finally {
$this->client->disconnect();
}
});
} catch (\Throwable $cliException) {
return $this->syncUserViaWeb($rootPassword, $username, $password, $group, $enabled, $cliException);
}
}
public function deleteUser(string $rootPassword, ?string $enablePassword, string $username): void
{
$this->validateUsername($username);
try {
$this->withWriteFallback(function () use ($rootPassword, $enablePassword, $username): void {
$this->client->connect('root', $rootPassword);
try {
$this->prepare($enablePassword ?: $rootPassword);
if ($this->userExists($this->client->command('show user'), $username)) {
$this->removeUser($username);
}
} finally {
$this->client->disconnect();
}
});
} catch (\Throwable $cliException) {
if (! $this->webFallback) {
throw $cliException;
}
$this->webFallback->login('root', $rootPassword);
if ($this->webFallback->hasUser($username)) {
$this->webFallback->deleteUser($username);
}
}
}
private function removeUser(string $username): void
{
try {
$this->client->command('user offline '.$username);
} catch (\RuntimeException $exception) {
if (! str_contains($exception->getMessage(), 'This account can not be process')) {
throw $exception;
}
}
$this->client->command('user delete '.$username);
$this->client->command('user save');
}
private function userExists(string $users, string $username): bool
{
return preg_match('/^\s*'.preg_quote($username, '/').'\s+/mi', $users) === 1;
}
private function withWriteFallback(Closure $operation): mixed
{
$primary = $this->client;
try {
return $operation();
} catch (\Throwable $primaryException) {
if (! $this->writeFallback) {
throw $primaryException;
}
$this->client = $this->writeFallback;
try {
return $operation();
} catch (\Throwable $fallbackException) {
throw new \RuntimeException('HSGQ_DUAL_CONNECTION_FAILED: SSH gagal menerapkan perubahan dan Telnet gagal: '.str($fallbackException->getMessage())->after(':')->trim()->limit(140), 0, $fallbackException);
} finally {
$this->client = $primary;
}
}
}
private function syncUserViaWeb(string $rootPassword, string $username, string $password, string $group, bool $enabled, \Throwable $cliException): array
{
if (! $this->webFallback) {
throw $cliException;
}
$level = match ($group) {
'RADIQ-READ' => 5,
'RADIQ-WRITE', 'RADIQ-NOC' => 3,
default => throw new InvalidArgumentException('HSGQ_ROLE_INVALID: group RADIQ tidak didukung WebGUI HSGQ.'),
};
$this->webFallback->login('root', $rootPassword);
$exists = $this->webFallback->hasUser($username);
if (! $enabled) {
if ($exists) {
$this->webFallback->deleteUser($username);
}
return ['remote_id' => null];
}
if ($exists) {
$this->webFallback->changePassword($username, $password);
} else {
$this->webFallback->addUser($username, $password, $level);
}
if (! $this->webFallback->hasUser($username)) {
throw new \RuntimeException('COMMAND_NOT_APPLIED: user tidak ditemukan setelah sinkronisasi WebGUI HSGQ.');
}
return ['remote_id' => $username, 'role' => $level, 'transport' => 'webgui'];
}
private function validateAccount(string $username, string $password): void
{
$this->validateUsername($username);
$this->validatePassword($password);
}
private function prepare(string $enablePassword): void
{
$this->client->enterEnable($enablePassword);
$this->client->command('terminal length 0');
$this->client->command('configure');
}
private function validateUsername(string $username): void
{
if ($username === 'root' || ! preg_match('/^[A-Za-z0-9_]{4,16}$/', $username)) {
throw new InvalidArgumentException('HSGQ_USERNAME_INVALID: username HSGQ harus 4-16 karakter atau merupakan akun yang dilindungi.');
}
}
private function validatePassword(string $password): void
{
if ($password === '' || strlen($password) > 64 || preg_match('/\s/', $password)) {
throw new InvalidArgumentException('HSGQ_PASSWORD_INVALID: password HSGQ maksimal 64 karakter tanpa spasi.');
}
}
}