mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-13 05:38:05 -04:00
Neither checked anything, so any enabled, API-enabled account could read them.
Control definitions describe how to drive a camera and are edited under Options,
which requires System. Manufacturers, CameraModels and EncoderTemplates already
gate their reference data that way; Controls was left open.
getLoad returns the system load average, which getSysLoadHTML() in the web ui
declines to render without canView('System'), so the API no longer hands the
same figure to an account without it.
getDiskPercent reported usage per monitor, keyed by monitor name, telling an
account which monitors exist and how much disk each uses even when it was not
permitted to view any of them. It now reports only on monitors the caller can
view, and asking about one specific monitor requires view on that monitor.
daemonCheck is deliberately left alone: it reports only whether ZoneMinder is
running, which the web ui's navbar shows to every logged-in user, and gating it
here would make the API stricter than the interface it serves.
327 lines
11 KiB
PHP
327 lines
11 KiB
PHP
<?php
|
|
App::uses('AppController', 'Controller');
|
|
|
|
class HostController extends AppController {
|
|
|
|
public $components = array('RequestHandler');
|
|
|
|
public function daemonCheck($daemon=false, $args=false) {
|
|
# To try to prevent abuse here, we are only going to allow certain characters in the daemon and args.
|
|
$count = 0;
|
|
$safe_daemon = $daemon ? preg_replace('/[^A-Za-z0-9\- \.]/', '', $daemon, -1, $count) : false;
|
|
if ($count) Error("Invalid characters found in daemon string ($daemon). Potential attack?");
|
|
$safe_args = $args ? preg_replace('/[^A-Za-z0-9\- \.]/', '', $args, -1, $count) : false;
|
|
if ($count) Error("Invalid characters found in args string ($args). Potential attack?");
|
|
|
|
$string = ZM_PATH_BIN.'/zmdc.pl check';
|
|
if ($safe_daemon) {
|
|
$string .= ' '.$safe_daemon;
|
|
if ($safe_args) $string .= ' '.$safe_args;
|
|
}
|
|
$result = exec($string);
|
|
$result = preg_match('/running/', $result);
|
|
|
|
$this->set(array(
|
|
'result' => $result,
|
|
'_serialize' => array('result')
|
|
));
|
|
}
|
|
|
|
// an interface to individually control the various ZM daemons
|
|
// invocation: https://server/zm/api/host/daemonControl/<daemon>.pl/<command>.json
|
|
// note that this API is only for interaction with a specific
|
|
// daemon. zmdc also allows other functions like logrot/etc
|
|
public function daemonControl($daemon, $command) {
|
|
global $user;
|
|
if ($command == 'check' || $command == 'status') {
|
|
$permission = 'View';
|
|
} else {
|
|
$permission = 'Edit';
|
|
}
|
|
$allowed = (!$user) || ($user->System() == $permission );
|
|
if ( !$allowed ) {
|
|
throw new UnauthorizedException(__("Insufficient privileges"));
|
|
return;
|
|
}
|
|
# To try to prevent abuse here, we are only going to allow certain characters in the daemon and args.
|
|
$safe_daemon = preg_replace('/[^A-Za-z0-9\-\.]/', '', $daemon, -1, $count);
|
|
if ($count) Error("Invalid characters found in daemon string ($daemon). Potential attack?");
|
|
$safe_command = preg_replace('/[^a-z]/', '', $command, -1, $count);
|
|
if ($count) Error("Invalid characters found in command string ($command). Potential attack?");
|
|
$string = ZM_PATH_BIN."/zmdc.pl $safe_command $safe_daemon";
|
|
$result = exec($string);
|
|
$this->set(array(
|
|
'result' => $result,
|
|
'_serialize' => array('result')
|
|
));
|
|
}
|
|
|
|
function getLoad() {
|
|
# getSysLoadHTML() in the web ui renders nothing without canView('System'),
|
|
# so the same figure is not handed out here to an account without it.
|
|
global $user;
|
|
if ($user and ($user->System() == 'None')) {
|
|
throw new UnauthorizedException(__('Insufficient Privileges'));
|
|
}
|
|
$load = sys_getloadavg();
|
|
|
|
$this->set(array(
|
|
'load' => $load,
|
|
'_serialize' => array('load')
|
|
));
|
|
}
|
|
|
|
function login() {
|
|
$ver = $this->_getVersion();
|
|
|
|
$login_array = [];
|
|
$login_array['version'] = $ver[0];
|
|
$login_array['apiversion'] = $ver[1];
|
|
|
|
if (!ZM_OPT_USE_AUTH) {
|
|
$login_array['_serialize'] = array_keys($login_array);
|
|
$this->set($login_array);
|
|
return;
|
|
}
|
|
|
|
$username = $this->request->query('user') ? $this->request->query('user') : $this->request->data('user');
|
|
if ( !$username )
|
|
$username = $this->request->query('username') ? $this->request->query('username') : $this->request->data('username');
|
|
$password = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');
|
|
if ( !$password )
|
|
$password = $this->request->query('password') ? $this->request->query('password') : $this->request->data('password');
|
|
|
|
$token = $this->request->query('token') ? $this->request->query('token') : $this->request->data('token');
|
|
|
|
if ( !($username && $password) && !$token ) {
|
|
throw new UnauthorizedException(__('No identity provided'));
|
|
}
|
|
|
|
$cred = [];
|
|
|
|
if ( $username && $password ) {
|
|
ZM\Debug('Username and password provided, generating access and refresh tokens');
|
|
$cred = $this->_getCredentials(true, '', $username); // generate refresh
|
|
} else {
|
|
ZM\Debug('Only generating access token');
|
|
$cred = $this->_getCredentials(false, $token); // don't generate refresh
|
|
}
|
|
if ($cred) {
|
|
$login_array['access_token'] = $cred[0];
|
|
$login_array['access_token_expires'] = $cred[1];
|
|
|
|
if ( $username && $password ) {
|
|
$login_array['refresh_token'] = $cred[2];
|
|
$login_array['refresh_token_expires'] = $cred[3];
|
|
}
|
|
}
|
|
|
|
if ( ZM_OPT_USE_LEGACY_API_AUTH ) {
|
|
$cred_depr = $this->_getCredentialsDeprecated();
|
|
if ($cred_depr) {
|
|
$login_array['credentials'] = $cred_depr[0];
|
|
$login_array['append_password'] = $cred_depr[1];
|
|
}
|
|
} else {
|
|
ZM\Debug('Legacy Auth is disabled, not generating auth= credentials');
|
|
}
|
|
|
|
$login_array['_serialize'] = array_keys($login_array);
|
|
$this->set($login_array);
|
|
} // end function login()
|
|
|
|
// clears out session
|
|
function logout() {
|
|
userLogout();
|
|
|
|
$this->set(array(
|
|
'result' => 'ok',
|
|
'_serialize' => array('result')
|
|
));
|
|
|
|
} // end function logout()
|
|
|
|
private function _getCredentialsDeprecated() {
|
|
$credentials = '';
|
|
$appendPassword = 0;
|
|
if ( ZM_OPT_USE_AUTH ) {
|
|
require_once __DIR__ .'/../../../includes/auth.php';
|
|
if ( ZM_AUTH_RELAY == 'hashed' ) {
|
|
$credentials = 'auth='.generateAuthHash(ZM_AUTH_HASH_IPS, true);
|
|
} else {
|
|
$credentials = 'user='.$_SESSION['Username'].'&pass=';
|
|
$appendPassword = 1;
|
|
}
|
|
}
|
|
return array($credentials, $appendPassword);
|
|
}
|
|
|
|
private function _getCredentials($generate_refresh_token=false, $token='', $username='') {
|
|
if (!ZM_OPT_USE_AUTH) {
|
|
ZM\Debug('OPT_USE_AUTH is turned off. Tokens will be null');
|
|
return;
|
|
}
|
|
|
|
if (!ZM_AUTH_HASH_SECRET)
|
|
throw new ForbiddenException(__('Please create a valid AUTH_HASH_SECRET in ZoneMinder'));
|
|
|
|
require_once __DIR__ .'/../../../includes/auth.php';
|
|
require_once __DIR__.'/../../../vendor/autoload.php';
|
|
|
|
if ($token) {
|
|
// If we have a token, we need to derive username from there
|
|
$ret = validateToken($token, 'refresh', true);
|
|
$username = $ret[0]->Username();
|
|
}
|
|
|
|
ZM\Debug("Creating token for \"$username\"");
|
|
|
|
/* we won't support AUTH_HASH_IPS in token mode
|
|
reasons:
|
|
a) counter-intuitive for mobile consumers
|
|
b) zmu will never be able to to validate via a token if we sign
|
|
it after appending REMOTE_ADDR
|
|
|
|
if (ZM_AUTH_HASH_IPS) {
|
|
$key = $key . $_SERVER['REMOTE_ADDR'];
|
|
}*/
|
|
|
|
$access_issued_at = time();
|
|
$access_ttl = max(ZM_AUTH_HASH_TTL, 1) * 3600;
|
|
|
|
// by default access token will expire in 2 hrs
|
|
// you can change it by changing the value of ZM_AUTH_HASH_TTL
|
|
$access_expire_at = $access_issued_at + $access_ttl;
|
|
|
|
$access_token = array(
|
|
'iss' => 'ZoneMinder',
|
|
'iat' => $access_issued_at,
|
|
'exp' => $access_expire_at,
|
|
'user' => $username,
|
|
'type' => 'access'
|
|
);
|
|
|
|
$jwt_access_token = \Firebase\JWT\JWT::encode($access_token, ZM_AUTH_HASH_SECRET, 'HS256');
|
|
|
|
$jwt_refresh_token = '';
|
|
$refresh_ttl = 0;
|
|
|
|
if ($generate_refresh_token) {
|
|
$refresh_issued_at = time();
|
|
$refresh_ttl = 24 * 3600; // 1 day
|
|
|
|
$refresh_expire_at = $refresh_issued_at + $refresh_ttl;
|
|
$refresh_token = array(
|
|
'iss' => 'ZoneMinder',
|
|
'iat' => $refresh_issued_at,
|
|
'exp' => $refresh_expire_at,
|
|
'user' => $username,
|
|
'type' => 'refresh'
|
|
);
|
|
$jwt_refresh_token = \Firebase\JWT\JWT::encode($refresh_token, ZM_AUTH_HASH_SECRET, 'HS256');
|
|
} # end if generate_refresh_token
|
|
return array($jwt_access_token, $access_ttl, $jwt_refresh_token, $refresh_ttl);
|
|
} # end function _getCredentials($generate_refresh_token=false, $token='')
|
|
|
|
// If $mid is set, only return disk usage for that monitor
|
|
// Else, return an array of total disk usage, and per-monitor
|
|
// usage.
|
|
// This function is deprecated. Use the Storage object or monitor object instead
|
|
function getDiskPercent($mid = null) {
|
|
$this->loadModel('Monitor');
|
|
|
|
// If $mid is passed, see if it is valid
|
|
if ( $mid and !$this->Monitor->exists($mid) ) {
|
|
throw new NotFoundException(__('Invalid monitor'));
|
|
}
|
|
require_once __DIR__ .'/../../../includes/Monitor.php';
|
|
# Usage is reported per monitor, keyed by monitor name, so an account that
|
|
# cannot view a monitor was being told that it exists and how much disk it
|
|
# uses. Report only on the monitors it may see.
|
|
if ($mid) {
|
|
$one = $this->Monitor->find('first', array('conditions' => array('Id' => $mid)));
|
|
$zm_monitor = new ZM\Monitor($one['Monitor']);
|
|
if (!$zm_monitor->canView()) {
|
|
throw new UnauthorizedException(__('Insufficient Privileges'));
|
|
}
|
|
}
|
|
|
|
$zm_dir_events = ZM_DIR_EVENTS;
|
|
|
|
// Test to see if $zm_dir_events is relative or absolute
|
|
#if ('/' === "" || strrpos($zm_dir_events, '/', -strlen($zm_dir_events)) !== TRUE) {
|
|
if ( substr($zm_dir_events, 0, 1) != '/' ) {
|
|
// relative - so add the full path
|
|
$zm_dir_events = ZM_PATH_WEB . '/' . $zm_dir_events;
|
|
}
|
|
|
|
if ( $mid ) {
|
|
// Get disk usage for $mid
|
|
ZM\Debug("Executing du -s0 $zm_dir_events/$mid | awk '{print \$1}'");
|
|
$usage = shell_exec("du -s0 ".escapeshellarg($zm_dir_events.'/'.$mid)." | awk '{print \$1}'");
|
|
} else {
|
|
$monitors = $this->Monitor->find('all', array(
|
|
'fields' => array('Id', 'Name', 'WebColour')
|
|
));
|
|
$usage = array();
|
|
|
|
// Add each monitor's usage to array
|
|
foreach ($monitors as $key => $value) {
|
|
$zm_monitor = new ZM\Monitor($value['Monitor']);
|
|
if (!$zm_monitor->canView()) continue;
|
|
$id = $value['Monitor']['Id'];
|
|
$name = $value['Monitor']['Name'];
|
|
$color = $value['Monitor']['WebColour'];
|
|
|
|
$space = shell_exec("du -s0 $zm_dir_events/$id | awk '{print $1}'");
|
|
if ( $space == null ) {
|
|
$space = 0;
|
|
}
|
|
$space = $space/1024/1024;
|
|
|
|
$usage[$name] = array(
|
|
'space' => rtrim($space),
|
|
'color' => $color
|
|
);
|
|
}
|
|
|
|
// Add total usage to array
|
|
$space = shell_exec( "df $zm_dir_events |tail -n1 | awk '{print $3 }'");
|
|
$space = $space/1024/1024;
|
|
$usage['Total'] = array(
|
|
'space' => rtrim($space),
|
|
'color' => '#F7464A'
|
|
);
|
|
}
|
|
|
|
$this->set(array(
|
|
'usage' => $usage,
|
|
'_serialize' => array('usage')
|
|
));
|
|
}
|
|
|
|
function getTimeZone() {
|
|
//http://php.net/manual/en/function.date-default-timezone-get.php
|
|
$tz = date_default_timezone_get();
|
|
$this->set(array(
|
|
'tz' => $tz,
|
|
'_serialize' => array('tz')
|
|
));
|
|
}
|
|
|
|
private function _getVersion() {
|
|
$version = ZM_VERSION;
|
|
$apiversion = '2.0';
|
|
return array($version, $apiversion);
|
|
}
|
|
|
|
function getVersion() {
|
|
$val = $this->_getVersion();
|
|
$this->set(array(
|
|
'version' => $val[0],
|
|
'apiversion' => $val[1],
|
|
'_serialize' => array('version', 'apiversion')
|
|
));
|
|
}
|
|
}
|