This commit introduces delivery metadata tracking for gateway commands, updates, and shells. Adds preferred delivery channel handling, refined validation for edge agents, improved relay management logic, and broker presence reporting. Includes schema changes, enhanced shell handling, and test coverage.
591 lines
25 KiB
PHP
591 lines
25 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\edge_gateway_manager;
|
|
use classes\response;
|
|
use objects\logs_o;
|
|
use traits\route_t;
|
|
|
|
class edgeGatewaysRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/edge-gateways', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$departmentId = self::isParametersSet(['department_id']) ? (int)self::getParameter('department_id') : null;
|
|
if ($departmentId !== null && $departmentId > 0) {
|
|
$this->requireDepartmentAccess((int)$departmentId);
|
|
}
|
|
|
|
$response->success((new edge_gateway_manager())->listGateways($departmentId));
|
|
}, [
|
|
'modules_shelly_config' => 'Manage department edge gateways for local Shelly control',
|
|
]);
|
|
|
|
$this->get('/edge-gateways/{id}', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$gateway = (new edge_gateway_manager())->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
$response->success($gateway);
|
|
}, [
|
|
'modules_shelly_config' => 'View department edge gateway detail',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/install-token', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
self::requireParameters(['department_id']);
|
|
$departmentId = (int)self::getParameter('department_id');
|
|
self::requireParameterIntPositive($departmentId, 'department_id');
|
|
$this->requireDepartmentAccess($departmentId);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success(
|
|
(new edge_gateway_manager())->createInstallToken(
|
|
$departmentId,
|
|
self::isParametersSet(['label']) ? (string)self::getParameter('label') : null,
|
|
$user ? (int)$user->id : null
|
|
),
|
|
201
|
|
);
|
|
}, [
|
|
'modules_shelly_config' => 'Create a one-time Raspberry Pi edge gateway installer token',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/discovery', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->queueDiscovery($gatewayId, $user ? (int)$user->id : null));
|
|
}, [
|
|
'modules_shelly_config' => 'Trigger a Shelly LAN discovery job on an edge gateway',
|
|
]);
|
|
|
|
$this->put('/edge-gateways/{id}/bindings', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
self::requireParameters(['bindings']);
|
|
self::requireType(self::getParameter('bindings'), self::TYPE_ARRAY());
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->setRelayBindings(
|
|
$gatewayId,
|
|
(array)self::getParameter('bindings'),
|
|
$user ? (int)$user->id : null
|
|
));
|
|
}, [
|
|
'modules_shelly_config' => 'Approve or override relay bindings for an edge gateway',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/update-jobs', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
self::requireParameters(['target_version']);
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->queueUpdate(
|
|
$gatewayId,
|
|
(string)self::getParameter('target_version'),
|
|
self::isParametersSet(['release_channel']) ? (string)self::getParameter('release_channel') : edge_gateway_manager::DEFAULT_RELEASE_CHANNEL,
|
|
$user ? (int)$user->id : null
|
|
), 201);
|
|
}, [
|
|
'modules_shelly_config' => 'Queue an automatic edge gateway update',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/uninstall', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->queueUninstall($gatewayId, $user ? (int)$user->id : null), 202);
|
|
}, [
|
|
'modules_shelly_config' => 'Queue an edge gateway uninstall on the Raspberry Pi',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/shell-sessions', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
self::requireParameters(['reason']);
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
self::requireMinLength('reason', 5);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->createShellSessionRequest(
|
|
$gatewayId,
|
|
(string)self::getParameter('reason'),
|
|
[
|
|
'cols' => self::isParametersSet(['cols']) ? (int)self::getParameter('cols') : null,
|
|
'rows' => self::isParametersSet(['rows']) ? (int)self::getParameter('rows') : null,
|
|
],
|
|
$user ? (int)$user->id : null
|
|
), 201);
|
|
}, [
|
|
'modules_shelly_config' => 'Approve a break-glass root shell on an edge gateway',
|
|
]);
|
|
|
|
$this->get('/edge-gateways/{id}/shell-sessions/{sessionId}/events', fn() => $this->handleShellSessionEvents(), [
|
|
'modules_shelly_config' => 'Poll root shell events for an edge gateway',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/shell-sessions/{sessionId}/input', fn() => $this->handleShellSessionInput(), [
|
|
'modules_shelly_config' => 'Send terminal input to an edge gateway root shell',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/shell-sessions/{sessionId}/resize', fn() => $this->handleShellSessionResize(), [
|
|
'modules_shelly_config' => 'Resize an edge gateway root shell terminal',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/shell-sessions/{sessionId}/close', fn() => $this->handleShellSessionClose(), [
|
|
'modules_shelly_config' => 'Close an approved edge gateway root shell session',
|
|
]);
|
|
|
|
$this->post('/edge-gateways/{id}/rotate-credentials', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->rotateGatewayCredentials($gatewayId, $user ? (int)$user->id : null));
|
|
}, [
|
|
'modules_shelly_config' => 'Rotate edge gateway agent credentials',
|
|
]);
|
|
|
|
$this->delete('/edge-gateways/{id}', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->deleteGateway($gatewayId, $user ? (int)$user->id : null));
|
|
}, [
|
|
'modules_shelly_config' => 'Delete an edge gateway registration',
|
|
]);
|
|
|
|
$this->post('/departments/{id}/gateway-cutover', function () {
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$departmentId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($departmentId, 'id');
|
|
$this->requireDepartmentAccess($departmentId);
|
|
self::requireParameters(['transport_mode']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success((new edge_gateway_manager())->setDepartmentTransportMode(
|
|
$departmentId,
|
|
(string)self::getParameter('transport_mode'),
|
|
$user ? (int)$user->id : null
|
|
));
|
|
}, [
|
|
'modules_shelly_config' => 'Cut a department over from Shelly cloud to local edge gateways',
|
|
]);
|
|
|
|
$this->get('/edge-agent/install-token/verify', fn() => $this->handleInstallTokenVerify());
|
|
$this->get('/edge-agent/install.sh', fn() => $this->renderInstallScript());
|
|
$this->get('/edge-agent/artifacts/agent.mjs', fn() => $this->renderAgentArtifact('agent.mjs', 'application/javascript; charset=utf-8'));
|
|
$this->get('/edge-agent/artifacts/package.json', fn() => $this->renderAgentArtifact('package.json', 'application/json; charset=utf-8'));
|
|
$this->post('/edge-agent/claim', fn() => $this->handleAgentClaim());
|
|
$this->post('/edge-agent/gateways/{id}/heartbeat', fn() => $this->handleAgentHeartbeat());
|
|
$this->post('/edge-agent/gateways/{id}/commands/poll', fn() => $this->handleAgentCommandPoll());
|
|
$this->post('/edge-agent/gateways/{id}/commands/{jobId}/result', fn() => $this->handleAgentCommandResult());
|
|
$this->post('/edge-agent/gateways/{id}/shell-actions/poll', fn() => $this->handleAgentShellActionPoll());
|
|
$this->post('/edge-agent/gateways/{id}/shell-actions/{actionId}/result', fn() => $this->handleAgentShellActionResult());
|
|
$this->post('/edge-agent/gateways/{id}/shell-sessions/{sessionId}/events', fn() => $this->handleAgentShellEvents());
|
|
$this->post('/edge-agent/internal/gateways/{id}/validate', fn() => $this->handleBrokerGatewayValidate());
|
|
$this->post('/edge-agent/internal/shell-sessions/validate', fn() => $this->handleBrokerShellValidate());
|
|
$this->post('/edge-agent/internal/gateways/{id}/presence', fn() => $this->handleBrokerGatewayPresence());
|
|
$this->post('/edge-agent/internal/shell-sessions/close', fn() => $this->handleBrokerShellSessionClose());
|
|
}
|
|
|
|
private function renderInstallScript(): void
|
|
{
|
|
$manager = new edge_gateway_manager();
|
|
$token = trim((string)$this->fromQuery('token'));
|
|
if ($token === '') {
|
|
http_response_code(400);
|
|
echo 'Missing token';
|
|
exit;
|
|
}
|
|
|
|
header('Content-Type: text/x-shellscript; charset=utf-8');
|
|
echo $manager->buildInstallScript($token);
|
|
exit;
|
|
}
|
|
|
|
private function handleInstallTokenVerify(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$token = trim((string)$this->fromQuery('token'));
|
|
if ($token === '') {
|
|
$response->error('Missing token', 400);
|
|
}
|
|
|
|
$response->success((new edge_gateway_manager())->verifyInstallToken($token));
|
|
}
|
|
|
|
private function renderAgentArtifact(string $fileName, string $contentType): void
|
|
{
|
|
$artifactPath = dirname(WD, 3) . '/services/edge-agent/dist/' . $fileName;
|
|
if (!is_file($artifactPath)) {
|
|
http_response_code(404);
|
|
echo 'Missing edge agent artifact';
|
|
exit;
|
|
}
|
|
|
|
header('Content-Type: ' . $contentType);
|
|
echo file_get_contents($artifactPath);
|
|
exit;
|
|
}
|
|
|
|
private function handleAgentClaim(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
self::requireParameters(['token']);
|
|
$payload = self::getParametersAsArray();
|
|
$response->success((new edge_gateway_manager())->claimGateway(
|
|
(string)$payload['token'],
|
|
trim((string)($payload['hostname'] ?? gethostname() ?: 'unknown-gateway')),
|
|
isset($payload['installed_version']) ? (string)$payload['installed_version'] : null,
|
|
(array)($payload['metadata'] ?? [])
|
|
), 201);
|
|
}
|
|
|
|
private function handleAgentHeartbeat(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
$response->success((new edge_gateway_manager())->recordHeartbeat($gatewayId, $token, $payload));
|
|
}
|
|
|
|
private function handleAgentCommandPoll(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
|
|
$waitSeconds = isset($payload['wait_seconds']) ? (int)$payload['wait_seconds'] : edge_gateway_manager::COMMAND_POLL_TIMEOUT_SECONDS;
|
|
$response->success((new edge_gateway_manager())->pollCommand($gatewayId, $token, $waitSeconds));
|
|
}
|
|
|
|
private function handleAgentCommandResult(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$jobId = (int)$this->fromRoute('jobId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($jobId, 'jobId');
|
|
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
|
|
$response->success((new edge_gateway_manager())->submitCommandResult(
|
|
$gatewayId,
|
|
$jobId,
|
|
$token,
|
|
(bool)($payload['ok'] ?? false),
|
|
isset($payload['payload']) && is_array($payload['payload']) ? (array)$payload['payload'] : [],
|
|
isset($payload['error']) ? (string)$payload['error'] : null
|
|
));
|
|
}
|
|
|
|
private function handleShellSessionEvents(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$sessionId = (int)$this->fromRoute('sessionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($sessionId, 'sessionId');
|
|
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$afterId = max(0, (int)$this->fromQuery('after_id'));
|
|
$waitSeconds = (int)$this->fromQuery('wait_seconds');
|
|
if ($waitSeconds <= 0) {
|
|
$waitSeconds = edge_gateway_manager::SHELL_EVENT_POLL_TIMEOUT_SECONDS;
|
|
}
|
|
|
|
$response->success($manager->pollShellSessionEvents($gatewayId, $sessionId, $afterId, $waitSeconds));
|
|
}
|
|
|
|
private function handleShellSessionInput(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
self::requireParameters(['data']);
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$sessionId = (int)$this->fromRoute('sessionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($sessionId, 'sessionId');
|
|
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->enqueueShellInput(
|
|
$gatewayId,
|
|
$sessionId,
|
|
(string)self::getParameter('data'),
|
|
$user ? (int)$user->id : null
|
|
));
|
|
}
|
|
|
|
private function handleShellSessionResize(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
self::requireParameters(['cols', 'rows']);
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$sessionId = (int)$this->fromRoute('sessionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($sessionId, 'sessionId');
|
|
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->enqueueShellResize(
|
|
$gatewayId,
|
|
$sessionId,
|
|
(int)self::getParameter('cols'),
|
|
(int)self::getParameter('rows'),
|
|
$user ? (int)$user->id : null
|
|
));
|
|
}
|
|
|
|
private function handleShellSessionClose(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requirePermission('modules_shelly_config');
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$sessionId = (int)$this->fromRoute('sessionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($sessionId, 'sessionId');
|
|
|
|
$manager = new edge_gateway_manager();
|
|
$gateway = $manager->getGateway($gatewayId);
|
|
$this->requireDepartmentAccess((int)$gateway['department_id']);
|
|
|
|
$user = (new authentication())->get_user();
|
|
$response->success($manager->requestShellSessionClose(
|
|
$gatewayId,
|
|
$sessionId,
|
|
$user ? (int)$user->id : null
|
|
));
|
|
}
|
|
|
|
private function handleAgentShellActionPoll(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
|
|
$waitSeconds = isset($payload['wait_seconds']) ? (int)$payload['wait_seconds'] : edge_gateway_manager::SHELL_ACTION_POLL_TIMEOUT_SECONDS;
|
|
$response->success((new edge_gateway_manager())->pollShellAction($gatewayId, $token, $waitSeconds));
|
|
}
|
|
|
|
private function handleAgentShellActionResult(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$actionId = (int)$this->fromRoute('actionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($actionId, 'actionId');
|
|
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
|
|
$response->success((new edge_gateway_manager())->submitShellActionResult(
|
|
$gatewayId,
|
|
$actionId,
|
|
$token,
|
|
(bool)($payload['ok'] ?? false),
|
|
isset($payload['error']) ? (string)$payload['error'] : null
|
|
));
|
|
}
|
|
|
|
private function handleAgentShellEvents(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
$sessionId = (int)$this->fromRoute('sessionId');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
self::requireParameterIntPositive($sessionId, 'sessionId');
|
|
|
|
$payload = self::getParametersAsArray();
|
|
$token = trim((string)($payload['agent_token'] ?? $this->fromRequest('agent_token')));
|
|
if ($token === '') {
|
|
$response->error('Missing edge gateway agent token', 401);
|
|
}
|
|
|
|
$events = isset($payload['events']) && is_array($payload['events'])
|
|
? (array)$payload['events']
|
|
: (isset($payload['event']) && is_array($payload['event']) ? [(array)$payload['event']] : []);
|
|
|
|
$response->success((new edge_gateway_manager())->recordShellSessionEvents(
|
|
$gatewayId,
|
|
$sessionId,
|
|
$token,
|
|
$events
|
|
));
|
|
}
|
|
|
|
private function handleBrokerGatewayValidate(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requireBrokerSharedSecret();
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$payload = self::getParametersAsArray();
|
|
self::requireParameters(['token']);
|
|
|
|
$response->success((new edge_gateway_manager())->validateBrokerAgentConnection(
|
|
$gatewayId,
|
|
(string)$payload['token']
|
|
));
|
|
}
|
|
|
|
private function handleBrokerShellValidate(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requireBrokerSharedSecret();
|
|
|
|
$payload = self::getParametersAsArray();
|
|
self::requireParameters(['token']);
|
|
|
|
$response->success((new edge_gateway_manager())->validateShellSessionToken((string)$payload['token']));
|
|
}
|
|
|
|
private function handleBrokerGatewayPresence(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requireBrokerSharedSecret();
|
|
|
|
$gatewayId = (int)$this->fromRoute('id');
|
|
self::requireParameterIntPositive($gatewayId, 'id');
|
|
$payload = self::getParametersAsArray();
|
|
self::requireParameters(['status']);
|
|
|
|
$response->success((new edge_gateway_manager())->recordBrokerPresence(
|
|
$gatewayId,
|
|
(string)$payload['status'],
|
|
isset($payload['connection_id']) ? (string)$payload['connection_id'] : null,
|
|
isset($payload['reason']) ? (string)$payload['reason'] : null,
|
|
isset($payload['metadata']) && is_array($payload['metadata']) ? (array)$payload['metadata'] : []
|
|
));
|
|
}
|
|
|
|
private function handleBrokerShellSessionClose(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$this->requireBrokerSharedSecret();
|
|
|
|
$payload = self::getParametersAsArray();
|
|
self::requireParameters(['token']);
|
|
|
|
$response->success((new edge_gateway_manager())->closeShellSession(
|
|
(string)$payload['token'],
|
|
isset($payload['transcript']) ? (string)$payload['transcript'] : '',
|
|
isset($payload['reason']) ? (string)$payload['reason'] : 'broker_closed'
|
|
));
|
|
}
|
|
|
|
private function requireBrokerSharedSecret(): void
|
|
{
|
|
global /** @var response $response */ $response;
|
|
$expected = trim((string)(getenv('EDGE_BROKER_SHARED_SECRET') ?: ''));
|
|
if ($expected === '') {
|
|
$response->error('Edge broker secret is not configured', 503);
|
|
}
|
|
|
|
$provided = trim((string)($_SERVER['HTTP_X_EDGE_BROKER_SECRET'] ?? $this->fromRequest('broker_secret')));
|
|
if ($provided === '' || !hash_equals($expected, $provided)) {
|
|
$response->error('Forbidden', 403);
|
|
}
|
|
}
|
|
}
|