From 0e10545175b4fc37f74858481640f0e9dd43fd8b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 12 Oct 2017 16:26:50 +0200 Subject: [PATCH] ServiceSet: allow controlled/restricted access fixes #1235 --- application/forms/IcingaServiceForm.php | 13 +-- application/forms/IcingaServiceSetForm.php | 12 +++ configuration.php | 10 +++ doc/82-Changelog.md | 7 +- .../Dashboard/Dashlet/ServiceSetsDashlet.php | 2 +- .../Restriction/FilterByNameRestriction.php | 82 +++++++++++++++++++ .../Web/Controller/ObjectController.php | 18 +++- .../Web/Controller/ObjectsController.php | 8 +- .../Web/Form/Validate/NamePattern.php | 1 + library/Director/Web/Table/ObjectSetTable.php | 22 ++++- library/Director/Web/Tabs/ObjectsTabs.php | 2 +- 11 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 library/Director/Restriction/FilterByNameRestriction.php diff --git a/application/forms/IcingaServiceForm.php b/application/forms/IcingaServiceForm.php index 44b23054..05952255 100644 --- a/application/forms/IcingaServiceForm.php +++ b/application/forms/IcingaServiceForm.php @@ -310,11 +310,14 @@ class IcingaServiceForm extends DirectorObjectForm $this->addNameElement() ->addDisabledElement() - ->groupMainProperties() - ->addCheckCommandElements(true) - ->addCheckExecutionElements(true) - ->addExtraInfoElements() - ->setButtons(); + ->groupMainProperties(); + + if ($this->hasPermission('director/admin')) { + $this->addCheckCommandElements(true) + ->addCheckExecutionElements(true) + ->addExtraInfoElements() + ->setButtons(); + } if ($this->hasBeenSent()) { $name = $this->getSentOrObjectValue('object_name'); diff --git a/application/forms/IcingaServiceSetForm.php b/application/forms/IcingaServiceSetForm.php index a7545dcd..a7b96bdb 100644 --- a/application/forms/IcingaServiceSetForm.php +++ b/application/forms/IcingaServiceSetForm.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Forms; use Icinga\Module\Director\Objects\IcingaHost; use Icinga\Module\Director\Web\Form\DirectorObjectForm; +use Icinga\Module\Director\Web\Form\Validate\NamePattern; class IcingaServiceSetForm extends DirectorObjectForm { @@ -32,6 +33,13 @@ class IcingaServiceSetForm extends DirectorObjectForm 'required' => true, )); + $rName = 'director/service_set/filter-by-name'; + foreach ($this->getAuth()->getRestrictions($rName) as $restriction) { + $this->getElement('object_name')->addValidator( + new NamePattern($restriction) + ); + } + $this->addHidden('object_type', 'template'); $this->addDescriptionElement() ->addAssignmentElements(); @@ -110,6 +118,10 @@ class IcingaServiceSetForm extends DirectorObjectForm protected function addAssignmentElements() { + if (! $this->hasPermission('director/service_set/apply')) { + return $this; + } + $this->addAssignFilter([ 'suggestionContext' => 'HostFilterColumns', 'description' => $this->translate( diff --git a/configuration.php b/configuration.php index 39906cbc..8d06ce0e 100644 --- a/configuration.php +++ b/configuration.php @@ -13,6 +13,8 @@ $this->providePermission( $this->providePermission('director/deploy', $this->translate('Allow to deploy configuration')); $this->providePermission('director/hosts', $this->translate('Allow to configure hosts')); $this->providePermission('director/services', $this->translate('Allow to configure services')); +$this->providePermission('director/servicesets', $this->translate('Allow to configure service sets')); +$this->providePermission('director/service_set/apply', $this->translate('Allow to define Service Set Apply Rules')); $this->providePermission('director/users', $this->translate('Allow to configure users')); $this->providePermission('director/notifications', $this->translate('Allow to configure notifications')); $this->providePermission( @@ -44,6 +46,14 @@ $this->provideRestriction( ) ); +$this->provideRestriction( + 'director/service_set/filter-by-name', + $this->translate( + 'Filter available service set templates. Use asterisks (*) as wildcards,' + . ' like in DB* or *net*' + ) +); + $this->provideSearchUrl($this->translate('Host configs'), 'director/hosts?limit=10', 60); /* diff --git a/doc/82-Changelog.md b/doc/82-Changelog.md index 817161cb..610f8f0c 100644 --- a/doc/82-Changelog.md +++ b/doc/82-Changelog.md @@ -12,10 +12,11 @@ before switching to a new version. ### Permissions and Restrictions * FEATURE: Showing the executed SQL query now requires the `showsql` permission +* FEATURE: Grant access to Service Set in a controlled way -### UI features -* Admins have now access to JSON download links in many places -* Users equipped with related permissions can toggle "Show SQL" in the GUI +### User Interface +* FEATURE: Admins have now access to JSON download links in many places +* FEATURE: Users equipped with related permissions can toggle "Show SQL" in the GUI 1.4.1 ----- diff --git a/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php b/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php index cb70341a..f971d421 100644 --- a/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php +++ b/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php @@ -26,6 +26,6 @@ class ServiceSetsDashlet extends Dashlet public function listRequiredPermissions() { - return array('director/service_sets'); + return array('director/servicesets'); } } diff --git a/library/Director/Restriction/FilterByNameRestriction.php b/library/Director/Restriction/FilterByNameRestriction.php new file mode 100644 index 00000000..f03022c9 --- /dev/null +++ b/library/Director/Restriction/FilterByNameRestriction.php @@ -0,0 +1,82 @@ +setType($type); + } + + protected function setType($type) + { + $this->type = $type; + $this->setNameForType($type); + } + + protected function setNameForType($type) + { + $this->name = "director/${type}/filter-by-name"; + } + + public function allows(IcingaObject $object) + { + if (! $this->isRestricted()) { + return true; + } + + return $this->prepareFilter()->matches([ + (object) ['object_name' => $object->getObjectName()] + ]); + } + + public function getFilter() + { + if ($this->filter === null) { + $this->filter = $this->prepareFilter(); + } + + return $this->filter; + } + + protected function filterQuery(ZfSelect $query, $tableAlias = 'o') + { + FilterRenderer::applyToQuery($this->getFilter(), $query); + } + + protected function prepareFilter() + { + $filter = Filter::matchAll(); + foreach ($this->auth->getRestrictions($this->name) as $restriction) { + $filter->addFilter(Filter::expression('object_name', '=', $restriction)); + } + + return $filter; + } + + protected function preparePrefixedFilter($prefix) + { + $filter = Filter::matchAll(); + foreach ($this->auth->getRestrictions($this->name) as $restriction) { + $filter->addFilter( + Filter::expression("$prefix.object_name", '=', $restriction) + ); + } + + return $filter; + } +} diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index dc5edacc..4fc9efaf 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -116,7 +116,8 @@ abstract class ObjectController extends ActionController public function renderAction() { - $this->assertPermission('director/showconfig'); + $this->assertTypePermission() + ->assertPermission('director/showconfig'); $this->tabs()->activate('render'); $preview = new ObjectPreview($this->requireObject(), $this->getRequest()); if ($this->object->isExternal()) { @@ -127,7 +128,7 @@ abstract class ObjectController extends ActionController public function cloneAction() { - $this->assertPermission('director/' . strtolower($this->getPluralType())); + $this->assertTypePermission(); $object = $this->requireObject(); $form = IcingaCloneObjectForm::load() ->setObject($object) @@ -181,7 +182,9 @@ abstract class ObjectController extends ActionController public function historyAction() { - $this->assertPermission('director/audit') + $this + ->assertTypePermission() + ->assertPermission('director/audit') ->setAutorefreshInterval(10) ->tabs()->activate('history'); @@ -268,7 +271,7 @@ abstract class ObjectController extends ActionController protected function addObject() { - $this->assertPermission('director/' . $this->getPluralType()); + $this->assertTypePermission(); $imports = $this->params->get('imports'); if (is_string($imports) && strlen($imports)) { $this->addTitle( @@ -322,6 +325,13 @@ abstract class ObjectController extends ActionController return $this->translate(ucfirst($this->getType())); } + protected function assertTypePermission() + { + return $this->assertPermission( + 'director/' . strtolower($this->getPluralType()) + ); + } + protected function eventuallyLoadObject() { if (null !== $this->params->get('name') || $this->params->get('id')) { diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index dc7839bf..1ad7bf3b 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -215,7 +215,7 @@ abstract class ObjectsController extends ActionController $type = $this->getType(); $tType = $this->translate(ucfirst($type)); $this - ->assertPermission('director/' . $this->getBaseType() . '_sets') + ->assertPermission('director/' . $this->getBaseType() . 'sets') ->addObjectsTabs() ->requireSupportFor('Sets') ->setAutorefreshInterval(10) @@ -240,7 +240,7 @@ abstract class ObjectsController extends ActionController ) ); - ObjectSetTable::create($type, $this->db())->renderTo($this); + ObjectSetTable::create($type, $this->db(), $this->getAuth())->renderTo($this); } protected function loadMultiObjectsFromParams() @@ -308,8 +308,8 @@ abstract class ObjectsController extends ActionController { // Strip final 's' and upcase an eventual 'group' return preg_replace( - array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/dependencie$/'), - array('Group', 'Period', 'Argument', 'ApiUser', 'dependency'), + array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/dependencie$/', '/set$/'), + array('Group', 'Period', 'Argument', 'ApiUser', 'dependency', 'Set'), str_replace( 'template', '', diff --git a/library/Director/Web/Form/Validate/NamePattern.php b/library/Director/Web/Form/Validate/NamePattern.php index 7b26eef5..15d74be4 100644 --- a/library/Director/Web/Form/Validate/NamePattern.php +++ b/library/Director/Web/Form/Validate/NamePattern.php @@ -26,6 +26,7 @@ class NamePattern extends Zend_Validate_Abstract { if ($this->filter === null) { $this->filter = new FilterMatch('prop', '=', $this->pattern); + $this->filter->setCaseSensitive(false); } return $this->filter->matches($value); diff --git a/library/Director/Web/Table/ObjectSetTable.php b/library/Director/Web/Table/ObjectSetTable.php index 05c59665..79f5afc6 100644 --- a/library/Director/Web/Table/ObjectSetTable.php +++ b/library/Director/Web/Table/ObjectSetTable.php @@ -2,10 +2,12 @@ namespace Icinga\Module\Director\Web\Table; +use Icinga\Authentication\Auth; use Icinga\Module\Director\Db; use dipl\Html\Link; use dipl\Web\Table\ZfQueryBasedTable; use dipl\Web\Url; +use Icinga\Module\Director\Restriction\FilterByNameRestriction; class ObjectSetTable extends ZfQueryBasedTable { @@ -17,10 +19,14 @@ class ObjectSetTable extends ZfQueryBasedTable private $type; - public static function create($type, Db $db) + /** @var Auth */ + private $auth; + + public static function create($type, Db $db, Auth $auth) { $table = new static($db); $table->type = $type; + $table->auth = $auth; return $table; } @@ -79,6 +85,12 @@ class ObjectSetTable extends ZfQueryBasedTable [] ); + $nameFilter = new FilterByNameRestriction( + $this->connection(), + $this->auth, + "${type}_set" + ); + $nameFilter->applyToQuery($query, 'os'); // Disabled for now, check for correctness: // $query->joinLeft( // ['osi' => "icinga_${type}_set_inheritance"], @@ -98,4 +110,12 @@ class ObjectSetTable extends ZfQueryBasedTable return $query; } + + /** + * @return Db + */ + public function connection() + { + return parent::connection(); + } } diff --git a/library/Director/Web/Tabs/ObjectsTabs.php b/library/Director/Web/Tabs/ObjectsTabs.php index b9d4a187..7519eaee 100644 --- a/library/Director/Web/Tabs/ObjectsTabs.php +++ b/library/Director/Web/Tabs/ObjectsTabs.php @@ -70,7 +70,7 @@ class ObjectsTabs extends Tabs )); } } - if ($object->supportsSets() && $auth->hasPermission("director/${type}_sets")) { + if ($object->supportsSets() && $auth->hasPermission("director/${type}sets")) { $this->add('sets', array( 'url' => sprintf('director/%s/sets', $plType), 'label' => $this->translate('Sets')