Merge pull request #1196 from pliablepixels/api-more-security

Api more security
This commit is contained in:
Isaac Connor 2015-12-20 10:22:40 -05:00
commit 37212dcf2e
8 changed files with 254 additions and 48 deletions

View File

@ -357,7 +357,23 @@ our @options =
type => $types{boolean}, type => $types{boolean},
category => "system", category => "system",
}, },
# PP - Google reCaptcha settings {
name => "ZM_OPT_USE_API",
default => "yes",
description => "Enable ZoneMinder APIs",
help => qqq("
ZoneMinder now features a new API using which 3rd party
applications can interact with ZoneMinder data. It is
STRONGLY recommended that you enable authentication along
with APIs. Note that the APIs return sensitive data like
Monitor access details which are configured as JSON objects.
Which is why we recommend you enabling authentication, especially
if you are exposing your ZM instance on the Internet.
"),
type => $types{boolean},
category => "system",
},
# PP - Google reCaptcha settings
{ {
name => "ZM_OPT_USE_GOOG_RECAPTCHA", name => "ZM_OPT_USE_GOOG_RECAPTCHA",
default => "no", default => "no",
@ -410,6 +426,7 @@ our @options =
type => $types {string}, type => $types {string},
category => "system", category => "system",
}, },
{ {
name => "ZM_DIR_EVENTS", name => "ZM_DIR_EVENTS",

View File

@ -34,7 +34,7 @@ class AppController extends Controller {
use CrudControllerTrait; use CrudControllerTrait;
public $components = [ public $components = [
'Session', // PP - We are going to use SessionHelper to check PHP session vars 'Session', // We are going to use SessionHelper to check PHP session vars
'RequestHandler', 'RequestHandler',
'Crud.Crud' => [ 'Crud.Crud' => [
'actions' => [ 'actions' => [
@ -49,7 +49,7 @@ class AppController extends Controller {
] ]
]; ];
//PP - Global beforeFilter function // Global beforeFilter function
//Zoneminder sets the username session variable //Zoneminder sets the username session variable
// to the logged in user. If this variable is set // to the logged in user. If this variable is set
// then you are logged in // then you are logged in
@ -58,14 +58,62 @@ class AppController extends Controller {
// Also checking to do this only if ZM_OPT_USE_AUTH is on // Also checking to do this only if ZM_OPT_USE_AUTH is on
public function beforeFilter() { public function beforeFilter() {
$this->loadModel('Config'); $this->loadModel('Config');
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API'));
$config = $this->Config->find('first', $options);
$zmOptApi = $config['Config']['Value'];
if ($zmOptApi !='1')
{
throw new UnauthorizedException(__('API Disabled'));
return;
}
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH'));
$config = $this->Config->find('first', $options); $config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value']; $zmOptAuth = $config['Config']['Value'];
if (!$this->Session->Read('user.Username') && ($zmOptAuth=='1')) if (!$this->Session->Read('user.Username') && ($zmOptAuth=='1'))
{ {
throw new NotFoundException(__('Not Authenticated')); throw new UnauthorizedException(__('Not Authenticated'));
return; return;
} }
else
{
$this->loadModel('User');
$loggedinUser = $this->Session->Read('user.Username');
$isEnabled = $this->Session->Read('user.Enabled');
// this will likely never happen as if its
// not enabled, login will fail and Not Auth will be returned
// however, keeping this here for now
if ($isEnabled != "1" && $zmOptAuth=="1")
{
throw new UnauthorizedException(__('User is not enabled'));
return;
}
if ($zmOptAuth=='1')
{
$options = array ('conditions' => array ('User.Username' => $loggedinUser));
$userMonitors = $this->User->find('first', $options);
$this->Session->Write('allowedMonitors',$userMonitors['User']['MonitorIds']);
$this->Session->Write('streamPermission',$userMonitors['User']['Stream']);
$this->Session->Write('eventPermission',$userMonitors['User']['Events']);
$this->Session->Write('controlPermission',$userMonitors['User']['Control']);
$this->Session->Write('systemPermission',$userMonitors['User']['System']);
$this->Session->Write('monitorPermission',$userMonitors['User']['Monitors']);
}
else // if auth is not on, you can do everything
{
//$userMonitors = $this->User->find('first', $options);
$this->Session->Write('allowedMonitors','');
$this->Session->Write('streamPermission','View');
$this->Session->Write('eventPermission','Edit');
$this->Session->Write('controlPermission','Edit');
$this->Session->Write('systemPermission','Edit');
$this->Session->Write('monitorPermission','Edit');
}
}
} }

View File

@ -48,7 +48,7 @@ class ImageComponent extends Component {
$imageFile = $config['ZM_DIR_EVENTS']."/".$imagePath; $imageFile = $config['ZM_DIR_EVENTS']."/".$imagePath;
//$thumbFile = ZM_DIR_EVENTS."/".$thumbPath; //$thumbFile = ZM_DIR_EVENTS."/".$thumbPath;
$thumbFile = $thumbPath; $thumbFile = $thumbPath;
// PP: This segment of code results in errors when trying to get Events API // This segment of code results in errors when trying to get Events API
// This actually seems to be generating images for the angular UI web view // This actually seems to be generating images for the angular UI web view
// and should not be a part of the API anyway // and should not be a part of the API anyway
// I've commented it so events APIs continue to work // I've commented it so events APIs continue to work

View File

@ -15,7 +15,7 @@ class ConfigsController extends AppController {
public $components = array('RequestHandler'); public $components = array('RequestHandler');
/** /**
* PP - resolves the issue of not returning all config parameters * resolves the issue of not returning all config parameters
* refer https://github.com/ZoneMinder/ZoneMinder/issues/953 * refer https://github.com/ZoneMinder/ZoneMinder/issues/953
* index method * index method
* *

View File

@ -14,6 +14,17 @@ class EventsController extends AppController {
*/ */
public $components = array('RequestHandler', 'Scaler', 'Image', 'Paginator'); public $components = array('RequestHandler', 'Scaler', 'Image', 'Paginator');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('eventPermission');
if ($canView =='None')
{
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/** /**
* index method * index method
* *
@ -22,6 +33,18 @@ class EventsController extends AppController {
*/ */
public function index() { public function index() {
$this->Event->recursive = -1; $this->Event->recursive = -1;
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors))
{
$mon_options = array('Event.MonitorId' => $allowedMonitors);
}
else
{
$mon_options='';
}
if ($this->request->params['named']) { if ($this->request->params['named']) {
$this->FilterComponent = $this->Components->load('Filter'); $this->FilterComponent = $this->Components->load('Filter');
@ -39,7 +62,7 @@ class EventsController extends AppController {
$this->Paginator->settings = array( $this->Paginator->settings = array(
// https://github.com/ZoneMinder/ZoneMinder/issues/995 // https://github.com/ZoneMinder/ZoneMinder/issues/995
// 'limit' => $limit['ZM_WEB_EVENTS_PER_PAGE'], // 'limit' => $limit['ZM_WEB_EVENTS_PER_PAGE'],
// PP - 25 events per page which is what the above // 25 events per page which is what the above
// default is, is way too low for an API // default is, is way too low for an API
// changing this to 100 so we don't kill ZM // changing this to 100 so we don't kill ZM
// with many event APIs. In future, we can // with many event APIs. In future, we can
@ -49,14 +72,14 @@ class EventsController extends AppController {
'limit' => '100', 'limit' => '100',
'order' => array('StartTime', 'MaxScore'), 'order' => array('StartTime', 'MaxScore'),
'paramType' => 'querystring', 'paramType' => 'querystring',
'conditions' => $conditions 'conditions' => array (array($conditions, $mon_options))
); );
$events = $this->Paginator->paginate('Event'); $events = $this->Paginator->paginate('Event');
// For each event, get its thumbnail data (path, width, height) // For each event, get its thumbnail data (path, width, height)
foreach ($events as $key => $value) { foreach ($events as $key => $value) {
// PP - $thumbData = $this->createThumbnail($value['Event']['Id']); //$thumbData = $this->createThumbnail($value['Event']['Id']);
$thumbData =""; $thumbData = "";
$events[$key]['thumbData'] = $thumbData; $events[$key]['thumbData'] = $thumbData;
} }
@ -71,41 +94,55 @@ class EventsController extends AppController {
* @param string $id * @param string $id
* @return void * @return void
*/ */
public function view($id = null) { public function view($id = null)
$this->loadModel('Config'); {
$configs = $this->Config->find('list', array( $this->loadModel('Config');
'fields' => array('Name', 'Value'), $configs = $this->Config->find('list', array(
'conditions' => array('Name' => array('ZM_DIR_EVENTS')) 'fields' => array('Name', 'Value'),
)); 'conditions' => array('Name' => array('ZM_DIR_EVENTS'))
));
$this->Event->recursive = 1; $this->Event->recursive = 1;
if (!$this->Event->exists($id)) { if (!$this->Event->exists($id)) {
throw new NotFoundException(__('Invalid event')); throw new NotFoundException(__('Invalid event'));
} }
$options = array('conditions' => array('Event.' . $this->Event->primaryKey => $id));
$event = $this->Event->find('first', $options);
$path = $configs['ZM_DIR_EVENTS'].'/'.$this->Image->getEventPath($event).'/'; $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
$event['Event']['BasePath'] = $path;
# Get the previous and next events for any monitor if (!empty($allowedMonitors))
$this->Event->id = $id; {
$mon_options = array('Event.MonitorId' => $allowedMonitors);
}
else
{
$mon_options='';
}
$options = array('conditions' => array(array('Event.' . $this->Event->primaryKey => $id), $mon_options));
$event = $this->Event->find('first', $options);
$path = $configs['ZM_DIR_EVENTS'].'/'.$this->Image->getEventPath($event).'/';
$event['Event']['BasePath'] = $path;
# Get the previous and next events for any monitor
$this->Event->id = $id;
$event_neighbors = $this->Event->find('neighbors'); $event_neighbors = $this->Event->find('neighbors');
$event['Event']['Next'] = $event_neighbors['next']['Event']['Id']; $event['Event']['Next'] = $event_neighbors['next']['Event']['Id'];
$event['Event']['Prev'] = $event_neighbors['prev']['Event']['Id']; $event['Event']['Prev'] = $event_neighbors['prev']['Event']['Id'];
# Also get the previous and next events for the same monitor # Also get the previous and next events for the same monitor
$event_monitor_neighbors = $this->Event->find('neighbors', array( $event_monitor_neighbors = $this->Event->find('neighbors', array(
'conditions'=>array('Event.MonitorId'=>$event['Event']['MonitorId']) 'conditions'=>array('Event.MonitorId'=>$event['Event']['MonitorId'])
)); ));
$event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id']; $event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id'];
$event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['Event']['Id']; $event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['Event']['Id'];
$this->set(array(
'event' => $event,
'_serialize' => array('event')
));
}
$this->set(array(
'event' => $event,
'_serialize' => array('event')
));
}
/** /**
* add method * add method
@ -113,6 +150,13 @@ class EventsController extends AppController {
* @return void * @return void
*/ */
public function add() { public function add() {
if ($this->Session->Read('eventPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
if ($this->request->is('post')) { if ($this->request->is('post')) {
$this->Event->create(); $this->Event->create();
if ($this->Event->save($this->request->data)) { if ($this->Event->save($this->request->data)) {
@ -131,6 +175,13 @@ class EventsController extends AppController {
* @return void * @return void
*/ */
public function edit($id = null) { public function edit($id = null) {
if ($this->Session->Read('eventPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Event->id = $id; $this->Event->id = $id;
if (!$this->Event->exists($id)) { if (!$this->Event->exists($id)) {
@ -157,15 +208,19 @@ class EventsController extends AppController {
* @return void * @return void
*/ */
public function delete($id = null) { public function delete($id = null) {
if ($this->Session->Read('eventPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Event->id = $id; $this->Event->id = $id;
if (!$this->Event->exists()) { if (!$this->Event->exists()) {
throw new NotFoundException(__('Invalid event')); throw new NotFoundException(__('Invalid event'));
} }
$this->request->allowMethod('post', 'delete'); $this->request->allowMethod('post', 'delete');
if ($this->Event->delete()) { if ($this->Event->delete()) {
// PP - lets make sure the frame table entry is removed too //$this->loadModel('Frame');
$this->loadModel('Frame'); //$this->Event->Frame->delete();
$this->Frame->delete();
return $this->flash(__('The event has been deleted.'), array('action' => 'index')); return $this->flash(__('The event has been deleted.'), array('action' => 'index'));
} else { } else {
return $this->flash(__('The event could not be deleted. Please, try again.'), array('action' => 'index')); return $this->flash(__('The event could not be deleted. Please, try again.'), array('action' => 'index'));

View File

@ -101,7 +101,10 @@ class HostController extends AppController {
function getVersion() { function getVersion() {
$version = Configure::read('ZM_VERSION'); $version = Configure::read('ZM_VERSION');
$apiversion = Configure::read('ZM_API_VERSION'); // not going to use the ZM_API_VERSION
// requires recompilation and dependency on ZM upgrade
//$apiversion = Configure::read('ZM_API_VERSION');
$apiversion = '1.0';
$this->set(array( $this->set(array(
'version' => $version, 'version' => $version,

View File

@ -16,6 +16,19 @@ class MonitorsController extends AppController {
*/ */
public $components = array('Paginator', 'RequestHandler'); public $components = array('Paginator', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('monitorPermission');
if ($canView =='None')
{
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/** /**
* index method * index method
* *
@ -23,7 +36,17 @@ class MonitorsController extends AppController {
*/ */
public function index() { public function index() {
$this->Monitor->recursive = 0; $this->Monitor->recursive = 0;
$monitors = $this->Monitor->find('all'); $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors))
{
$options = array('conditions'=>array('Monitor.Id'=> $allowedMonitors));
}
else
{
$options='';
}
$monitors = $this->Monitor->find('all',$options);
$this->set(array( $this->set(array(
'monitors' => $monitors, 'monitors' => $monitors,
'_serialize' => array('monitors') '_serialize' => array('monitors')
@ -42,7 +65,21 @@ class MonitorsController extends AppController {
if (!$this->Monitor->exists($id)) { if (!$this->Monitor->exists($id)) {
throw new NotFoundException(__('Invalid monitor')); throw new NotFoundException(__('Invalid monitor'));
} }
$options = array('conditions' => array('Monitor.' . $this->Monitor->primaryKey => $id)); $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors))
{
$restricted = array('Monitor.' . $this->Monitor->primaryKey => $allowedMonitors);
}
else
{
$restricted = '';
}
$options = array('conditions' => array(
array('Monitor.' . $this->Monitor->primaryKey => $id),
$restricted
)
);
$monitor = $this->Monitor->find('first', $options); $monitor = $this->Monitor->find('first', $options);
$this->set(array( $this->set(array(
'monitor' => $monitor, 'monitor' => $monitor,
@ -57,6 +94,13 @@ class MonitorsController extends AppController {
*/ */
public function add() { public function add() {
if ($this->request->is('post')) { if ($this->request->is('post')) {
if ($this->Session->Read('systemPermission') != 'Edit')
{
throw new UnauthotizedException(__('Insufficient privileges'));
return;
}
$this->Monitor->create(); $this->Monitor->create();
if ($this->Monitor->save($this->request->data)) { if ($this->Monitor->save($this->request->data)) {
$this->daemonControl($this->Monitor->id, 'start', $this->request->data); $this->daemonControl($this->Monitor->id, 'start', $this->request->data);
@ -78,7 +122,11 @@ class MonitorsController extends AppController {
if (!$this->Monitor->exists($id)) { if (!$this->Monitor->exists($id)) {
throw new NotFoundException(__('Invalid monitor')); throw new NotFoundException(__('Invalid monitor'));
} }
if ($this->Session->Read('systemPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
if ($this->Monitor->save($this->request->data)) { if ($this->Monitor->save($this->request->data)) {
$message = 'Saved'; $message = 'Saved';
} else { } else {
@ -89,9 +137,8 @@ class MonitorsController extends AppController {
'message' => $message, 'message' => $message,
'_serialize' => array('message') '_serialize' => array('message')
)); ));
// PP - restart this monitor after change // - restart this monitor after change
$this->daemonControl($this->Monitor->id, 'restart', $this->request->data); $this->daemonControl($this->Monitor->id, 'restart', $this->request->data);
} }
/** /**
@ -106,6 +153,11 @@ class MonitorsController extends AppController {
if (!$this->Monitor->exists()) { if (!$this->Monitor->exists()) {
throw new NotFoundException(__('Invalid monitor')); throw new NotFoundException(__('Invalid monitor'));
} }
if ($this->Session->Read('systemPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->request->allowMethod('post', 'delete'); $this->request->allowMethod('post', 'delete');
$this->daemonControl($this->Monitor->id, 'stop'); $this->daemonControl($this->Monitor->id, 'stop');

View File

@ -0,0 +1,31 @@
<?php
App::uses('AppModel', 'Model');
/**
* User Model
*
*/
class User extends AppModel {
/**
* Use table
*
* @var mixed False or table name
*/
public $useTable = 'Users';
/**
* Primary key field
*
* @var string
*/
public $primaryKey = 'Id';
/**
* Display field
*
* @var string
*/
public $displayField = 'Name';
}