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,65 @@
<?php
namespace App\Network\Clients\Hisfocus;
use App\Network\Clients\Hsgq\HsgqCliClient;
use phpseclib3\Net\SSH2;
use RuntimeException;
class HisfocusSshClient implements HsgqCliClient
{
private SSH2 $ssh;
public function __construct(private readonly string $host, private readonly int $port, private readonly int $timeout = 10) {}
public function connect(string $username, string $password): void
{
$this->ssh = new SSH2($this->host, $this->port, $this->timeout);
$this->ssh->setTimeout($this->timeout);
if (! $this->ssh->login($username, $password)) {
throw new RuntimeException('AUTHENTICATION_FAILED: login SSH Hisfocus ditolak.');
}
$this->readPrompt();
}
public function enterEnable(string $password): void
{
$this->ssh->write("enable\n");
$output = $this->ssh->read('/(?:Password\s*:|[#>]\s*$)/i', SSH2::READ_REGEX);
if (preg_match('/Password\s*:/i', (string) $output)) {
$this->ssh->write($password."\n");
$output = $this->readPrompt();
}
if (! str_ends_with(trim((string) $output), '#')) {
throw new RuntimeException('ENABLE_FAILED: gagal masuk privileged mode Hisfocus.');
}
}
public function command(string $command): string
{
$this->ssh->write($command."\n");
$output = $this->readPrompt();
if (preg_match('/(?:%\s*)?(?:Error|Invalid|Incomplete|Ambiguous|unknown command|not found)/i', $output)) {
throw new RuntimeException('COMMAND_REJECTED: Hisfocus menolak perintah read-only.');
}
return $output;
}
public function disconnect(): void
{
if (isset($this->ssh)) {
$this->ssh->disconnect();
}
}
private function readPrompt(): string
{
$output = $this->ssh->read('/(?:\([^\r\n]+\))?[#>]\s*$/', SSH2::READ_REGEX);
if ($output === false || $output === '') {
throw new RuntimeException('CONNECTION_TIMEOUT: prompt CLI Hisfocus tidak diterima.');
}
return $output;
}
}
@@ -0,0 +1,112 @@
<?php
namespace App\Network\Clients\Hisfocus;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class HisfocusWebClient
{
public function __construct(private readonly string $host, private readonly int $port = 80, private readonly int $timeout = 10) {}
/** @return array<string, string> */
public function users(string $loginUsername, string $loginPassword): array
{
$match = null;
for ($attempt = 1; $attempt <= 3; $attempt++) {
$body = $this->request($loginUsername, $loginPassword)->get($this->url('/userOverview.asp'))->body();
if (preg_match('/var\s+userList\s*=\s*new\s+Array\((.*?)\);/s', $body, $found)) {
$match = $found;
break;
}
if ($attempt < 3) {
usleep(250_000);
}
}
if (! $match) {
throw new RuntimeException('HISFOCUS_WEB_PARSE_FAILED: daftar user WebGUI tidak dikenali setelah tiga percobaan.');
}
preg_match_all('/"([^"]*)"/', $match[1], $values);
$users = [];
foreach (array_chunk($values[1], 2) as $user) {
if (count($user) === 2) {
$users[$user[0]] = $user[1];
}
}
return $users;
}
public function addUser(string $loginUsername, string $loginPassword, string $username, string $password, string $group): void
{
$response = $this->request($loginUsername, $loginPassword)->asForm()->post($this->url('/goform/setAddUser'), [
'addUserHiddenId' => 0,
'UserName' => $username,
'UserGroup' => $group,
'UserPassword' => $password,
'ComfirmPassword' => $password,
]);
$this->assertAccepted($response->status(), $response->body());
$this->assertUserState($loginUsername, $loginPassword, $username, $group);
}
public function changePassword(string $loginUsername, string $loginPassword, string $username, string $oldPassword, string $newPassword): void
{
$response = $this->request($loginUsername, $loginPassword)->asForm()->post($this->url('/goform/setUserPassword'), [
'HiddenUserName' => $username,
'LYS' => $oldPassword,
'PBT' => $newPassword,
]);
$this->assertAccepted($response->status(), $response->body());
}
public function deleteUser(string $loginUsername, string $loginPassword, string $username): void
{
$response = $this->request($loginUsername, $loginPassword)->asForm()->post($this->url('/goform/setDeleteUser'), [
'user'.$username.'DeleteCheck' => 'on',
]);
$this->assertAccepted($response->status(), $response->body());
$this->assertUserState($loginUsername, $loginPassword, $username, null);
}
public function canLogin(string $username, string $password): bool
{
return $this->request($username, $password)->get($this->url('/'))->successful();
}
private function request(string $username, string $password): PendingRequest
{
return Http::withBasicAuth($username, $password)->timeout($this->timeout)->connectTimeout($this->timeout);
}
private function assertAccepted(int $status, string $body): void
{
if ($status >= 400 || stripos($body, 'Access Denied') !== false) {
throw new RuntimeException('HISFOCUS_WEB_REJECTED: WebGUI menolak operasi user.');
}
}
private function assertUserState(string $loginUsername, string $loginPassword, string $username, ?string $expectedGroup): void
{
for ($attempt = 1; $attempt <= 4; $attempt++) {
$users = $this->users($loginUsername, $loginPassword);
$exists = array_key_exists($username, $users);
if ($expectedGroup === null ? ! $exists : ($exists && strcasecmp($users[$username], $expectedGroup) === 0)) {
return;
}
if ($attempt < 4) {
usleep(250_000);
}
}
throw new RuntimeException($expectedGroup === null
? 'HISFOCUS_WEB_DELETE_FAILED: user masih tercatat di WebGUI.'
: 'HISFOCUS_WEB_ADD_FAILED: user atau role belum tercatat di WebGUI.');
}
private function url(string $path): string
{
return "http://{$this->host}:{$this->port}{$path}";
}
}