From 8ca6cc32b2c789a3149861159ad258f2cb9491e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:39:39 +0200 Subject: Adding upstream version 2.11.4. Signed-off-by: Daniel Baumann --- .../library/Monitoring/Object/MonitoredObject.php | 930 +++++++++++++++++++++ 1 file changed, 930 insertions(+) create mode 100644 modules/monitoring/library/Monitoring/Object/MonitoredObject.php (limited to 'modules/monitoring/library/Monitoring/Object/MonitoredObject.php') diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php new file mode 100644 index 0000000..91fd9e7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -0,0 +1,930 @@ +backend = $backend; + } + + /** + * Get the object's data view + * + * @return \Icinga\Module\Monitoring\DataView\DataView + */ + abstract protected function getDataView(); + + /** + * Get all note urls configured for this monitored object + * + * @return array All note urls as a string + */ + abstract public function getNotesUrls(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + + return $this->filter; + } + + /** + * {@inheritdoc} + */ + public function setFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * Require the object's type to be one of the given types + * + * @param array $oneOf + * + * @return bool + * @throws InvalidArgumentException If the object's type is not one of the given types. + */ + public function assertOneOf(array $oneOf) + { + if (! in_array($this->type, $oneOf)) { + throw new InvalidArgumentException; + } + return true; + } + + /** + * Fetch the object's properties + * + * @return bool + */ + public function fetch() + { + $properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow(); + + if ($properties === false) { + return false; + } + + if (isset($properties->host_contacts)) { + $this->contacts = array(); + foreach (preg_split('~,~', $properties->host_contacts) as $contact) { + $this->contacts[] = (object) array( + 'contact_name' => $contact, + 'contact_alias' => $contact, + 'contact_email' => null, + 'contact_pager' => null, + ); + } + } + + $this->properties = $properties; + + return true; + } + + /** + * Fetch the object's acknowledgement + */ + public function fetchAcknowledgement() + { + if ($this->comments === null) { + $this->fetchComments(); + } + + return $this; + } + + /** + * Fetch the object's comments + * + * @return $this + */ + public function fetchComments() + { + $commentsView = $this->backend->select()->from('comment', array( + 'author' => 'comment_author_name', + 'comment' => 'comment_data', + 'expiration' => 'comment_expiration', + 'id' => 'comment_internal_id', + 'name' => 'comment_name', + 'persistent' => 'comment_is_persistent', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type' + )); + if ($this->type === self::TYPE_SERVICE) { + $commentsView + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $commentsView->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $commentsView + ->whereEx(new FilterEqual('comment_type', '=', ['ack', 'comment'])) + ->whereEx(new FilterEqual('object_type', '=', $this->type)); + + $comments = $commentsView->fetchAll(); + + if ((bool) $this->properties->{$this->prefix . 'acknowledged'}) { + $ackCommentIdx = null; + + foreach ($comments as $i => $comment) { + if ($comment->type === 'ack') { + $this->acknowledgement = new Acknowledgement(array( + 'author' => $comment->author, + 'comment' => $comment->comment, + 'entry_time' => $comment->timestamp, + 'expiration_time' => $comment->expiration, + 'sticky' => (int) $this->properties->{$this->prefix . 'acknowledgement_type'} === 2 + )); + $ackCommentIdx = $i; + break; + } + } + + if ($ackCommentIdx !== null) { + unset($comments[$ackCommentIdx]); + } + } + + $this->comments = $comments; + + return $this; + } + + /** + * Fetch the object's contact groups + * + * @return $this + */ + public function fetchContactgroups() + { + $contactsGroups = $this->backend->select()->from('contactgroup', array( + 'contactgroup_name', + 'contactgroup_alias' + )); + if ($this->type === self::TYPE_SERVICE) { + $contactsGroups + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contactsGroups->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contactgroups = $contactsGroups; + return $this; + } + + /** + * Fetch the object's contacts + * + * @return $this + */ + public function fetchContacts() + { + $contacts = $this->backend->select()->from("{$this->type}contact", array( + 'contact_name', + 'contact_alias', + 'contact_email', + 'contact_pager', + )); + if ($this->type === self::TYPE_SERVICE) { + $contacts + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contacts->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contacts = $contacts; + return $this; + } + + /** + * Fetch this object's obfuscated custom variables + * + * @return $this + */ + public function fetchCustomvars() + { + + if ($this->type === self::TYPE_SERVICE) { + $this->fetchServiceVariables(); + $customvars = $this->serviceVariables; + } else { + $this->fetchHostVariables(); + $customvars = $this->hostVariables; + } + + $this->customvars = $customvars; + $this->hideBlacklistedProperties(); + $this->customvars = $this->obfuscateCustomVars($this->customvars, null); + $this->customvarsWithOriginalNames = $this->obfuscateCustomVars($this->customvarsWithOriginalNames, null); + + return $this; + } + + /** + * Obfuscate custom variables recursively + * + * @param stdClass|array $customvars The custom variables to obfuscate + * + * @return stdClass|array The obfuscated custom variables + */ + protected function obfuscateCustomVars($customvars, $_) + { + return self::protectCustomVars($customvars); + } + + public static function protectCustomVars($customvars) + { + $blacklist = []; + $blacklistPattern = ''; + + if (($blacklistConfig = Config::module('monitoring')->get('security', 'protected_customvars', '')) !== '') { + foreach (explode(',', $blacklistConfig) as $customvar) { + $nonWildcards = array(); + foreach (explode('*', $customvar) as $nonWildcard) { + $nonWildcards[] = preg_quote($nonWildcard, '/'); + } + $blacklist[] = implode('.*', $nonWildcards); + } + $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i'; + } + + if (! $blacklistPattern) { + return $customvars; + } + + $obfuscator = function ($vars) use ($blacklistPattern, &$obfuscator) { + $result = []; + foreach ($vars as $name => $value) { + if ($blacklistPattern && preg_match($blacklistPattern, $name)) { + $result[$name] = '***'; + } elseif ($value instanceof stdClass || is_array($value)) { + $obfuscated = $obfuscator($value); + $result[$name] = $value instanceof stdClass ? (object) $obfuscated : $obfuscated; + } else { + $result[$name] = $value; + } + } + + return $result; + }; + $obfuscatedCustomVars = $obfuscator($customvars); + + return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars; + } + + /** + * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties + * + * Currently this only affects the custom variables + */ + protected function hideBlacklistedProperties() + { + if ($this->blacklistedProperties === null) { + $this->blacklistedProperties = new GlobFilter( + Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') + ); + } + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvars]] + ); + $this->customvars = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvarsWithOriginalNames]] + ); + $this->customvarsWithOriginalNames = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + } + + /** + * Fetch the host custom variables related to this object + * + * @return $this + */ + public function fetchHostVariables() + { + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_HOST)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + $this->hostVariables = []; + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames = []; + } + + foreach ($query as $row) { + if ($row->is_json) { + $this->hostVariables[strtolower($row->varname)] = json_decode($row->varvalue); + } else { + $this->hostVariables[strtolower($row->varname)] = $row->varvalue; + } + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames[$row->varname] = $this->hostVariables[strtolower($row->varname)]; + } + } + + return $this; + } + + /** + * Fetch the service custom variables related to this object + * + * @return $this + * + * @throws ProgrammingError In case this object is not a service + */ + public function fetchServiceVariables() + { + if ($this->type !== static::TYPE_SERVICE) { + throw new ProgrammingError('Cannot fetch service custom variables for non-service objects'); + } + + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_SERVICE)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + + $this->serviceVariables = []; + $this->customvarsWithOriginalNames = []; + foreach ($query as $row) { + if ($row->is_json) { + $this->customvarsWithOriginalNames[$row->varname] = json_decode($row->varvalue); + $this->serviceVariables[strtolower($row->varname)] = $this->customvarsWithOriginalNames[$row->varname]; + } else { + $this->serviceVariables[strtolower($row->varname)] = $row->varvalue; + $this->customvarsWithOriginalNames[$row->varname] = $row->varvalue; + } + } + + return $this; + } + + /** + * Fetch the object's downtimes + * + * @return $this + */ + public function fetchDowntimes() + { + $downtimes = $this->backend->select()->from('downtime', array( + 'author_name' => 'downtime_author_name', + 'comment' => 'downtime_comment', + 'duration' => 'downtime_duration', + 'end' => 'downtime_end', + 'entry_time' => 'downtime_entry_time', + 'id' => 'downtime_internal_id', + 'is_fixed' => 'downtime_is_fixed', + 'is_flexible' => 'downtime_is_flexible', + 'is_in_effect' => 'downtime_is_in_effect', + 'name' => 'downtime_name', + 'objecttype' => 'object_type', + 'scheduled_end' => 'downtime_scheduled_end', + 'scheduled_start' => 'downtime_scheduled_start', + 'start' => 'downtime_start' + )) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->order('downtime_is_in_effect', 'DESC') + ->order('downtime_scheduled_start', 'ASC'); + if ($this->type === self::TYPE_SERVICE) { + $downtimes + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $downtimes + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->downtimes = $downtimes->getQuery()->fetchAll(); + return $this; + } + + /** + * Fetch the object's event history + * + * @return $this + */ + public function fetchEventhistory() + { + $eventHistory = $this->backend + ->select() + ->from( + 'eventhistory', + array( + 'id', + 'object_type', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', + 'timestamp', + 'state', + 'output', + 'type' + ) + ) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $eventHistory->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->eventhistory = $eventHistory; + return $this; + } + + /** + * Fetch the object's host groups + * + * @return $this + */ + public function fetchHostgroups() + { + $this->hostgroups = $this->backend->select() + ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchPairs(); + return $this; + } + + /** + * Fetch the object's service groups + * + * @return $this + */ + public function fetchServicegroups() + { + $query = $this->backend->select() + ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $query->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs(); + return $this; + } + + /** + * Fetch stats + * + * @return $this + */ + public function fetchStats() + { + $this->stats = $this->backend->select()->from('servicestatussummary', array( + 'services_total', + 'services_ok', + 'services_critical', + 'services_critical_unhandled', + 'services_critical_handled', + 'services_warning', + 'services_warning_unhandled', + 'services_warning_handled', + 'services_unknown', + 'services_unknown_unhandled', + 'services_unknown_handled', + 'services_pending', + )) + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchRow(); + return $this; + } + + /** + * Get all action urls configured for this monitored object + * + * @return array All note urls as a string + */ + public function getActionUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->action_url) + ); + } + + /** + * Get the type of the object + * + * @param bool $translate + * + * @return string + */ + public function getType($translate = false) + { + if ($translate !== false) { + switch ($this->type) { + case self::TYPE_HOST: + $type = mt('montiroing', 'host'); + break; + case self::TYPE_SERVICE: + $type = mt('monitoring', 'service'); + break; + default: + throw new InvalidArgumentException('Invalid type ' . $this->type); + } + } else { + $type = $this->type; + } + return $type; + } + + /** + * Parse the content of the action_url or notes_url attributes + * + * Find all occurences of http links, separated by whitespaces and quoted + * by single or double-ticks. + * + * @link http://docs.icinga.com/latest/de/objectdefinitions.html + * + * @param string $urlString A string containing one or more urls + * @return array Array of urls as strings + */ + public static function parseAttributeUrls($urlString) + { + if (empty($urlString)) { + return array(); + } + $links = array(); + if (strpos($urlString, "' ") === false) { + $links[] = $urlString; + } else { + // parse notes-url format + foreach (explode("' ", $urlString) as $url) { + $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; + $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; + $links[] = $url; + } + } + return $links; + } + + /** + * Fetch all available data of the object + * + * @return $this + */ + public function populate() + { + $this + ->fetchComments() + ->fetchContactgroups() + ->fetchContacts() + ->fetchCustomvars() + ->fetchDowntimes(); + + // Call fetchHostgroups or fetchServicegroups depending on the object's type + $fetchGroups = 'fetch' . ucfirst($this->type) . 'groups'; + $this->$fetchGroups(); + + return $this; + } + + /** + * Resolve macros in all given strings in the current object context + * + * @param array $strs An array of urls as string + * + * @return array + */ + protected function resolveAllStrings(array $strs) + { + foreach ($strs as $i => $str) { + $strs[$i] = Macro::resolveMacros($str, $this); + } + return $strs; + } + + /** + * Set the object's properties + * + * @param object $properties + * + * @return $this + */ + public function setProperties($properties) + { + $this->properties = (object) $properties; + return $this; + } + + public function __isset($name) + { + if (property_exists($this->properties, $name)) { + return isset($this->properties->$name); + } elseif (property_exists($this, $name)) { + return isset($this->$name); + } + return false; + } + + public function __get($name) + { + if (property_exists($this->properties, $name)) { + return $this->properties->$name; + } elseif (property_exists($this, $name)) { + if ($this->$name === null) { + $fetchMethod = 'fetch' . ucfirst($name); + $this->$fetchMethod(); + } + + return $this->$name; + } elseif (preg_match('/^_(host|service)_(.+)/i', $name, $matches)) { + if (strtolower($matches[1]) === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + $variableName = strtolower($matches[2]); + if (isset($customvars[$variableName])) { + return $customvars[$variableName]; + } + + return null; // Unknown custom variables MUST NOT throw an error + } elseif (in_array($name, array('contact_name', 'contactgroup_name', 'hostgroup_name', 'servicegroup_name'))) { + if ($name === 'contact_name') { + if ($this->contacts === null) { + $this->fetchContacts(); + } + + return array_map(function ($el) { + return $el->contact_name; + }, $this->contacts); + } elseif ($name === 'contactgroup_name') { + if ($this->contactgroups === null) { + $this->fetchContactgroups(); + } + + return array_map(function ($el) { + return $el->contactgroup_name; + }, $this->contactgroups); + } elseif ($name === 'hostgroup_name') { + if ($this->hostgroups === null) { + $this->fetchHostgroups(); + } + + return array_keys($this->hostgroups); + } else { // $name === 'servicegroup_name' + if ($this->servicegroups === null) { + $this->fetchServicegroups(); + } + + return array_keys($this->servicegroups); + } + } elseif (strpos($name, $this->prefix) !== 0) { + $propertyName = strtolower($name); + $prefixedName = $this->prefix . $propertyName; + if (property_exists($this->properties, $prefixedName)) { + return $this->properties->$prefixedName; + } + + if ($this->type === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { // $this->type === static::TYPE_SERVICE + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + if (isset($customvars[$propertyName])) { + return $customvars[$propertyName]; + } + } + + throw new InvalidPropertyException('Can\'t access property \'%s\'. Property does not exist.', $name); + } +} -- cgit v1.2.3