diff options
Diffstat (limited to 'library/Icingadb/Common/CommandActions.php')
-rw-r--r-- | library/Icingadb/Common/CommandActions.php | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/library/Icingadb/Common/CommandActions.php b/library/Icingadb/Common/CommandActions.php new file mode 100644 index 0000000..2cd13fe --- /dev/null +++ b/library/Icingadb/Common/CommandActions.php @@ -0,0 +1,308 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Common; + +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Forms\Command\Object\AcknowledgeProblemForm; +use Icinga\Module\Icingadb\Forms\Command\Object\AddCommentForm; +use Icinga\Module\Icingadb\Forms\Command\Object\CheckNowForm; +use Icinga\Module\Icingadb\Forms\Command\Object\ProcessCheckResultForm; +use Icinga\Module\Icingadb\Forms\Command\Object\RemoveAcknowledgementForm; +use Icinga\Module\Icingadb\Forms\Command\Object\ScheduleCheckForm; +use Icinga\Module\Icingadb\Forms\Command\Object\ScheduleHostDowntimeForm; +use Icinga\Module\Icingadb\Forms\Command\Object\ScheduleServiceDowntimeForm; +use Icinga\Module\Icingadb\Forms\Command\Object\SendCustomNotificationForm; +use Icinga\Module\Icingadb\Forms\Command\Object\ToggleObjectFeaturesForm; +use Icinga\Security\SecurityException; +use Icinga\Web\Notification; +use ipl\Orm\Model; +use ipl\Orm\Query; +use ipl\Web\Url; + +/** + * Trait CommandActions + */ +trait CommandActions +{ + /** @var Query $commandTargets */ + protected $commandTargets; + + /** @var Model $commandTargetModel */ + protected $commandTargetModel; + + /** + * Get url to view command targets, used as redirection target + * + * @return Url + */ + abstract protected function getCommandTargetsUrl(): Url; + + /** + * Get status of toggleable features + * + * @return object + */ + protected function getFeatureStatus() + { + } + + /** + * Fetch command targets + * + * @return Query|Model[] + */ + abstract protected function fetchCommandTargets(); + + /** + * Get command targets + * + * @return Query|Model[] + */ + protected function getCommandTargets() + { + if (! isset($this->commandTargets)) { + $this->commandTargets = $this->fetchCommandTargets(); + } + + return $this->commandTargets; + } + + /** + * Get the model of the command targets + * + * @return Model + */ + protected function getCommandTargetModel(): Model + { + if (! isset($this->commandTargetModel)) { + $commandTargets = $this->getCommandTargets(); + if (is_array($commandTargets) && !empty($commandTargets)) { + $this->commandTargetModel = $commandTargets[0]; + } else { + $this->commandTargetModel = $commandTargets->getModel(); + } + } + + return $this->commandTargetModel; + } + + /** + * Check whether the permission is granted on any of the command targets + * + * @param string $permission + * + * @return bool + */ + protected function isGrantedOnCommandTargets(string $permission): bool + { + $commandTargets = $this->getCommandTargets(); + if (is_array($commandTargets)) { + foreach ($commandTargets as $commandTarget) { + if ($this->isGrantedOn($permission, $commandTarget)) { + return true; + } + } + + return false; + } + + return $this->isGrantedOnType( + $permission, + $this->getCommandTargetModel()->getTableName(), + $commandTargets->getFilter() + ); + } + + /** + * Assert that the permission is granted on any of the command targets + * + * @param string $permission + * + * @throws SecurityException + */ + protected function assertIsGrantedOnCommandTargets(string $permission) + { + if (! $this->isGrantedOnCommandTargets($permission)) { + throw new SecurityException('No permission for %s', $permission); + } + } + + /** + * Handle and register the given command form + * + * @param string|CommandForm $form + * + * @return void + */ + protected function handleCommandForm($form) + { + $isXhr = $this->getRequest()->isXmlHttpRequest(); + if ($isXhr && $this->getRequest()->isApiRequest()) { + // Prevents the framework already, this is just a fail-safe + $this->httpBadRequest('Responding with JSON during a Web request is not supported'); + } + + if (is_string($form)) { + /** @var CommandForm $form */ + $form = new $form(); + } + + $form->setObjects($this->getCommandTargets()); + + if ($isXhr) { + $this->handleWebRequest($form); + } else { + $this->handleApiRequest($form); + } + } + + /** + * Handle a Web request for the given form + * + * @param CommandForm $form + * + * @return void + */ + protected function handleWebRequest(CommandForm $form): void + { + $actionUrl = $this->getRequest()->getUrl(); + if ($this->view->compact) { + $actionUrl = clone $actionUrl; + // TODO: This solves https://github.com/Icinga/icingadb-web/issues/124 but I'd like to omit this + // entirely. I think it should be solved like https://github.com/Icinga/icingaweb2/pull/4300 so + // that a request's url object still has params like showCompact and _dev + $actionUrl->getParams()->add('showCompact', true); + } + + $form->setAction($actionUrl->getAbsoluteUrl()); + $form->on($form::ON_SUCCESS, function () { + // This forces the column to reload nearly instantly after the redirect + // and ensures the effect of the command is visible to the user asap + $this->getResponse()->setAutoRefreshInterval(1); + + $this->redirectNow($this->getCommandTargetsUrl()); + }); + + $form->handleRequest($this->getServerRequest()); + + $this->addContent($form); + } + + /** + * Handle an API request for the given form + * + * @param CommandForm $form + * + * @return never + */ + protected function handleApiRequest(CommandForm $form) + { + $form->setIsApiTarget(); + $form->on($form::ON_SUCCESS, function () { + $this->getResponse() + ->json() + ->setSuccessData(Notification::getInstance()->popMessages()) + ->sendResponse(); + }); + + $form->handleRequest($this->getServerRequest()); + + $errors = []; + foreach ($form->getElements() as $element) { + $errors[$element->getName()] = $element->getMessages(); + } + + $response = $this->getResponse()->json(); + $response->setHttpResponseCode(422); + $response->setFailData($errors) + ->sendResponse(); + } + + public function acknowledgeAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/acknowledge-problem'); + $this->setTitle(t('Acknowledge Problem')); + $this->handleCommandForm(AcknowledgeProblemForm::class); + } + + public function addCommentAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/comment/add'); + $this->setTitle(t('Add Comment')); + $this->handleCommandForm(AddCommentForm::class); + } + + public function checkNowAction() + { + if (! $this->isGrantedOnCommandTargets('icingadb/command/schedule-check/active-only')) { + $this->assertIsGrantedOnCommandTargets('icingadb/command/schedule-check'); + } + + $this->handleCommandForm(CheckNowForm::class); + } + + public function processCheckresultAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/process-check-result'); + $this->setTitle(t('Submit Passive Check Result')); + $this->handleCommandForm(ProcessCheckResultForm::class); + } + + public function removeAcknowledgementAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/remove-acknowledgement'); + $this->handleCommandForm(RemoveAcknowledgementForm::class); + } + + public function scheduleCheckAction() + { + if (! $this->isGrantedOnCommandTargets('icingadb/command/schedule-check/active-only')) { + $this->assertIsGrantedOnCommandTargets('icingadb/command/schedule-check'); + } + + $this->setTitle(t('Reschedule Check')); + $this->handleCommandForm(ScheduleCheckForm::class); + } + + public function scheduleDowntimeAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/downtime/schedule'); + + switch ($this->getCommandTargetModel()->getTableName()) { + case 'host': + $this->setTitle(t('Schedule Host Downtime')); + $this->handleCommandForm(ScheduleHostDowntimeForm::class); + break; + case 'service': + $this->setTitle(t('Schedule Service Downtime')); + $this->handleCommandForm(ScheduleServiceDowntimeForm::class); + break; + } + } + + public function sendCustomNotificationAction() + { + $this->assertIsGrantedOnCommandTargets('icingadb/command/send-custom-notification'); + $this->setTitle(t('Send Custom Notification')); + $this->handleCommandForm(SendCustomNotificationForm::class); + } + + public function toggleFeaturesAction() + { + $commandObjects = $this->getCommandTargets(); + $form = null; + if (count($commandObjects) > 1) { + $this->isGrantedOnCommandTargets('i/am-only-used/to-establish/the-object-auth-cache'); + $form = new ToggleObjectFeaturesForm($this->getFeatureStatus()); + } else { + foreach ($commandObjects as $object) { + // There's only a single result, a foreach is the most compatible way to retrieve the object + $form = new ToggleObjectFeaturesForm($object); + } + } + + $this->handleCommandForm($form); + } +} |