mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-13 07:13:21 -04:00
Filter::canView(), canEdit() and canDelete() ask the user object
$u->canEdit('System'), $u->canView('Events') and so on, but ZM\User had no such
methods. The calls fell through to ZM_Object::__call(), which stored the argument
as a property and returned it, so every check returned a truthy area name. Any
account with Events=View passed as a System editor and could save and run a
filter with AutoExecute, an OS command run by zmfilter.pl. The same fall-through
made the filter ownership and filterdebug checks pass for everyone.
Add canView(), canEdit() and canCreate() to ZM\User. They take the level from the
user's own field and from their role, the way the global functions in auth.php do
for the logged-in user, and refuse anything that is not a permission area.
Monitor::commandFieldChangesNotAllowed() now uses canEdit('System') so a role's
System=Edit counts there too.
In the filter action, require edit on a stored filter before applying the
submitted fields when saving over it, since the fields can name a new owner;
stop after a refused save instead of starting the filter in the background; and
require edit on the filter for start/stop/restart, which had no check.
tests/php/test_user_permissions.php drives the real ZM\User, ZM\User_Role and
ZM\Filter classes. The older filter tests stubbed the user with its own
canView/canEdit, which is why they passed while the real class had neither.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
232 lines
7.1 KiB
PHP
232 lines
7.1 KiB
PHP
<?php
|
|
namespace ZM;
|
|
require_once('database.php');
|
|
require_once('Object.php');
|
|
require_once('Group_Permission.php');
|
|
require_once('Monitor_Permission.php');
|
|
require_once('User_Preference.php');
|
|
|
|
class User extends ZM_Object {
|
|
protected static $setters = array('Monitor_Permissions', 'Preferences', 'Role');
|
|
protected static $table = 'Users';
|
|
|
|
protected $Id;
|
|
protected $Username = '';
|
|
protected $Name = '';
|
|
protected $Email = '';
|
|
protected $Phone = '';
|
|
protected $Password = '';
|
|
protected $Language = '';
|
|
protected $Enabled = 1;
|
|
protected $Stream = 'None';
|
|
protected $Events = 'None';
|
|
protected $Experiments = 'None';
|
|
protected $Control = 'None';
|
|
protected $Monitors = 'None';
|
|
protected $Groups = 'None';
|
|
protected $Devices = 'None';
|
|
protected $Snapshots = 'None';
|
|
protected $System = 'None';
|
|
protected $MaxBandwidth = '';
|
|
protected $TokenMinExpiry = 0;
|
|
protected $APIEnabled = 1;
|
|
protected $HomeView = 'console';
|
|
protected $RoleId = null;
|
|
|
|
|
|
protected $defaults = array(
|
|
'Id' => null,
|
|
'Username' => array('type'=>'text','filter_regexp'=>'/[^\w\.@ ]/', 'default'=>''),
|
|
'Name' => '',
|
|
'Email' => '',
|
|
'Phone' => '',
|
|
'Password' => '',
|
|
'Language' => '',
|
|
'Enabled' => 1,
|
|
'Stream' => 'None',
|
|
'Events' => 'None',
|
|
'Control' => 'None',
|
|
'Monitors' => 'None',
|
|
'Groups' => 'None',
|
|
'Devices' => 'None',
|
|
'Snapshots' => 'None',
|
|
'System' => 'None',
|
|
'MaxBandwidth' => '',
|
|
'TokenMinExpiry' => 0,
|
|
'APIEnabled' => 1,
|
|
'HomeView' => '',
|
|
'RoleId' => null,
|
|
);
|
|
|
|
private $Group_Permissions; # array of GP objects indexed by id
|
|
private $Monitor_Permissions;
|
|
private $Preferences;
|
|
private $Role;
|
|
|
|
public static function find( $parameters = array(), $options = array() ) {
|
|
return ZM_Object::_find(self::class, $parameters, $options);
|
|
}
|
|
|
|
public static function find_one( $parameters = array(), $options = array() ) {
|
|
return ZM_Object::_find_one(self::class, $parameters, $options);
|
|
}
|
|
|
|
public function Name($new=null) {
|
|
if ($new != null) {
|
|
$this->Name = $new;
|
|
}
|
|
if (property_exists($this, 'Name') and !empty($this->Name)) {
|
|
return $this->Name;
|
|
}
|
|
return $this->Username();
|
|
}
|
|
|
|
public static function Indexed_By_Id() {
|
|
$results = array();
|
|
foreach ( ZM_Object::_find('ZM\User', null, array('order'=>'lower(Username)')) as $Object ) {
|
|
$results[$Object->Id()] = $Object;
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
public function Group_Permissions() {
|
|
if (!$this->Group_Permissions) {
|
|
if ($this->Id()) {
|
|
$this->Group_Permissions = array_to_hash_by_key('GroupId', Group_Permission::find(['UserId'=>$this->Id()]));
|
|
} else {
|
|
$this->Group_Permissions = [];
|
|
}
|
|
}
|
|
return array_values($this->Group_Permissions);
|
|
}
|
|
|
|
public function Group_Permission($group_id) {
|
|
if (!$this->Group_Permissions) $this->Group_Permissions();
|
|
|
|
if (!isset($this->Group_Permissions[$group_id])) {
|
|
$gp = $this->Group_Permissions[$group_id] = new Group_Permission();
|
|
$gp->UserId($this->Id());
|
|
$gp->GroupId($this->Id());
|
|
}
|
|
|
|
return $this->Group_Permissions[$group_id];
|
|
}
|
|
|
|
public function Monitor_Permissions($new=-1) {
|
|
if ($new != -1) $this->Monitor_Permissions = $new;
|
|
if (!$this->Monitor_Permissions) {
|
|
if ($this->Id()) {
|
|
$this->Monitor_Permissions = array_to_hash_by_key('MonitorId', Monitor_Permission::find(['UserId'=>$this->Id()]));
|
|
} else {
|
|
$this->Monitor_Permissions = [];
|
|
}
|
|
}
|
|
return array_values($this->Monitor_Permissions);
|
|
}
|
|
|
|
public function Monitor_Permission($monitor_id) {
|
|
if (!$this->Monitor_Permissions) $this->Monitor_Permissions();
|
|
|
|
if (!isset($this->Monitor_Permissions[$monitor_id])) {
|
|
$mp = $this->Monitor_Permissions[$monitor_id] = new Monitor_Permission();
|
|
$mp->UserId($this->Id());
|
|
$mp->MonitorId($monitor_id);
|
|
}
|
|
return $this->Monitor_Permissions[$monitor_id];
|
|
}
|
|
|
|
public function Preferences($new=-1) {
|
|
if ($new != -1) $this->Preferences = $new;
|
|
if (!$this->Preferences) {
|
|
$this->Preferences = array_to_hash_by_key('Name', User_Preference::find(['UserId'=>$this->Id()]));
|
|
}
|
|
return array_values($this->Preferences);
|
|
}
|
|
|
|
public function Preference($name) {
|
|
if (!$this->Preferences) $this->Preferences();
|
|
|
|
if (!isset($this->Preferences[$name])) {
|
|
$up = $this->Preferences[$name] = new User_Preference();
|
|
$up->UserId($this->Id());
|
|
$up->Name($name);
|
|
}
|
|
return $this->Preferences[$name];
|
|
}
|
|
|
|
public function Role($new = null) {
|
|
if ($new !== null) {
|
|
$this->Role = $new;
|
|
$this->RoleId = $new ? $new->Id() : null;
|
|
}
|
|
if (!$this->Role and $this->RoleId) {
|
|
require_once('User_Role.php');
|
|
$this->Role = User_Role::find_one(['Id' => $this->RoleId]);
|
|
}
|
|
return $this->Role;
|
|
}
|
|
|
|
// The permission areas a user (or their role) holds a None/View/Edit/Create level for.
|
|
protected static $permissionAreas = array('Stream', 'Events', 'Control', 'Monitors', 'Groups', 'Devices',
|
|
'Snapshots', 'System');
|
|
|
|
// The levels granted for $area by the user's own field and by their role. Without these
|
|
// methods $u->canEdit('System') fell through to ZM_Object::__call(), which returned the area
|
|
// name, so every user passed as a System editor. refs GHSA-ff93-w6fm-vqxr
|
|
private function permissionLevels($area) {
|
|
if (!in_array($area, self::$permissionAreas, true)) return array();
|
|
$levels = array($this->$area());
|
|
$role = $this->Role();
|
|
if ($role) $levels[] = $role->$area();
|
|
return $levels;
|
|
}
|
|
|
|
public function canView($area) {
|
|
foreach ($this->permissionLevels($area) as $level) {
|
|
if ($level and ($level != 'None')) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function canEdit($area) {
|
|
foreach ($this->permissionLevels($area) as $level) {
|
|
if (($level == 'Edit') or ($level == 'Create')) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function canCreate($area) {
|
|
return in_array('Create', $this->permissionLevels($area), true);
|
|
}
|
|
|
|
public function viewableMonitorIds() {
|
|
if (!property_exists($this, 'viewableMonitorIds')) {
|
|
$this->viewableMonitorIds = [];
|
|
$this->unviewableMonitorIds = [];
|
|
foreach (Monitor::find() as $monitor) {
|
|
if ($monitor->canView($this)) {
|
|
$this->viewableMonitorIds[] = $monitor->Id();
|
|
} else {
|
|
$this->unviewableMonitorIds[] = $monitor->Id();
|
|
}
|
|
}
|
|
}
|
|
return $this->viewableMonitorIds;
|
|
}
|
|
public function unviewableMonitorIds() {
|
|
if (!property_exists($this, 'unviewableMonitorIds')) {
|
|
$this->viewableMonitorIds = [];
|
|
$this->unviewableMonitorIds = [];
|
|
foreach (Monitor::find() as $monitor) {
|
|
if ($monitor->canView($this)) {
|
|
$this->viewableMonitorIds[] = $monitor->Id();
|
|
} else {
|
|
$this->unviewableMonitorIds[] = $monitor->Id();
|
|
}
|
|
}
|
|
}
|
|
return $this->unviewableMonitorIds;
|
|
}
|
|
} # end class User
|
|
?>
|