From 02a3652c86c7d18bbd8e9fba4dd40b21c2936f61 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 6 Mar 2017 21:46:22 +0100 Subject: [PATCH] Restrictriction: add simple hostgroup restrictions refs #832 --- application/controllers/HostController.php | 23 ++++++ application/controllers/HostsController.php | 12 +++ application/forms/IcingaHostForm.php | 18 +++++ application/tables/IcingaHostTable.php | 17 +++- configuration.php | 8 ++ library/Director/Dashboard/Dashboard.php | 34 +++++++- .../Restriction/BetaHostgroupRestriction.php | 80 +++++++++++++++++++ .../Restriction/ObjectRestriction.php | 45 +++++++++++ .../Web/Controller/ObjectController.php | 16 ++++ .../Web/Controller/ObjectsController.php | 6 ++ .../Director/Web/Form/DirectorObjectForm.php | 36 +++++++++ 11 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 library/Director/Restriction/BetaHostgroupRestriction.php create mode 100644 library/Director/Restriction/ObjectRestriction.php diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 0b318552..604bf994 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -8,8 +8,10 @@ use Icinga\Module\Director\Db\AppliedServiceSetLoader; use Icinga\Module\Director\Exception\NestingError; use Icinga\Module\Director\IcingaConfig\AgentWizard; use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaService; use Icinga\Module\Director\Objects\IcingaServiceSet; +use Icinga\Module\Director\Restriction\BetaHostgroupRestriction; use Icinga\Module\Director\Util; use Icinga\Module\Director\Web\Controller\ObjectController; use Icinga\Web\Url; @@ -47,6 +49,27 @@ class HostController extends ObjectController $this->assertPermission('director/hosts'); } + protected function loadRestrictions() + { + return array( + $this->getHostgroupRestriction() + ); + } + + protected function getHostgroupRestriction() + { + return new BetaHostgroupRestriction($this->db(), $this->Auth()); + } + + /** + * @param IcingaHost $object + * @return bool + */ + protected function allowsObject(IcingaObject $object) + { + return $this->getHostgroupRestriction()->allowsHost($object); + } + public function editAction() { parent::editAction(); diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php index 3984ab2c..7696efdc 100644 --- a/application/controllers/HostsController.php +++ b/application/controllers/HostsController.php @@ -6,6 +6,8 @@ use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\FilterChain; use Icinga\Data\Filter\FilterExpression; use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Restriction\BetaHostgroupRestriction; +use Icinga\Module\Director\Tables\IcingaHostTable; use Icinga\Module\Director\Web\Controller\ObjectsController; class HostsController extends ObjectsController @@ -76,4 +78,14 @@ class HostsController extends ObjectsController $this->setViewScript('objects/form'); } + + /** + * @param IcingaHostTable $table + */ + protected function applyTableFilters($table) + { + $table->addObjectRestriction( + new BetaHostgroupRestriction($this->db(), $this->Auth()) + ); + } } diff --git a/application/forms/IcingaHostForm.php b/application/forms/IcingaHostForm.php index 64b427a7..5340b43e 100644 --- a/application/forms/IcingaHostForm.php +++ b/application/forms/IcingaHostForm.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Forms; +use Icinga\Module\Director\Restriction\BetaHostgroupRestriction; use Icinga\Module\Director\Web\Form\DirectorObjectForm; class IcingaHostForm extends DirectorObjectForm @@ -132,6 +133,10 @@ class IcingaHostForm extends DirectorObjectForm */ protected function addGroupsElement() { + if ($this->hasHostGroupRestriction()) { + return $this; + } + $groups = $this->enumHostgroups(); if (empty($groups)) { return $this; @@ -153,6 +158,19 @@ class IcingaHostForm extends DirectorObjectForm return $this; } + protected function hasHostGroupRestriction() + { + foreach ($this->getObjectRestrictions() as $restriction) { + if ($restriction instanceof BetaHostgroupRestriction) { + if ($restriction->isRestricted()) { + return true; + } + } + } + + return false; + } + /** * @return $this */ diff --git a/application/tables/IcingaHostTable.php b/application/tables/IcingaHostTable.php index ed4afbfe..4efdf0bc 100644 --- a/application/tables/IcingaHostTable.php +++ b/application/tables/IcingaHostTable.php @@ -2,16 +2,25 @@ namespace Icinga\Module\Director\Tables; +use Icinga\Module\Director\Restriction\ObjectRestriction; use Icinga\Module\Director\Web\Table\QuickTable; class IcingaHostTable extends QuickTable { + protected $objectRestrictions = array(); + protected $searchColumns = array( 'host', 'address', 'display_name' ); + public function addObjectRestriction(ObjectRestriction $restriction) + { + $this->objectRestrictions[$restriction->getName()] = $restriction; + return $this; + } + public function getColumns() { return array( @@ -58,10 +67,16 @@ class IcingaHostTable extends QuickTable protected function getUnfilteredQuery() { - return $this->db()->select()->from( + $query = $this->db()->select()->from( array('h' => 'icinga_host'), array() )->order('h.object_name'); + + foreach ($this->objectRestrictions as $restriction) { + $restriction->applyToHostsQuery($query); + } + + return $query; } public function getBaseQuery() diff --git a/configuration.php b/configuration.php index c2f82197..f753951d 100644 --- a/configuration.php +++ b/configuration.php @@ -18,6 +18,14 @@ $this->providePermission( ); $this->providePermission('director/*', $this->translate('Allow unrestricted access to Icinga Director')); +$this->provideRestriction( + 'director/beta-filter/hostgroups', + $this->translate( + 'BETA: Limit access to the given comma-separated list of hostgroups. This' + . ' restriction might change without pre-announcement' + ) +); + $this->provideSearchUrl($this->translate('Host configs'), 'director/hosts?limit=10', 60); /* diff --git a/library/Director/Dashboard/Dashboard.php b/library/Director/Dashboard/Dashboard.php index 32ab52c6..fd51fe35 100644 --- a/library/Director/Dashboard/Dashboard.php +++ b/library/Director/Dashboard/Dashboard.php @@ -4,7 +4,9 @@ namespace Icinga\Module\Director\Dashboard; use Countable; use Exception; +use Icinga\Authentication\Auth; use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Restriction\BetaHostgroupRestriction; use Icinga\Web\View; use Icinga\Module\Director\Dashboard\Dashlet\Dashlet; use Icinga\Module\Director\Db; @@ -183,10 +185,40 @@ abstract class Dashboard implements Countable } } - return $this->db->getDbAdapter()->select()->from( + $query = $this->db->getDbAdapter()->select()->from( array('o' => 'icinga_' . $type), $columns ); + + return $this->applyRestrictions($type, $query); + } + + protected function applyRestrictions($type, $query) + { + switch ($type) { + case 'hostgroup': + $r = new BetaHostgroupRestriction($this->getDb(), $this->getAuth()); + $r->applyToHostGroupsQuery($query); + break; + case 'host': + $r = new BetaHostgroupRestriction($this->getDb(), $this->getAuth()); + $r->applyToHostsQuery($query, 'o.id'); + break; + } + + return $query; + } + + protected function applyHostgroupRestrictions($query) + { + $restrictions = new BetaHostgroupRestriction($this->getDb(), $this->getAuth()); + $restrictions->applyToHostGroupsQuery($query); + + } + + protected function getAuth() + { + return Auth::getInstance(); } protected function getCntSql($objectType) diff --git a/library/Director/Restriction/BetaHostgroupRestriction.php b/library/Director/Restriction/BetaHostgroupRestriction.php new file mode 100644 index 00000000..ee1e6a6c --- /dev/null +++ b/library/Director/Restriction/BetaHostgroupRestriction.php @@ -0,0 +1,80 @@ +isRestricted()) { + return true; + } + + $query = $this->db->select()->from( + array('h' => 'icinga_host'), + array('id') + ); + + $this->applyToHostsQuery($query); + + return (int) $this->db->fetchOne($query) === (int) $host->get('id'); + } + + public function applyToHostsQuery(ZfSelect $query, $hostIdColumn = 'h.id') + { + if (! $this->isRestricted()) { + return; + } + $groups = $this->listRestrictedHostgroups(); + + if (empty($groups)) { + $query->where('(1 = 0)'); + } else { + $sub = $this->db->select()->from( + array('hgh' => 'icinga_hostgroup_host'), + array('e' => '(1)') + )->join( + array('hg' => 'icinga_hostgroup'), + 'hgh.hostgroup_id = hg.id' + )->where('hgh.host_id = ' . $hostIdColumn) + ->where('hg.object_name IN (?)', $groups); + + $query->where('EXISTS ?', $sub); + } + } + + public function applyToHostGroupsQuery(ZfSelect $query) + { + if (! $this->isRestricted()) { + return; + } + $groups = $this->listRestrictedHostgroups(); + + if (empty($groups)) { + $query->where('(1 = 0)'); + } else { + $query->where('object_name IN (?)', $groups); + } + } + + protected function listRestrictedHostgroups() + { + if ($restrictions = $this->auth->getRestrictions($this->getName())) { + $groups = array(); + foreach ($restrictions as $restriction) { + foreach ($this->gracefullySplitOnComma($restriction) as $group) { + $groups[$group] = $group; + } + } + + return array_keys($groups); + } else { + return null; + } + } +} diff --git a/library/Director/Restriction/ObjectRestriction.php b/library/Director/Restriction/ObjectRestriction.php new file mode 100644 index 00000000..81cd8730 --- /dev/null +++ b/library/Director/Restriction/ObjectRestriction.php @@ -0,0 +1,45 @@ +db = $connection->getDbAdapter(); + $this->auth = $auth; + } + + public function getName() + { + if ($this->name === null) { + throw new ProgrammingError('ObjectRestriction has no name'); + } + + return $this->name; + } + + public function isRestricted() + { + $restrictions = $this->auth->getRestrictions($this->getName()); + return ! empty($restrictions); + } + + protected function gracefullySplitOnComma($string) + { + return preg_split('/\s*,\s*/', $string, -1, PREG_SPLIT_NO_EMPTY); + } +} diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index 118bc6d4..9e93b9e6 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -22,6 +22,16 @@ abstract class ObjectController extends ActionController 'endpoint' ); + protected function loadRestrictions() + { + return array(); + } + + protected function allowsObject(IcingaObject $object) + { + return true; + } + public function init() { parent::init(); @@ -169,6 +179,7 @@ abstract class ObjectController extends ActionController ->setDb($this->db()) ->setApi($this->getApiIfAvailable()); $form->setObject($object); + $form->setObjectRestrictions($this->loadRestrictions()); $this->view->title = $object->object_name; $this->view->form->handleRequest(); @@ -334,6 +345,11 @@ abstract class ObjectController extends ActionController $name, $this->db() ); + + if (! $this->allowsObject($this->object)) { + $this->object = null; + throw new NotFoundError('No such object available'); + } } elseif ($id = $this->params->get('id')) { $this->object = IcingaObject::loadByType( $this->getType(), diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index 78bdb47d..a8537457 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -142,6 +142,8 @@ abstract class ObjectsController extends ActionController } } + $this->applyTableFilters($table); + $this->view->title = $title; $this->view->addLink = $this->view->qlink( @@ -155,6 +157,10 @@ abstract class ObjectsController extends ActionController $this->setViewScript('objects/table'); } + protected function applyTableFilters($table) + { + } + public function editAction() { $type = ucfirst($this->getType()); diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index 76a11ceb..a669021e 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -11,6 +11,7 @@ use Icinga\Module\Director\Exception\NestingError; use Icinga\Module\Director\IcingaConfig\StateFilterSet; use Icinga\Module\Director\IcingaConfig\TypeFilterSet; use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\Restriction\ObjectRestriction; use Icinga\Module\Director\Util; use Zend_Form_Element as ZfElement; use Zend_Form_Element_Select as ZfSelect; @@ -49,6 +50,9 @@ abstract class DirectorObjectForm extends QuickForm private $presetImports; + /** @var ObjectRestriction[] */ + private $objectRestrictions = array(); + private $earlyProperties = array( 'imports', 'check_command', @@ -561,6 +565,38 @@ abstract class DirectorObjectForm extends QuickForm return $this; } + /** + * @param ObjectRestriction[] $restrictions + * @return $this + */ + public function setObjectRestrictions(array $restrictions) + { + $this->objectRestrictions = array(); + foreach ($restrictions as $restriction) { + $this->addObjectRestriction($restriction); + } + + return $this; + } + + /** + * @param ObjectRestriction $restriction + * @return $this + */ + public function addObjectRestriction(ObjectRestriction $restriction) + { + $this->objectRestrictions[$restriction->getName()] = $restriction; + return $this; + } + + /** + * @return ObjectRestriction[] + */ + public function getObjectRestrictions() + { + return $this->objectRestrictions; + } + protected function getObjectClassname() { if ($this->className === null) {