Files
zoneminder/tests/php/test_remote_control_url.php
T
Nic BoetandClaude Opus 5.5 99cf213f62 fix: forward control requests to the server that records the monitor
In a multi-server setup a PTZ move, preset or light command sent to one
server for a monitor that another server records never reached the
camera. Monitor::sendControlCommand() passed the zmcontrol option string
to the other server's monitors/daemonControl API, which only starts and
stops zmcontrol.pl, so the request built a URL like
daemonControl/4/ --panspeed=51 ... --command=moveConLeft/zmcontrol.pl.json
and answered "Failed". The web UI's own PTZ buttons send to the
monitor's server and were not affected; clients that send every request
to the server they log in to (zm-py, pyzmNg, zmNinjaNg, aiozoneminder)
and monitor actions whose target is on another server were.

ajax/control.php now hands such a request, as received, to the index.php
of the server that records the monitor (or the action's target), with
the auth relay in place of the caller's credentials, and answers with
that server's reply. That server checks the permissions and builds the
command itself. A request is forwarded at most once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:24:29 -05:00

94 lines
3.9 KiB
PHP

<?php
// Tests remoteControlUrl() in web/includes/control_functions.php: in a
// multi-server setup a PTZ or other control request for a monitor that another
// server records is handed to that server, which runs its zmcontrol.pl.
//
// Before, the server that got the request sent the zmcontrol option string to
// the other server's monitors/daemonControl API, which only starts and stops
// zmcontrol.pl, so the camera never moved and the request answered "Failed".
//
// Run: php tests/php/test_remote_control_url.php
define('ZM_SERVER_ID', 1);
$stubdir = sys_get_temp_dir().'/zm_remote_control_stubs_'.getmypid();
@mkdir($stubdir, 0700, true);
file_put_contents($stubdir.'/logger.php', "<?php\nnamespace ZM;\nfunction Warning(\$s) {}\nfunction Error(\$s) {}\nfunction Debug(\$s) {}\n");
set_include_path($stubdir.PATH_SEPARATOR.get_include_path());
require_once __DIR__.'/../../web/includes/control_functions.php';
function get_auth_relay() { return 'auth=0123456789abcdef&user=admin'; }
class StubServer {
private $id;
public function __construct($id) { $this->id = $id; }
public function Id() { return $this->id; }
public function UrlToIndex() { return 'http://zm2.example:8080/zm/index.php'; }
}
class StubMonitor {
private $serverId;
private $serverKnown;
public function __construct($serverId, $serverKnown = true) {
$this->serverId = $serverId;
$this->serverKnown = $serverKnown;
}
public function ServerId() { return $this->serverId; }
public function Server() { return new StubServer($this->serverKnown ? $this->serverId : null); }
}
$failures = 0;
$passes = 0;
function check($name, $got, $want) {
global $failures, $passes;
if ($got === $want) {
$passes++;
echo "ok - $name\n";
} else {
$failures++;
echo "FAIL - $name (got ".var_export($got, true).", want ".var_export($want, true).")\n";
}
}
$request = array(
'view' => 'request', 'request' => 'control', 'id' => '4', 'control' => 'moveConLeft',
'xge' => '50', 'yge' => '50', 'token' => 'jwt', 'username' => 'admin', 'password' => 'secret',
);
check('monitor on this server is handled here', remoteControlUrl(new StubMonitor(1), $request), null);
check('monitor with no server is handled here', remoteControlUrl(new StubMonitor(0), $request), null);
check('monitor on a server ZoneMinder does not list is handled here',
remoteControlUrl(new StubMonitor(2, false), $request), null);
check('a request forwarded once is handled where it lands',
remoteControlUrl(new StubMonitor(2), $request + array('forwarded' => '1')), null);
$url = remoteControlUrl(new StubMonitor(2), $request);
check('monitor on another server goes to that server',
strtok($url, '?'), 'http://zm2.example:8080/zm/index.php');
parse_str(parse_url($url, PHP_URL_QUERY), $sent);
check('the control request is passed on', array(
isset($sent['view']) ? $sent['view'] : null,
isset($sent['request']) ? $sent['request'] : null,
isset($sent['id']) ? $sent['id'] : null,
isset($sent['control']) ? $sent['control'] : null,
isset($sent['xge']) ? $sent['xge'] : null,
isset($sent['forwarded']) ? $sent['forwarded'] : null,
), array('request', 'control', '4', 'moveConLeft', '50', '1'));
check('the caller\'s credentials are not passed on', array(
isset($sent['token']), isset($sent['password'])), array(false, false));
check('the auth relay says who is asking', array(
isset($sent['auth']) ? $sent['auth'] : null, isset($sent['user']) ? $sent['user'] : null),
array('0123456789abcdef', 'admin'));
$action = array('view' => 'request', 'request' => 'control', 'action' => 'monitorAction', 'id' => '3', 'aid' => '7');
parse_str(parse_url(remoteControlUrl(new StubMonitor(2), $action), PHP_URL_QUERY), $sent);
check('a monitor action keeps its action and ids', array($sent['action'], $sent['id'], $sent['aid']),
array('monitorAction', '3', '7'));
array_map('unlink', glob($stubdir.'/*'));
@rmdir($stubdir);
echo "\n$passes passed, $failures failed\n";
exit($failures ? 1 : 0);