228 lines
8.8 KiB
PHP
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.');
|
|
}
|
|
}
|
|
}
|