From a5f61bc0132cbc4b2515dcf255139e82f7757c4e Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Thu, 1 Sep 2022 16:15:32 +0200 Subject: [PATCH] fix(poller): fix remote server duplication (#11552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: MON-14579 Co-authored-by: Jérémy Jaouen --- bin/registerServerTopology.sh | 2 +- .../Centreon/PlatformInformation/Update.json | 3 + doc/API/centreon-api-v22.04.yaml | 4 + src/Centreon/Application/ApiPlatform.php | 10 +- .../Model/PlatformInformation.php | 24 + .../Model/PlatformInformationFactory.php | 3 + .../UpdatePartiallyPlatformInformation.php | 1 + .../Model/PlatformPending.php | 14 +- .../Model/PlatformRegistered.php | 13 +- .../PlatformTopologyService.php | 14 +- .../RemoteServerRepositoryInterface.php | 4 +- .../RemoteServer/RemoteServerService.php | 4 + .../RemoteServerRepositoryRDB.php | 8 +- .../CentreonConfigurationRemote.php | 69 +- .../Webservice/CentreonRemoteServer.php | 29 +- .../LinkedPollerConfigurationService.php | 22 +- .../CentreonEventSubscriber.php | 6 +- tests/api/features/PlatformTopology.feature | 8 +- .../centreon_configuration_poller.class.php | 4 +- www/class/centreonStatistics.class.php | 2 +- .../configuration/configServers/DB-Func.php | 100 ++- .../configServers/popup/popup.php | 26 +- www/install/createTables.sql | 4 +- www/install/insertBaseConf.sql | 2 +- www/install/php/Update-22.04.5.php | 102 +++ www/install/php/Update-22.10.0-beta.1.php | 596 ++++++++++++++++++ 26 files changed, 958 insertions(+), 116 deletions(-) create mode 100644 www/install/php/Update-22.04.5.php create mode 100644 www/install/php/Update-22.10.0-beta.1.php diff --git a/bin/registerServerTopology.sh b/bin/registerServerTopology.sh index 7ddbbb81111..99688d8f242 100755 --- a/bin/registerServerTopology.sh +++ b/bin/registerServerTopology.sh @@ -431,7 +431,7 @@ function request_to_remote() { fi # Prepare Remote Payload - REMOTE_PAYLOAD='{"isRemote":true,"platformName":"'"${CURRENT_NODE_NAME}"'","centralServerAddress":"'"${PARSED_URL[HOST]}"'","apiUsername":"'"${API_USERNAME}"'","apiCredentials":"'"${API_TARGET_PASSWORD}"'","apiScheme":"'"${PARSED_URL[SCHEME]}"'","apiPort":'"${PARSED_URL[PORT]}"',"apiPath":"'"${CENTREON_BASE_URI}"'",'"${PEER_VALIDATION}" + REMOTE_PAYLOAD='{"isRemote":true,"address":"'${PARSED_CURRENT_NODE_URL[HOST]}'","platformName":"'"${CURRENT_NODE_NAME}"'","centralServerAddress":"'"${PARSED_URL[HOST]}"'","apiUsername":"'"${API_USERNAME}"'","apiCredentials":"'"${API_TARGET_PASSWORD}"'","apiScheme":"'"${PARSED_URL[SCHEME]}"'","apiPort":'"${PARSED_URL[PORT]}"',"apiPath":"'"${CENTREON_BASE_URI}"'",'"${PEER_VALIDATION}" if [[ -n PROXY_PAYLOAD ]]; then REMOTE_PAYLOAD="${REMOTE_PAYLOAD}""${PROXY_PAYLOAD}" fi diff --git a/config/json_validator/latest/Centreon/PlatformInformation/Update.json b/config/json_validator/latest/Centreon/PlatformInformation/Update.json index 53c44fcc20d..6f7e93d6bd1 100644 --- a/config/json_validator/latest/Centreon/PlatformInformation/Update.json +++ b/config/json_validator/latest/Centreon/PlatformInformation/Update.json @@ -10,6 +10,9 @@ "isRemote": { "type": "boolean" }, + "address": { + "type": "string" + }, "centralServerAddress": { "type": "string" }, diff --git a/doc/API/centreon-api-v22.04.yaml b/doc/API/centreon-api-v22.04.yaml index 34f82120597..5f7227d9367 100644 --- a/doc/API/centreon-api-v22.04.yaml +++ b/doc/API/centreon-api-v22.04.yaml @@ -6730,6 +6730,10 @@ components: type: boolean example: true description: "Platform is a remote server" + address: + type: string + example: "10.0.0.1" + description: "The address of the platform" centralServerAddress: type: string example: "192.168.0.1" diff --git a/src/Centreon/Application/ApiPlatform.php b/src/Centreon/Application/ApiPlatform.php index 4b80b3cac34..89f4d02e48a 100644 --- a/src/Centreon/Application/ApiPlatform.php +++ b/src/Centreon/Application/ApiPlatform.php @@ -28,16 +28,16 @@ class ApiPlatform { /** - * @var float + * @var string */ private $version; /** * Get the API version * - * @return float + * @return string */ - public function getVersion(): float + public function getVersion(): string { return $this->version; } @@ -45,10 +45,10 @@ public function getVersion(): float /** * Set the API version * - * @param float $version + * @param string $version * @return $this */ - public function setVersion(float $version): self + public function setVersion(string $version): self { $this->version = $version; return $this; diff --git a/src/Centreon/Domain/PlatformInformation/Model/PlatformInformation.php b/src/Centreon/Domain/PlatformInformation/Model/PlatformInformation.php index ef5ac50725e..fa363f39863 100644 --- a/src/Centreon/Domain/PlatformInformation/Model/PlatformInformation.php +++ b/src/Centreon/Domain/PlatformInformation/Model/PlatformInformation.php @@ -40,6 +40,11 @@ class PlatformInformation */ private $platformName; + /** + * @var string server address + */ + private string $address = '127.0.0.1'; + /** * @var string|null central's address */ @@ -126,6 +131,25 @@ public function setPlatformName(?string $name): self return $this; } + /** + * @return string + */ + public function getAddress(): string + { + return $this->address; + } + + /** + * @param string $address + * @return $this + */ + public function setAddress(string $address): self + { + $this->address = $address; + + return $this; + } + /** * @return string|null */ diff --git a/src/Centreon/Domain/PlatformInformation/Model/PlatformInformationFactory.php b/src/Centreon/Domain/PlatformInformation/Model/PlatformInformationFactory.php index 226a63b5521..ab11808e97d 100644 --- a/src/Centreon/Domain/PlatformInformation/Model/PlatformInformationFactory.php +++ b/src/Centreon/Domain/PlatformInformation/Model/PlatformInformationFactory.php @@ -54,6 +54,9 @@ public function createRemoteInformation(array $information): PlatformInformation $platformInformation = new PlatformInformation($isRemote); foreach ($information as $key => $value) { switch ($key) { + case 'address': + $platformInformation->setAddress($value); + break; case 'centralServerAddress': $platformInformation->setCentralServerAddress($value); break; diff --git a/src/Centreon/Domain/PlatformInformation/UseCase/V20/UpdatePartiallyPlatformInformation.php b/src/Centreon/Domain/PlatformInformation/UseCase/V20/UpdatePartiallyPlatformInformation.php index 20bcb7e0082..408a6bf88bf 100644 --- a/src/Centreon/Domain/PlatformInformation/UseCase/V20/UpdatePartiallyPlatformInformation.php +++ b/src/Centreon/Domain/PlatformInformation/UseCase/V20/UpdatePartiallyPlatformInformation.php @@ -241,6 +241,7 @@ private function convertCentralToRemote( $platformInformationToUpdate, $currentPlatformInformation ); + $this->remoteServerService->convertCentralToRemote( $platformInformationToUpdate ); diff --git a/src/Centreon/Domain/PlatformTopology/Model/PlatformPending.php b/src/Centreon/Domain/PlatformTopology/Model/PlatformPending.php index 0e217dd3a17..be2675480c7 100644 --- a/src/Centreon/Domain/PlatformTopology/Model/PlatformPending.php +++ b/src/Centreon/Domain/PlatformTopology/Model/PlatformPending.php @@ -196,13 +196,11 @@ private function checkIpAddress(?string $address): ?string { // Check for valid IPv4 or IPv6 IP // or not sent address (in the case of Central's "parent_address") - if (null === $address || false !== filter_var($address, FILTER_VALIDATE_IP)) { - return $address; - } - - // check for DNS to be resolved - $addressResolved = filter_var(gethostbyname($address), FILTER_VALIDATE_IP); - if (false === $addressResolved) { + if ( + $address !== null + && ! filter_var($address, FILTER_VALIDATE_IP) + && ! filter_var($address, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) + ) { throw new \InvalidArgumentException( sprintf( _("The address '%s' of '%s' is not valid or not resolvable"), @@ -212,7 +210,7 @@ private function checkIpAddress(?string $address): ?string ); } - return $addressResolved; + return $address; } /** diff --git a/src/Centreon/Domain/PlatformTopology/Model/PlatformRegistered.php b/src/Centreon/Domain/PlatformTopology/Model/PlatformRegistered.php index ba3d275aa17..5eac82c5d2a 100644 --- a/src/Centreon/Domain/PlatformTopology/Model/PlatformRegistered.php +++ b/src/Centreon/Domain/PlatformTopology/Model/PlatformRegistered.php @@ -194,14 +194,11 @@ public function setHostname(?string $hostname): PlatformInterface */ private function checkIpAddress(?string $address): ?string { - // Check for valid IPv4 or IPv6 IP - // or not sent address (in the case of Central's "parent_address") - if (null === $address || false !== filter_var($address, FILTER_VALIDATE_IP)) { - return $address; - } - - // check for DNS to be resolved - if (false === filter_var(gethostbyname($address), FILTER_VALIDATE_IP)) { + if ( + $address !== null + && ! filter_var($address, FILTER_VALIDATE_IP) + && ! filter_var($address, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) + ) { throw new \InvalidArgumentException( sprintf( _("The address '%s' of '%s' is not valid or not resolvable"), diff --git a/src/Centreon/Domain/PlatformTopology/PlatformTopologyService.php b/src/Centreon/Domain/PlatformTopology/PlatformTopologyService.php index 5df7e808762..8a6978789b9 100644 --- a/src/Centreon/Domain/PlatformTopology/PlatformTopologyService.php +++ b/src/Centreon/Domain/PlatformTopology/PlatformTopologyService.php @@ -482,9 +482,14 @@ private function findParentPlatform(PlatformInterface $platform): ?PlatformInter return null; } - $registeredParentInTopology = $this->platformTopologyRepository->findPlatformByAddress( - $platform->getParentAddress() - ); + if ($platform->getType() === PlatformPending::TYPE_REMOTE) { + $registeredParentInTopology = $this->platformTopologyRepository->findTopLevelPlatform(); + } else { + $registeredParentInTopology = $this->platformTopologyRepository->findPlatformByAddress( + $platform->getParentAddress() + ); + } + if (null === $registeredParentInTopology) { throw new EntityNotFoundException( sprintf( @@ -553,6 +558,7 @@ public function getPlatformTopology(): array ); if (null !== $platformParent) { $platform->setParentAddress($platformParent->getAddress()); + $platform->setParentId($platformParent->getId()); } } @@ -614,7 +620,7 @@ public function deletePlatformAndReallocateChildren(int $serverId): void */ if ($deletedPlatform->getServerId() !== null) { if ($deletedPlatform->getType() === PlatformPending::TYPE_REMOTE) { - $this->remoteServerRepository->deleteRemoteServerByAddress($deletedPlatform->getAddress()); + $this->remoteServerRepository->deleteRemoteServerByServerId($deletedPlatform->getServerId()); $this->remoteServerRepository->deleteAdditionalRemoteServer($deletedPlatform->getServerId()); } diff --git a/src/Centreon/Domain/RemoteServer/Interfaces/RemoteServerRepositoryInterface.php b/src/Centreon/Domain/RemoteServer/Interfaces/RemoteServerRepositoryInterface.php index d87e6168bce..b2680409c7b 100644 --- a/src/Centreon/Domain/RemoteServer/Interfaces/RemoteServerRepositoryInterface.php +++ b/src/Centreon/Domain/RemoteServer/Interfaces/RemoteServerRepositoryInterface.php @@ -28,9 +28,9 @@ interface RemoteServerRepositoryInterface /** * Delete a Remote Server. * - * @param string $address + * @param int $serverId */ - public function deleteRemoteServerByAddress(string $address): void; + public function deleteRemoteServerByServerId(int $serverId): void; /** * Delete an Additional Remote Server, for pollers linked to multiple Remote Servers. diff --git a/src/Centreon/Domain/RemoteServer/RemoteServerService.php b/src/Centreon/Domain/RemoteServer/RemoteServerService.php index 105204a26f4..4e905d00d0c 100644 --- a/src/Centreon/Domain/RemoteServer/RemoteServerService.php +++ b/src/Centreon/Domain/RemoteServer/RemoteServerService.php @@ -137,16 +137,20 @@ public function convertCentralToRemote(PlatformInformation $platformInformation) if ($platformInformation->getPlatformName() !== null) { $topLevelPlatform->setName($platformInformation->getPlatformName()); } + $topLevelPlatform->setAddress($platformInformation->getAddress()); + /** * Find any children platform and forward them to Central Parent. */ $platforms = $this->platformTopologyRepository->findChildrenPlatformsByParentId( $topLevelPlatform->getId() ); + /** * Insert the Top Level Platform at the beginning of array, as it need to be registered first. */ array_unshift($platforms, $topLevelPlatform); + /** * Register the platforms on the Parent Central */ diff --git a/src/Centreon/Infrastructure/RemoteServer/RemoteServerRepositoryRDB.php b/src/Centreon/Infrastructure/RemoteServer/RemoteServerRepositoryRDB.php index f6df3a3b396..70693a9c58b 100644 --- a/src/Centreon/Infrastructure/RemoteServer/RemoteServerRepositoryRDB.php +++ b/src/Centreon/Infrastructure/RemoteServer/RemoteServerRepositoryRDB.php @@ -41,10 +41,12 @@ public function __construct(DatabaseConnection $db) /** * @inheritDoc */ - public function deleteRemoteServerByAddress(string $address): void + public function deleteRemoteServerByServerId(int $serverId): void { - $statement = $this->db->prepare($this->translateDbName("DELETE FROM remote_servers WHERE ip = :address")); - $statement->bindValue(':address', $address, \PDO::PARAM_STR); + $statement = $this->db->prepare( + $this->translateDbName("DELETE FROM remote_servers WHERE server_id = :server_id") + ); + $statement->bindValue(':server_id', $serverId, \PDO::PARAM_INT); $statement->execute(); } diff --git a/src/CentreonRemote/Application/Webservice/CentreonConfigurationRemote.php b/src/CentreonRemote/Application/Webservice/CentreonConfigurationRemote.php index 1ff417403b2..c08a3bac795 100755 --- a/src/CentreonRemote/Application/Webservice/CentreonConfigurationRemote.php +++ b/src/CentreonRemote/Application/Webservice/CentreonConfigurationRemote.php @@ -211,7 +211,7 @@ public function getList(): array public function postGetRemotesList(): array { $query = 'SELECT ns.id, ns.ns_ip_address as ip, ns.name FROM nagios_server as ns ' . - 'JOIN remote_servers as rs ON rs.ip = ns.ns_ip_address ' . + 'JOIN remote_servers as rs ON rs.server_id = ns.id ' . 'WHERE rs.is_connected = 1'; $statement = $this->pearDB->query($query); @@ -469,6 +469,7 @@ public function postLinkCentreonRemoteServer(): array // add server to the list of remote servers in database (table remote_servers) $this->addServerToListOfRemotes( + (int) $serverId, $serverIP, $centreonPath, $httpMethod, @@ -532,6 +533,7 @@ public function authorize($action, $user, $isInternal = false): bool /** * Add server ip in table of remote servers * + * @param int $serverId the poller id * @param string $serverIP the IP of the server * @param string $centreonPath the path to access to Centreon * @param string $httpMethod the method to access to server (HTTP/HTTPS) @@ -540,6 +542,7 @@ public function authorize($action, $user, $isInternal = false): bool * @param bool $noProxy to do not use configured proxy */ private function addServerToListOfRemotes( + int $serverId, string $serverIP, string $centreonPath, string $httpMethod, @@ -547,34 +550,46 @@ private function addServerToListOfRemotes( bool $noCheckCertificate, bool $noProxy ): void { - $dbAdapter = $this->getDi()[\Centreon\ServiceProvider::CENTREON_DB_MANAGER]->getAdapter('configuration_db'); - $date = date('Y-m-d H:i:s'); - - $sql = 'SELECT * FROM `remote_servers` WHERE `ip` = ?'; - $dbAdapter->query($sql, [$serverIP]); - $hasIpInTable = (bool)$dbAdapter->count(); + $currentDate = date('Y-m-d H:i:s'); - if ($hasIpInTable) { - $sql = 'UPDATE `remote_servers` SET - `is_connected` = ?, `connected_at` = ?, `centreon_path` = ?, - `no_check_certificate` = ?, `no_proxy` = ? - WHERE `ip` = ?'; - $data = ['1', $date, $centreonPath, ($noCheckCertificate ?: 0), ($noProxy ?: 0), $serverIP]; - $dbAdapter->query($sql, $data); + $statement = $this->pearDB->prepare('SELECT 1 FROM `remote_servers` WHERE `server_id` = :server_id'); + $statement->bindValue(':server_id', $serverId, \PDO::PARAM_INT); + $statement->execute(); + $remoteAlreadyExists = (bool) $statement->rowCount(); + + if ($remoteAlreadyExists) { + $updateStatement = $this->pearDB->prepare( + 'UPDATE `remote_servers` SET + `is_connected` = 1, `connected_at` = :connected_at, `centreon_path` = :centreon_path, + `no_check_certificate` = :no_check_certificate, `no_proxy` = :no_proxy, `ip_address` = :ip_address + WHERE `server_id` = :server_id' + ); + $updateStatement->bindValue(':connected_at', $currentDate, \PDO::PARAM_STR); + $updateStatement->bindValue(':centreon_path', $centreonPath, \PDO::PARAM_STR); + $updateStatement->bindValue(':no_check_certificate', $noCheckCertificate ? '1' : '0', \PDO::PARAM_STR); + $updateStatement->bindValue(':no_proxy', $noProxy ? '1' : '0', \PDO::PARAM_STR); + $updateStatement->bindValue(':ip_address', $serverIP, \PDO::PARAM_STR); + $updateStatement->bindValue(':server_id', $serverId, \PDO::PARAM_INT); + $updateStatement->execute(); } else { - $data = [ - 'ip' => $serverIP, - 'version' => '', - 'is_connected' => '1', - 'created_at' => $date, - 'connected_at' => $date, - 'centreon_path' => $centreonPath, - 'http_method' => $httpMethod, - 'http_port' => $httpPort ?: null, - 'no_check_certificate' => $noCheckCertificate ?: 0, - 'no_proxy' => $noProxy ?: 0 - ]; - $dbAdapter->insert('remote_servers', $data); + $insertStatement = $this->pearDB->prepare( + 'INSERT INTO `remote_servers` + (`ip`, `version`, `is_connected`, `created_at`, `connected_at`, `centreon_path`, + `http_method`, `http_port`, `no_check_certificate`, `no_proxy`, `server_id`) + VALUES + (:ip_address, "", 1, :created_at, :connected_at, :centreon_path, :http_method, :http_port, + :no_check_certificate, :no_proxy, :server_id)' + ); + $insertStatement->bindValue(':ip_address', $serverIP, \PDO::PARAM_STR); + $insertStatement->bindValue(':created_at', $currentDate, \PDO::PARAM_STR); + $insertStatement->bindValue(':connected_at', $currentDate, \PDO::PARAM_STR); + $insertStatement->bindValue(':centreon_path', $centreonPath, \PDO::PARAM_STR); + $insertStatement->bindValue(':http_method', $httpMethod, \PDO::PARAM_STR); + $insertStatement->bindValue(':http_port', $httpPort ?: null, \PDO::PARAM_INT); + $insertStatement->bindValue(':no_check_certificate', $noCheckCertificate ? '1' : '0', \PDO::PARAM_STR); + $insertStatement->bindValue(':no_proxy', $noProxy ? '1' : '0', \PDO::PARAM_STR); + $insertStatement->bindValue(':server_id', $serverId, \PDO::PARAM_INT); + $insertStatement->execute(); } } diff --git a/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php b/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php index 8bfd90de78f..b5f4309ec4c 100644 --- a/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php +++ b/src/CentreonRemote/Application/Webservice/CentreonRemoteServer.php @@ -112,7 +112,7 @@ public function postAddToWaitList(): string if ( !isset($_POST['version']) || !$_POST['version'] - || empty($version = filter_var($_POST['version'], FILTER_SANITIZE_STRING)) + || empty($version = filter_var($_POST['version'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)) ) { throw new \RestBadRequestException('Please send \'version\' in the request.'); } @@ -138,20 +138,21 @@ public function postAddToWaitList(): string throw new \RestConflictException('Address already in wait list.'); } - $createdAt = date('Y-m-d H:i:s'); - $insertQuery = "INSERT INTO `remote_servers` (`ip`, `version`, `is_connected`, - `created_at`, `http_method`, `http_port`, `no_check_certificate`) - VALUES (:ip, :version, 0, '{$createdAt}', - :http_method, :http_port, :no_check_certificate - )"; - - $insert = $this->pearDB->prepare($insertQuery); - $insert->bindValue(':ip', $ip, \PDO::PARAM_STR); - $insert->bindValue(':version', $version, \PDO::PARAM_STR); - $insert->bindValue(':http_method', $httpScheme, \PDO::PARAM_STR); - $insert->bindValue(':http_port', $httpPort, \PDO::PARAM_INT); - $insert->bindValue(':no_check_certificate', $noCheckCertificate, \PDO::PARAM_STR); try { + $createdAt = date('Y-m-d H:i:s'); + $insertQuery = "INSERT INTO `remote_servers` (`ip`, `version`, `is_connected`, + `created_at`, `http_method`, `http_port`, `no_check_certificate`) + VALUES (:ip, :version, 0, :created_at, + :http_method, :http_port, :no_check_certificate + )"; + + $insert = $this->pearDB->prepare($insertQuery); + $insert->bindValue(':ip', $ip, \PDO::PARAM_STR); + $insert->bindValue(':version', $version, \PDO::PARAM_STR); + $insert->bindValue(':created_at', $createdAt, \PDO::PARAM_STR); + $insert->bindValue(':http_method', $httpScheme, \PDO::PARAM_STR); + $insert->bindValue(':http_port', $httpPort, \PDO::PARAM_INT); + $insert->bindValue(':no_check_certificate', $noCheckCertificate, \PDO::PARAM_STR); $insert->execute(); } catch (\Exception $e) { throw new \RestBadRequestException('There was an error while saving the data.'); diff --git a/src/CentreonRemote/Domain/Service/ConfigurationWizard/LinkedPollerConfigurationService.php b/src/CentreonRemote/Domain/Service/ConfigurationWizard/LinkedPollerConfigurationService.php index 47b581aebb8..1ec14f7259e 100644 --- a/src/CentreonRemote/Domain/Service/ConfigurationWizard/LinkedPollerConfigurationService.php +++ b/src/CentreonRemote/Domain/Service/ConfigurationWizard/LinkedPollerConfigurationService.php @@ -354,16 +354,26 @@ private function triggerExportForOldRemotes(array $pollerIDs): void $alreadyExportedRemotes[] = $remoteID; // Get all linked pollers of the remote - $queryPollersOfRemote = "SELECT id FROM nagios_server WHERE remote_id = {$remoteID}"; - $linkedStatement = $this->db->query($queryPollersOfRemote); + $linkedStatement = $this->db->prepare( + "SELECT id + FROM nagios_server + WHERE remote_id = :remote_id" + ); + $linkedStatement->bindValue(':remote_id', $remoteID, \PDO::PARAM_INT); + $linkedStatement->execute(); $linkedResults = $linkedStatement->fetchAll(\PDO::FETCH_ASSOC); $linkedPollersOfRemote = array_column($linkedResults, 'id'); // Get information of remote - $remoteDataStatement = $this->db->query("SELECT ns.ns_ip_address as ip, rs.centreon_path, - rs.http_method, rs.http_port, rs.no_check_certificate, rs.no_proxy - FROM nagios_server as ns JOIN remote_servers as rs ON rs.ip = ns.ns_ip_address - WHERE ns.id = {$remoteID}"); + $remoteDataStatement = $this->db->prepare( + "SELECT ns.ns_ip_address as ip, rs.centreon_path, + rs.http_method, rs.http_port, rs.no_check_certificate, rs.no_proxy + FROM nagios_server as ns + JOIN remote_servers as rs ON rs.server_id = ns.id + WHERE ns.id = :server_id" + ); + $remoteDataStatement->bindValue(':server_id', $remoteID, \PDO::PARAM_INT); + $remoteDataStatement->execute(); $remoteDataResults = $remoteDataStatement->fetchAll(\PDO::FETCH_ASSOC); // Exclude the selected pollers which are going to another remote diff --git a/src/EventSubscriber/CentreonEventSubscriber.php b/src/EventSubscriber/CentreonEventSubscriber.php index 80358f8dfa5..97d4808f1bf 100644 --- a/src/EventSubscriber/CentreonEventSubscriber.php +++ b/src/EventSubscriber/CentreonEventSubscriber.php @@ -319,11 +319,11 @@ public function defineApiVersionInAttributes(RequestEvent $event): void * @todo We need to use an other name because after routing, * its value is overwritten by the value of the 'version' property from uri */ - $event->getRequest()->attributes->set('version', (float) $requestApiVersion); + $event->getRequest()->attributes->set('version', $requestApiVersion); // Used for controllers - $event->getRequest()->attributes->set('version_number', (float) $requestApiVersion); - $this->apiPlatform->setVersion((float) $requestApiVersion); + $event->getRequest()->attributes->set('version_number', $requestApiVersion); + $this->apiPlatform->setVersion($requestApiVersion); } } diff --git a/tests/api/features/PlatformTopology.feature b/tests/api/features/PlatformTopology.feature index 0e0ed386cf1..64f2a273831 100644 --- a/tests/api/features/PlatformTopology.feature +++ b/tests/api/features/PlatformTopology.feature @@ -84,14 +84,14 @@ Feature: { "name": "inconsistent_address", "type": "poller", - "address": "666.", + "address": "666_", "parent_address": "127.0.0.1" } """ Then the response code should be "400" And the response should be equal to: """ - {"message":"The address '666.' of 'inconsistent_address' is not valid or not resolvable"} + {"message":"The address '666_' of 'inconsistent_address' is not valid or not resolvable"} """ # Register a platform using name with illegal characters / Should fail and an error should be returned @@ -150,14 +150,14 @@ Feature: "name": "inconsistent_parent_address", "type": "poller", "address": "6.6.6.1", - "parent_address": "666.", + "parent_address": "666_", "hostname": "poller.test.localhost.localdomain" } """ Then the response code should be "400" And the response should be equal to: """ - {"message":"The address '666.' of 'inconsistent_parent_address' is not valid or not resolvable"} + {"message":"The address '666_' of 'inconsistent_parent_address' is not valid or not resolvable"} """ # Register a poller linked to the Central. diff --git a/www/api/class/centreon_configuration_poller.class.php b/www/api/class/centreon_configuration_poller.class.php index d2b33623d06..f131d1eb186 100644 --- a/www/api/class/centreon_configuration_poller.class.php +++ b/www/api/class/centreon_configuration_poller.class.php @@ -81,14 +81,14 @@ public function getList() if (isset($this->arguments['t'])) { if ($this->arguments['t'] == 'remote') { - $queryPoller .= "JOIN remote_servers rs ON (ns.ns_ip_address = rs.ip) "; + $queryPoller .= "JOIN remote_servers rs ON ns.id = rs.server_id "; // Exclude selected master Remote Server if (isset($this->arguments['e'])) { $queryPoller .= 'WHERE ns.id <> :masterId '; $queryValues['masterId'] = (int)$this->arguments['e']; } } elseif ($this->arguments['t'] == 'poller') { - $queryPoller .= "LEFT JOIN remote_servers rs ON (ns.ns_ip_address = rs.ip) " + $queryPoller .= "LEFT JOIN remote_servers rs ON ns.id = rs.server_id " . "WHERE rs.ip IS NULL " . "AND ns.localhost = '0' "; } elseif ($this->arguments['t'] == 'central') { diff --git a/www/class/centreonStatistics.class.php b/www/class/centreonStatistics.class.php index 9bb2612f959..50a8eb332df 100644 --- a/www/class/centreonStatistics.class.php +++ b/www/class/centreonStatistics.class.php @@ -87,7 +87,7 @@ public function getPlatformInfo() "(SELECT COUNT(sg.sg_id) FROM servicegroup sg " . "WHERE sg.sg_activate = '1') as nb_sg, " . "@nb_remotes:=(SELECT COUNT(ns.id) FROM nagios_server ns, remote_servers rs WHERE ns.ns_activate = '1' " . - "AND rs.ip = ns.ns_ip_address) as nb_remotes , " . + "AND rs.server_id = ns.id) as nb_remotes , " . "((SELECT COUNT(ns2.id) FROM nagios_server ns2 WHERE ns2.ns_activate = '1')-@nb_remotes-1) as nb_pollers," . " '1' as nb_central " . "FROM host h WHERE h.host_activate = '1' AND h.host_register = '1'"; diff --git a/www/include/configuration/configServers/DB-Func.php b/www/include/configuration/configServers/DB-Func.php index 5e800693fc7..1a2a39c0190 100644 --- a/www/include/configuration/configServers/DB-Func.php +++ b/www/include/configuration/configServers/DB-Func.php @@ -285,17 +285,17 @@ function deleteServerInDB(array $serverIds): void // Is a Remote Server? $statement = $pearDB->prepare( - 'SELECT * FROM remote_servers WHERE ip = :ip' + 'SELECT * FROM remote_servers WHERE server_id = :id' ); - $statement->bindValue(':ip', $row['ip'], \PDO::PARAM_STR); + $statement->bindValue(':id', $serverId, \PDO::PARAM_INT); $statement->execute(); if ($statement->rowCount() > 0) { // Delete entry from remote_servers $statement = $pearDB->prepare( - 'DELETE FROM remote_servers WHERE ip = :ip' + 'DELETE FROM remote_servers WHERE server_id = :id' ); - $statement->bindValue(':ip', $row['ip'], \PDO::PARAM_STR); + $statement->bindValue(':id', $serverId, \PDO::PARAM_INT); $statement->execute(); // Delete all relation between this Remote Server and pollers $pearDB->query( @@ -437,6 +437,8 @@ function duplicateServer(array $server, array $nbrDup): void $statement->bindValue(':poller_id', (int) $row['id'], \PDO::PARAM_INT); $statement->bindValue(':b_poller_id', (int) $serverId, \PDO::PARAM_INT); $statement->execute(); + + duplicateRemoteServerInformation((int) $serverId, (int) $row['id']); } } catch (\PDOException $e) { // Nothing to do @@ -768,14 +770,14 @@ function addUserRessource(int $serverId): bool * Update Remote Server information * * @param array $data - * @param string|null $oldIpAddress Old IP address of the server before the upgrade + * @param int $id remote server id */ -function updateRemoteServerInformation(array $data, string $oldIpAddress = null) +function updateRemoteServerInformation(array $data, int $id) { global $pearDB; - $statement = $pearDB->prepare("SELECT COUNT(*) AS total FROM remote_servers WHERE ip = :ip"); - $statement->bindValue(':ip', $oldIpAddress ?? $data["ns_ip_address"]); + $statement = $pearDB->prepare("SELECT COUNT(*) AS total FROM remote_servers WHERE server_id = :id"); + $statement->bindValue(':id', $id, \PDO::PARAM_INT); $statement->execute(); $total = (int) $statement->fetch(\PDO::FETCH_ASSOC)['total']; @@ -784,14 +786,14 @@ function updateRemoteServerInformation(array $data, string $oldIpAddress = null) UPDATE remote_servers SET http_method = :http_method, http_port = :http_port, no_check_certificate = :no_check_certificate, no_proxy = :no_proxy, ip = :new_ip - WHERE ip = :ip + WHERE server_id = :id "); $statement->bindValue(':http_method', $data["http_method"]); $statement->bindValue(':http_port', $data["http_port"] ?? null, \PDO::PARAM_INT); $statement->bindValue(':no_proxy', $data["no_proxy"]["no_proxy"]); $statement->bindValue(':no_check_certificate', $data["no_check_certificate"]["no_check_certificate"]); $statement->bindValue(':new_ip', $data["ns_ip_address"]); - $statement->bindValue(':ip', $oldIpAddress ?? $data["ns_ip_address"]); + $statement->bindValue(':id', $id, \PDO::PARAM_INT); $statement->execute(); } } @@ -1011,13 +1013,13 @@ function updateServer(int $id, array $data): void $stmt->bindValue($key, $value); } $stmt->execute(); + + updateRemoteServerInformation($data, $id); try { updateServerIntoPlatformTopology($retValue, $id); } catch (\Exception $e) { // catch exception but don't return anything to avoid blank pages on form } - - updateRemoteServerInformation($data, $ipAddressBeforeChanges); additionnalRemoteServersByPollerId( $id, $data["remote_additional_id"] ?? null @@ -1348,8 +1350,8 @@ function updateServerIntoPlatformTopology(array $pollerInformations, int $server /** * Check if we are updating a Remote Server */ - $statement = $pearDB->prepare("SELECT * FROM remote_servers WHERE ip = :address"); - $statement->bindValue(':address', $pollerIp, \PDO::PARAM_STR); + $statement = $pearDB->prepare("SELECT 1 FROM remote_servers WHERE server_id = :id"); + $statement->bindValue(':id', $serverId, \PDO::PARAM_INT); $statement->execute(); $isRemote = $statement->fetch(\PDO::FETCH_ASSOC); if ($isRemote) { @@ -1496,3 +1498,73 @@ function ipCanBeUpdated(array $options): bool } return true; } + +/** + * Get Remote servers information + * + * @param integer $serverId + * @return array + */ +function getRemoteServerInformation(int $serverId): array +{ + global $pearDB; + + $statement = $pearDB->prepare("SELECT * FROM remote_servers WHERE server_id = :id LIMIT 1"); + $statement->bindValue(':id', $serverId, \PDO::PARAM_INT); + $statement->execute(); + if (($result = $statement->fetch(\PDO::FETCH_ASSOC)) !== false) { + return $result; + } + + return []; +} + +/** + * Duplicate information for remote server + * + * @param int $duplicatedId + * @param int $newId + */ +function duplicateRemoteServerInformation(int $duplicatedId, int $newId): void +{ + global $pearDB; + $remoteServerInformation = getRemoteServerInformation($duplicatedId); + if (! empty($remoteServerInformation)) { + $insertRemoteServerStatement = $pearDB->prepare( + "INSERT INTO `remote_servers` (ip, `version`, is_connected, + centreon_path, http_method, http_port, no_check_certificate, no_proxy, server_id) VALUES + (:ip, :version, :isConnected, :centreonPath, :httpMethod, :httpPort, + :noCheckCertificate, :noProxy, :serverId)" + ); + $insertRemoteServerStatement->bindValue(":ip", $remoteServerInformation["ip"], \PDO::PARAM_STR); + $insertRemoteServerStatement->bindValue(":version", $remoteServerInformation["version"], \PDO::PARAM_STR); + $insertRemoteServerStatement->bindValue( + ":isConnected", + (int) $remoteServerInformation["is_connected"], + \PDO::PARAM_INT + ); + $insertRemoteServerStatement->bindValue( + ":centreonPath", + $remoteServerInformation["centreon_path"], + \PDO::PARAM_STR + ); + $insertRemoteServerStatement->bindValue( + ":httpMethod", + $remoteServerInformation["http_method"], + \PDO::PARAM_STR + ); + $insertRemoteServerStatement->bindValue( + ":httpPort", + $remoteServerInformation["http_port"] !== null ? (int) $remoteServerInformation["http_port"] : null, + \PDO::PARAM_INT + ); + $insertRemoteServerStatement->bindValue( + ":noCheckCertificate", + $remoteServerInformation["no_check_certificate"], + \PDO::PARAM_STR + ); + $insertRemoteServerStatement->bindValue(":noProxy", $remoteServerInformation["no_proxy"], \PDO::PARAM_STR); + $insertRemoteServerStatement->bindValue(":serverId", $newId, \PDO::PARAM_INT); + $insertRemoteServerStatement->execute(); + } +} diff --git a/www/include/configuration/configServers/popup/popup.php b/www/include/configuration/configServers/popup/popup.php index de70b9d67d4..acaa3f6ad06 100644 --- a/www/include/configuration/configServers/popup/popup.php +++ b/www/include/configuration/configServers/popup/popup.php @@ -60,19 +60,21 @@ $dbResult = $pearDB->query($query); $remotesServerIPs = $dbResult->fetchAll(PDO::FETCH_COLUMN); $dbResult->closeCursor(); -//get poller informations -$query = " -SELECT ns.`id`, ns.`name`, ns.`gorgone_port`, ns.`ns_ip_address`, ns.`localhost`, ns.remote_id, -remote_server_use_as_proxy, cn.`command_file`, GROUP_CONCAT( pr.`remote_server_id` ) AS list_remote_server_id -FROM nagios_server AS ns - LEFT JOIN remote_servers AS rs ON (rs.ip = ns.ns_ip_address) - LEFT JOIN cfg_nagios AS cn ON (cn.`nagios_id` = ns.`id`) - LEFT JOIN rs_poller_relation AS pr ON (pr.`poller_server_id` = ns.`id`) -WHERE ns.ns_activate = '1' -AND ns.`id` =" . (int)$pollerId; -$dbResult = $pearDB->query($query); -$server = $dbResult->fetch(); +//get poller informations +$statement = $pearDB->prepare( + "SELECT ns.`id`, ns.`name`, ns.`gorgone_port`, ns.`ns_ip_address`, ns.`localhost`, ns.remote_id, + remote_server_use_as_proxy, cn.`command_file`, GROUP_CONCAT( pr.`remote_server_id` ) AS list_remote_server_id + FROM nagios_server AS ns + LEFT JOIN remote_servers AS rs ON rs.server_id = ns.id + LEFT JOIN cfg_nagios AS cn ON cn.`nagios_id` = ns.`id` + LEFT JOIN rs_poller_relation AS pr ON pr.`poller_server_id` = ns.`id` + WHERE ns.ns_activate = '1' + AND ns.`id` = :server_id" +); +$statement->bindValue(':server_id', (int) $pollerId, \PDO::PARAM_INT); +$statement->execute(); +$server = $statement->fetch(); //get gorgone api informations $gorgoneApi = []; diff --git a/www/install/createTables.sql b/www/install/createTables.sql index c72f2449beb..b623f1f6f5a 100644 --- a/www/install/createTables.sql +++ b/www/install/createTables.sql @@ -2329,7 +2329,9 @@ CREATE TABLE IF NOT EXISTS `remote_servers` ( `http_method` enum('http','https') NOT NULL DEFAULT 'http', `http_port` int(11) DEFAULT NULL, `no_check_certificate` enum('0','1') NOT NULL DEFAULT '0', - `no_proxy` enum('0','1') NOT NULL DEFAULT '0' + `no_proxy` enum('0','1') NOT NULL DEFAULT '0', + `server_id` int(11) NOT NULL, + CONSTRAINT `remote_server_nagios_server_ibfk_1` FOREIGN KEY(`server_id`) REFERENCES `nagios_server` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Create rs_poller_relation for the additional relationship between poller and remote servers diff --git a/www/install/insertBaseConf.sql b/www/install/insertBaseConf.sql index 06481bb738f..02dd52d742e 100644 --- a/www/install/insertBaseConf.sql +++ b/www/install/insertBaseConf.sql @@ -2,7 +2,7 @@ -- Insert version -- -INSERT INTO `informations` (`key` ,`value`) VALUES ('version', '22.04.3'); +INSERT INTO `informations` (`key` ,`value`) VALUES ('version', '22.04.5'); -- -- Contenu de la table `contact` diff --git a/www/install/php/Update-22.04.5.php b/www/install/php/Update-22.04.5.php new file mode 100644 index 00000000000..33e05e73c9f --- /dev/null +++ b/www/install/php/Update-22.04.5.php @@ -0,0 +1,102 @@ +isColumnExist('remote_servers', 'server_id') === 0) { + $errorMessage = "Unable to add 'server_id' column to remote_servers table"; + $pearDB->query( + "ALTER TABLE remote_servers + ADD COLUMN `server_id` int(11) NOT NULL" + ); + + migrateRemoteServerRelations($pearDB); + + $errorMessage = "Unable to add foreign key constraint of remote_servers.server_id"; + $pearDB->query( + "ALTER TABLE remote_servers + ADD CONSTRAINT `remote_server_nagios_server_ibfk_1` + FOREIGN KEY(`server_id`) REFERENCES `nagios_server` (`id`) + ON DELETE CASCADE" + ); + } +} catch (\Exception $e) { + $centreonLog->insertLog( + 4, + $versionOfTheUpgrade . $errorMessage . + " - Code : " . (int)$e->getCode() . + " - Error : " . $e->getMessage() . + " - Trace : " . $e->getTraceAsString() + ); + + throw new \Exception($versionOfTheUpgrade . $errorMessage, (int) $e->getCode(), $e); +} + +/** + * Manage relations between remote servers and nagios servers + * + * @param \CentreonDB $pearDB + */ +function migrateRemoteServerRelations(\CentreonDB $pearDB): void +{ + $processedIps = []; + + $selectServerStatement = $pearDB->prepare( + "SELECT id FROM nagios_server WHERE ns_ip_address = :ip_address" + ); + $deleteRemoteStatement = $pearDB->prepare( + "DELETE FROM remote_servers WHERE id = :id" + ); + $updateRemoteStatement = $pearDB->prepare( + "UPDATE remote_servers SET server_id = :server_id WHERE id = :id" + ); + + $result = $pearDB->query( + "SELECT id, ip FROM remote_servers" + ); + while ($remote = $result->fetch()) { + $remoteIp = $remote['ip']; + $remoteId = $remote['id']; + if (in_array($remoteIp, $processedIps)) { + $deleteRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $deleteRemoteStatement->execute(); + } + + $processedIps[] = $remoteIp; + + $selectServerStatement->bindValue(':ip_address', $remoteIp, \PDO::PARAM_STR); + $selectServerStatement->execute(); + if ($server = $selectServerStatement->fetch()) { + $updateRemoteStatement->bindValue(':server_id', $server['id'], \PDO::PARAM_INT); + $updateRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $updateRemoteStatement->execute(); + } else { + $deleteRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $deleteRemoteStatement->execute(); + } + } +} diff --git a/www/install/php/Update-22.10.0-beta.1.php b/www/install/php/Update-22.10.0-beta.1.php new file mode 100644 index 00000000000..e927a0e55d7 --- /dev/null +++ b/www/install/php/Update-22.10.0-beta.1.php @@ -0,0 +1,596 @@ +query("ALTER TABLE cb_field MODIFY description VARCHAR(510) DEFAULT NULL"); + + $pearDB->beginTransaction(); + + $errorMessage = "Unable to delete 'oreon_web_path' option from database"; + $pearDB->query("DELETE FROM `options` WHERE `key` = 'oreon_web_path'"); + + $errorMessage = "Unable to delete 'appKey' information from database"; + $pearDB->query("DELETE FROM `informations` WHERE `key` = 'appKey'"); + + $errorMessage = "Impossible to add new BBDO streams"; + createBbdoStreamConfigurationForms($pearDB); + + $errorMessage = "Impossible to update pollers ACLs"; + updatePollerAcls($pearDB); + + $pearDB->commit(); + + if ($pearDB->isColumnExist('remote_servers', 'app_key') === 1) { + $errorMessage = "Unable to drop 'app_key' from remote_servers table"; + $pearDB->query("ALTER TABLE remote_servers DROP COLUMN `app_key`"); + } + + if ($pearDB->isColumnExist('remote_servers', 'server_id') === 0) { + $errorMessage = "Unable to add 'server_id' column to remote_servers table"; + $pearDB->query( + "ALTER TABLE remote_servers + ADD COLUMN `server_id` int(11) NOT NULL" + ); + + migrateRemoteServerRelations($pearDB); + + $errorMessage = "Unable to add foreign key constraint of remote_servers.server_id"; + $pearDB->query( + "ALTER TABLE remote_servers + ADD CONSTRAINT `remote_server_nagios_server_ibfk_1` + FOREIGN KEY(`server_id`) REFERENCES `nagios_server` (`id`) + ON DELETE CASCADE" + ); + } +} catch (\Exception $e) { + if ($pearDB->inTransaction()) { + $pearDB->rollBack(); + } + + $centreonLog->insertLog( + 4, + $versionOfTheUpgrade . $errorMessage . + " - Code : " . (int)$e->getCode() . + " - Error : " . $e->getMessage() . + " - Trace : " . $e->getTraceAsString() + ); + + throw new \Exception($versionOfTheUpgrade . $errorMessage, (int) $e->getCode(), $e); +} + +/** + * Manage relations between remote servers and nagios servers + * + * @param \CentreonDB $pearDB + */ +function migrateRemoteServerRelations(\CentreonDB $pearDB): void +{ + $processedIps = []; + + $selectServerStatement = $pearDB->prepare( + "SELECT id FROM nagios_server WHERE ns_ip_address = :ip_address" + ); + $deleteRemoteStatement = $pearDB->prepare( + "DELETE FROM remote_servers WHERE id = :id" + ); + $updateRemoteStatement = $pearDB->prepare( + "UPDATE remote_servers SET server_id = :server_id WHERE id = :id" + ); + + $result = $pearDB->query( + "SELECT id, ip FROM remote_servers" + ); + while ($remote = $result->fetch()) { + $remoteIp = $remote['ip']; + $remoteId = $remote['id']; + if (in_array($remoteIp, $processedIps)) { + $deleteRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $deleteRemoteStatement->execute(); + } + + $processedIps[] = $remoteIp; + + $selectServerStatement->bindValue(':ip_address', $remoteIp, \PDO::PARAM_STR); + $selectServerStatement->execute(); + if ($server = $selectServerStatement->fetch()) { + $updateRemoteStatement->bindValue(':server_id', $server['id'], \PDO::PARAM_INT); + $updateRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $updateRemoteStatement->execute(); + } else { + $deleteRemoteStatement->bindValue(':id', $remoteId, \PDO::PARAM_INT); + $deleteRemoteStatement->execute(); + } + } +} + +/** + * @param CentreonDB $pearDB + * @throws \Exception + */ +function updatePollerAcls(CentreonDB $pearDB): void +{ + $stmt = $pearDB->query( + "SELECT topology_id FROM topology WHERE topology_page = 60901" + ); + $pollersTopologyId = $stmt->fetch(); + if ($pollersTopologyId === false) { + return; + } + $pollersTopologyId = (int) $pollersTopologyId['topology_id']; + + updatePollerActionsAcls($pearDB, $pollersTopologyId); + updatePollerMenusAcls($pearDB, $pollersTopologyId); +} + +/** + * @param CentreonDB $pearDB + * @param int $topologyId + * @throws \Exception + */ +function updatePollerMenusAcls(CentreonDB $pearDB, int $topologyId): void +{ + $stmt = $pearDB->prepare( + "UPDATE acl_topology_relations SET access_right = '1' + WHERE access_right = '2' AND topology_topology_id = :topologyId" + ); + $stmt->bindValue(':topologyId', $topologyId, \PDO::PARAM_INT); + $stmt->execute(); + + $stmt = $pearDB->prepare("UPDATE topology SET readonly = '1' WHERE topology_id = :topologyId"); + $stmt->bindValue(':topologyId', $topologyId, \PDO::PARAM_INT); + $stmt->execute(); +} + +/** + * @param CentreonDB $pearDB + * @param int $topologyId + * @throws \Exception + */ +function updatePollerActionsAcls(CentreonDB $pearDB, int $topologyId): void +{ + // Get ACL action ids linked to pollers page with read/write access + $stmt = $pearDB->prepare( + "SELECT DISTINCT(gar.acl_action_id) FROM acl_group_actions_relations gar + JOIN acl_group_topology_relations gtr ON gar.acl_group_id = gtr.acl_group_id + JOIN acl_topology_relations tr ON tr.acl_topo_id = gtr.acl_topology_id + WHERE tr.topology_topology_id = :topologyId AND tr.access_right = '1'" + ); + $stmt->bindValue(':topologyId', $topologyId, \PDO::PARAM_INT); + $stmt->execute(); + + $actionIdsToUpdate = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0); + if (empty($actionIdsToUpdate)) { + return; + } + + // Get ACL action ids linked to pollers page without read/write access + $stmt = $pearDB->prepare( + "SELECT DISTINCT(gar.acl_action_id) FROM acl_group_actions_relations gar + JOIN acl_group_topology_relations gtr ON gar.acl_group_id = gtr.acl_group_id + WHERE gtr.acl_topology_id NOT IN ( + SELECT acl_topo_id FROM acl_topology_relations + WHERE topology_topology_id = :topologyId AND access_right = '1' + )" + ); + $stmt->bindValue(':topologyId', $topologyId, \PDO::PARAM_INT); + $stmt->execute(); + + $actionIdsToExclude = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0); + + foreach ($actionIdsToUpdate as $actionId) { + /** + * Do not update ACL action linked to write AND read only / none pollers page access + * so the most restrictive access wins + */ + if (in_array($actionId, $actionIdsToExclude)) { + continue; + } + + $stmt = $pearDB->prepare( + "INSERT INTO acl_actions_rules (acl_action_rule_id, acl_action_name) VALUES + (:actionId, 'create_edit_poller_cfg'), (:actionId, 'delete_poller_cfg')" + ); + $stmt->bindValue(':actionId', $actionId); + $stmt->execute(); + } +} + +/** + * @param CentreonDb $pearDB + */ +function createBbdoStreamConfigurationForms(CentreonDb $pearDB): void +{ + $streams = insertStreams($pearDB); + $fields = getFieldsDetails(); + + $tagTypeRelationStmt = $pearDB->prepare('INSERT INTO cb_tag_type_relation VALUES (1, :typeId, 0), (2, :typeId, 0)'); + + foreach ($streams as $id => $name) { + $tagTypeRelationStmt->bindValue(':typeId', $id, \PDO::PARAM_INT); + $tagTypeRelationStmt->execute(); + + $fields[$name] = insertFields($pearDB, $fields[$name]); + linkFieldsToStreamType($pearDB, $id, $fields[$name]); + } +} + +/** + * @param CentreonDB $pearDB + * @param int $streamTypeId + * @param array $fields + */ +function linkFieldsToStreamType(CentreonDB $pearDB, int $streamTypeId, array $fields): void +{ + $typeFieldRelationStmt = $pearDB->prepare( + 'INSERT INTO cb_type_field_relation + (cb_type_id, cb_field_id, is_required, order_display, jshook_name, jshook_arguments) + VALUES (:typeId, :fieldId, :isRequired, :orderDisplay, :jshook_name, :jshook_arguments)' + ); + + foreach ($fields as $key => $field) { + $typeFieldRelationStmt->bindValue(':typeId', $streamTypeId, \PDO::PARAM_INT); + $typeFieldRelationStmt->bindValue(':fieldId', $field['id'], \PDO::PARAM_INT); + $typeFieldRelationStmt->bindValue(':isRequired', $field['isRequired'], \PDO::PARAM_STR); + $typeFieldRelationStmt->bindValue(':orderDisplay', $key, \PDO::PARAM_STR); + $typeFieldRelationStmt->bindValue(':jshook_name', $field['jsHook'] ?? null, \PDO::PARAM_STR); + $typeFieldRelationStmt->bindValue(':jshook_arguments', $field['jsArguments'] ?? null, \PDO::PARAM_STR); + $typeFieldRelationStmt->execute(); + } +} + +/** + * @param CentreonDB $pearDB + * @return array + */ +function insertStreams(CentreonDB $pearDB): array +{ + $pearDB->query("INSERT INTO cb_module VALUES (NULL, 'BBDO', NULL, NULL, 0, 1)"); + $moduleId = $pearDB->lastInsertId(); + + $stmt = $pearDB->prepare( + "INSERT INTO cb_type (type_name, type_shortname, cb_module_id) VALUES + ('BBDO Server', 'bbdo_server', :moduleId), + ('BBDO Client', 'bbdo_client', :moduleId)" + ); + $stmt->bindValue(':moduleId', $moduleId, \PDO::PARAM_INT); + $stmt->execute(); + + $stmt = $pearDB->query( + "SELECT cb_type_id, type_shortname FROM cb_type WHERE type_shortname in ('bbdo_server', 'bbdo_client')" + ); + + return $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); +} + +/** + * @param CentreonDB $pearDB + * @param array $fields + * @return array + */ +function insertFields(CentreonDB $pearDB, array $fields): array +{ + $fieldStmt = $pearDB->prepare( + "INSERT INTO cb_field (fieldname, displayname, fieldtype, description) VALUES + (:fieldname, :displayname, :fieldtype, :description)" + ); + + foreach ($fields as &$field) { + $fieldStmt->bindValue(':fieldname', $field['fieldname'], \PDO::PARAM_STR); + $fieldStmt->bindValue(':displayname', $field['displayname'], \PDO::PARAM_STR); + $fieldStmt->bindValue(':fieldtype', $field['fieldtype'], \PDO::PARAM_STR); + $fieldStmt->bindValue(':description', $field['description'], \PDO::PARAM_STR); + $fieldStmt->execute(); + + $field['id'] = $pearDB->lastInsertId(); + + if (in_array($field['fieldtype'], ['radio', 'multiselect'])) { + insertFieldOptions($pearDB, $field); + } + } + + return $fields; +} + +/** + * @param CentreonDB $pearDB + * @param array $field + * @throws \Exception + */ +function insertFieldOptions(CentreonDB $pearDB, array $field): void +{ + if (in_array($field['fieldname'], ['encryption', 'compression', 'retention'])) { + $field['optionListId'] = findListIdByFieldname($pearDB, 'config'); + } else { + $field['optionListId'] = findListIdByFieldname($pearDB, $field['fieldname']); + } + + $fieldOptionsStmt = $pearDB->prepare( + "INSERT INTO cb_list (cb_list_id, cb_field_id, default_value) VALUES (:listId, :fieldId, :defaultValue)" + ); + $fieldOptionsStmt->bindValue(':listId', $field['optionListId'], \PDO::PARAM_INT); + $fieldOptionsStmt->bindValue(':fieldId', $field['id'], \PDO::PARAM_INT); + $fieldOptionsStmt->bindValue(':defaultValue', $field['defaultValue'], \PDO::PARAM_STR); + $fieldOptionsStmt->execute(); + + if ($field['fieldname'] === 'transport_protocol') { + insertGrpcListOptions($pearDB, $field['optionListId']); + } +} + +/** + * Retrieve a list id based on an existing field name already attached to it + * + * @param CentreonDB $pearDB + * @param string $fieldname + * @return int + * @throws \Exception + */ +function findListIdByFieldname(CentreonDB $pearDB, string $fieldname): int +{ + $stmt = $pearDB->prepare( + "SELECT l.cb_list_id FROM cb_list l, cb_field f + WHERE l.cb_field_id = f.cb_field_id AND f.fieldname = :fieldname" + ); + $stmt->bindValue(':fieldname', $fieldname, \PDO::PARAM_STR); + $stmt->execute(); + + $listId = $stmt->fetchColumn(); + + if ($listId === false) { + if ($fieldname === 'transport_protocol') { + $stmt = $pearDB->query("SELECT MAX(cb_list_id) FROM cb_list_values"); + $maxId = $stmt->fetchColumn(); + if ($maxId === false) { + throw new Exception("Cannot find biggest cb_list_id in cb_list_values table"); + } + $listId = $maxId + 1; + } else { + throw new Exception("Cannot find cb_list_id in cb_list_values table"); + } + } + return $listId; +} + +/** + * @param CentreonDB $pearDB + * @param int $listId + * @return int id of the newly created list + * @throws \Exception + */ +function insertGrpcListOptions(CentreonDB $pearDB, int $listId): void +{ + $stmt = $pearDB->prepare("SELECT 1 FROM cb_list_values where cb_list_id = :listId"); + $stmt->bindValue(':listId', $listId, \PDO::PARAM_INT); + $stmt->execute(); + + $doesListExist = $stmt->fetchColumn(); + if ($doesListExist) { + return; + } + + $insertStmt = $pearDB->prepare( + "INSERT INTO cb_list_values VALUES (:listId, 'gRPC', 'gRPC'), (:listId, 'TCP', 'TCP')" + ); + $insertStmt->bindValue(':listId', $listId, \PDO::PARAM_INT); + $insertStmt->execute(); +} + +/** + * @return array{bbdo_server:,bbdo_client:} + */ +function getFieldsDetails(): array +{ + $bbdoServer = [ + [ + "fieldname" => 'host', + "displayname" => 'Listening address (optional)', + "fieldtype" => 'text', + "description" => 'Fill in this field only if you want to specify the address on which Broker ' + . 'should listen', + "isRequired" => 0 + ], + [ + "fieldname" => 'port', + "displayname" => 'Listening port', + "fieldtype" => 'text', + "description" => 'TCP port on which Broker should listen', + "isRequired" => 1 + ], + [ + "fieldname" => 'transport_protocol', + "displayname" => 'Transport protocol', + "fieldtype" => 'radio', + "description" => 'The transport protocol can be either TCP (binary flow over TCP) or gRPC (HTTP2)', + "isRequired" => 1, + "defaultValue" => 'gRPC', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"target": "authorization", "value": "gRPC"}', + ], + [ + "fieldname" => 'authorization', + "displayname" => 'Authorization token (optional)', + "fieldtype" => 'password', + "description" => 'Authorization token to be requested from the client (must be the same for both client ' + . 'and server)', + "isRequired" => 0, + ], + [ + "fieldname" => 'encryption', + "displayname" => 'Enable TLS encryption', + "fieldtype" => 'radio', + "description" => 'Enable TLS 1.3 encryption', + "isRequired" => 1, + "defaultValue" => 'no', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"target": ["private_key", "certificate"], "value": "yes"}', + ], + [ + "fieldname" => 'private_key', + "displayname" => 'Private key path', + "fieldtype" => 'text', + "description" => 'Full path to the file containing the private key in PEM format (required for encryption)', + "isRequired" => 0, + ], + [ + "fieldname" => 'certificate', + "displayname" => 'Certificate path', + "fieldtype" => 'text', + "description" => 'Full path to the file containing the certificate in PEM format (required for encryption)', + "isRequired" => 0, + ], + [ + "fieldname" => 'compression', + "displayname" => 'Compression', + "fieldtype" => 'radio', + "description" => 'Enable data compression', + "isRequired" => 1, + "defaultValue" => 'no', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"tag": "output"}', + ], + [ + "fieldname" => 'retention', + "displayname" => 'Enable retention', + "fieldtype" => 'radio', + "description" => 'Enable data retention until the client is connected', + "isRequired" => 1, + "defaultValue" => 'no', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"tag": "output"}', + ], + [ + "fieldname" => 'category', + "displayname" => 'Filter on event categories', + "fieldtype" => 'multiselect', + "description" => 'Broker event categories to filter. If none is selected, all categories of events will ' + . 'be processed', + "isRequired" => 0, + "defaultValue" => null, + "optionListId" => null, + ], + ]; + + $bbdoClient = [ + [ + "fieldname" => 'host', + "displayname" => 'Server address', + "fieldtype" => 'text', + "description" => 'Address of the server to which the client should connect', + "isRequired" => 1, + ], + [ + "fieldname" => 'port', + "displayname" => 'Server port', + "fieldtype" => 'int', + "description" => 'TCP port of the server to which the client should connect', + "isRequired" => 1, + ], + [ + "fieldname" => 'retry_interval', + "displayname" => 'Retry interval (seconds)', + "fieldtype" => 'int', + "description" => 'Number of seconds between a lost or failed connection and the next try', + "isRequired" => 0, + ], + [ + "fieldname" => 'transport_protocol', + "displayname" => 'Transport protocol', + "fieldtype" => 'radio', + "description" => 'The transport protocol can be either TCP (binary flow over TCP) or gRPC (HTTP2)', + "isRequired" => 1, + "defaultValue" => 'gRPC', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"target": "authorization", "value": "gRPC"}', + ], + [ + "fieldname" => 'authorization', + "displayname" => 'Authorization token (optional)', + "fieldtype" => 'password', + "description" => 'Authorization token expected by the server (must be the same for both client and server)', + "isRequired" => 0, + ], + [ + "fieldname" => 'encryption', + "displayname" => 'Enable TLS encryption', + "fieldtype" => 'radio', + "description" => 'Enable TLS 1.3 encryption', + "isRequired" => 1, + "defaultValue" => 'no', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"target": ["ca_certificate", "ca_name"], "value": "yes"}', + ], + [ + "fieldname" => 'ca_certificate', + "displayname" => 'Trusted CA\'s certificate path (optional)', + "fieldtype" => 'text', + "description" => "If the server's certificate is signed by an untrusted Certification Authority (CA), " + . "then specify the certificate's path.\nIf the server's certificate is self-signed, then specify " + . "its path.\n You can also add the certificate to the store of certificates trusted by the operating " + . "system.\nThe file must be in PEM format.", + "isRequired" => 0, + ], + [ + "fieldname" => 'ca_name', + "displayname" => 'Certificate Common Name (optional)', + "fieldtype" => 'text', + "description" => "If the Common Name (CN) of the certificate is different from the value in the " + . "\"Server address\" field, the CN must be provided here", + "isRequired" => 0, + ], + [ + "fieldname" => 'compression', + "displayname" => 'Compression', + "fieldtype" => 'radio', + "description" => 'Enable data compression', + "isRequired" => 1, + "defaultValue" => 'no', + "optionListId" => null, + "jsHook" => 'bbdoStreams', + "jsArguments" => '{"tag": "output"}', + ], + [ + "fieldname" => 'category', + "displayname" => 'Filter on event categories', + "fieldtype" => 'multiselect', + "description" => 'Broker event categories to filter. If none is selected, all categories of events will ' + . 'be processed', + "isRequired" => 0, + "defaultValue" => null, + "optionListId" => 6, + ], + ]; + + return ['bbdo_server' => $bbdoServer, 'bbdo_client' => $bbdoClient]; +}