1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * Class containing methods for operations with configuration. 24 */ 25class CConfiguration extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'export' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'import' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 30 'importcompare' => ['min_user_type' => USER_TYPE_ZABBIX_USER] 31 ]; 32 33 /** 34 * @param array $params 35 * 36 * @return string 37 */ 38 public function export(array $params) { 39 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 40 'format' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [CExportWriterFactory::YAML, CExportWriterFactory::XML, CExportWriterFactory::JSON, CExportWriterFactory::RAW])], 41 'prettyprint' => ['type' => API_BOOLEAN, 'default' => false], 42 'options' => ['type' => API_OBJECT, 'flags' => API_REQUIRED, 'fields' => [ 43 'groups' => ['type' => API_IDS], 44 'hosts' => ['type' => API_IDS], 45 'images' => ['type' => API_IDS], 46 'maps' => ['type' => API_IDS], 47 'mediaTypes' => ['type' => API_IDS], 48 'templates' => ['type' => API_IDS] 49 ]] 50 ]]; 51 if (!CApiInputValidator::validate($api_input_rules, $params, '/', $error)) { 52 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 53 } 54 55 if ($params['format'] === CExportWriterFactory::XML) { 56 $lib_xml = (new CFrontendSetup())->checkPhpLibxml(); 57 58 if ($lib_xml['result'] == CFrontendSetup::CHECK_FATAL) { 59 self::exception(ZBX_API_ERROR_INTERNAL, $lib_xml['error']); 60 } 61 62 $xml_writer = (new CFrontendSetup())->checkPhpXmlWriter(); 63 64 if ($xml_writer['result'] == CFrontendSetup::CHECK_FATAL) { 65 self::exception(ZBX_API_ERROR_INTERNAL, $xml_writer['error']); 66 } 67 } 68 69 $export = new CConfigurationExport($params['options']); 70 $export->setBuilder(new CConfigurationExportBuilder()); 71 $writer = CExportWriterFactory::getWriter($params['format']); 72 $writer->formatOutput($params['prettyprint']); 73 $export->setWriter($writer); 74 75 $export_data = $export->export(); 76 77 if ($export_data === false) { 78 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 79 } 80 81 return $export_data; 82 } 83 84 /** 85 * Validate input parameters for import() and importcompare() methods. 86 * 87 * @param type $params 88 * 89 * @throws APIException if the input is invalid. 90 */ 91 protected function validateImport($params): void { 92 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 93 'format' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [CImportReaderFactory::YAML, CImportReaderFactory::XML, CImportReaderFactory::JSON])], 94 'source' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED], 95 'rules' => ['type' => API_OBJECT, 'flags' => API_REQUIRED, 'fields' => [ 96 'discoveryRules' => ['type' => API_OBJECT, 'fields' => [ 97 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 98 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 99 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 100 ]], 101 'graphs' => ['type' => API_OBJECT, 'fields' => [ 102 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 103 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 104 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 105 ]], 106 'groups' => ['type' => API_OBJECT, 'fields' => [ 107 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 108 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 109 ]], 110 'hosts' => ['type' => API_OBJECT, 'fields' => [ 111 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 112 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 113 ]], 114 'httptests' => ['type' => API_OBJECT, 'fields' => [ 115 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 116 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 117 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 118 ]], 119 'images' => ['type' => API_OBJECT, 'fields' => [ 120 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 121 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 122 ]], 123 'items' => ['type' => API_OBJECT, 'fields' => [ 124 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 125 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 126 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 127 ]], 128 'maps' => ['type' => API_OBJECT, 'fields' => [ 129 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 130 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 131 ]], 132 'mediaTypes' => ['type' => API_OBJECT, 'fields' => [ 133 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 134 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 135 ]], 136 'templateLinkage' => ['type' => API_OBJECT, 'fields' => [ 137 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 138 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 139 ]], 140 'templates' => ['type' => API_OBJECT, 'fields' => [ 141 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 142 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false] 143 ]], 144 'templateDashboards' => ['type' => API_OBJECT, 'fields' => [ 145 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 146 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 147 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 148 ]], 149 'triggers' => ['type' => API_OBJECT, 'fields' => [ 150 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 151 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 152 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 153 ]], 154 'valueMaps' => ['type' => API_OBJECT, 'fields' => [ 155 'createMissing' => ['type' => API_BOOLEAN, 'default' => false], 156 'updateExisting' => ['type' => API_BOOLEAN, 'default' => false], 157 'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false] 158 ]] 159 ]] 160 ]]; 161 if (!CApiInputValidator::validate($api_input_rules, $params, '/', $error)) { 162 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 163 } 164 165 if (array_key_exists('maps', $params['rules']) && !self::checkAccess(CRoleHelper::ACTIONS_EDIT_MAPS) 166 && ($params['rules']['maps']['createMissing'] || $params['rules']['maps']['updateExisting'])) { 167 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'rules', 168 _('no permissions to create and edit maps') 169 )); 170 } 171 172 if ($params['format'] === CImportReaderFactory::XML) { 173 $lib_xml = (new CFrontendSetup())->checkPhpLibxml(); 174 175 if ($lib_xml['result'] == CFrontendSetup::CHECK_FATAL) { 176 self::exception(ZBX_API_ERROR_INTERNAL, $lib_xml['error']); 177 } 178 179 $xml_reader = (new CFrontendSetup())->checkPhpXmlReader(); 180 181 if ($xml_reader['result'] == CFrontendSetup::CHECK_FATAL) { 182 self::exception(ZBX_API_ERROR_INTERNAL, $xml_reader['error']); 183 } 184 } 185 } 186 187 /** 188 * @param array $params 189 * 190 * @return bool 191 */ 192 public function import($params) { 193 $this->validateImport($params); 194 195 $import_reader = CImportReaderFactory::getReader($params['format']); 196 $data = $import_reader->read($params['source']); 197 198 $import_validator_factory = new CImportValidatorFactory($params['format']); 199 $import_converter_factory = new CImportConverterFactory(); 200 201 $validator = new CXmlValidator($import_validator_factory, $params['format']); 202 203 $data = $validator 204 ->setStrict(true) 205 ->validate($data, '/'); 206 207 foreach (['1.0', '2.0', '3.0', '3.2', '3.4', '4.0', '4.2', '4.4', '5.0', '5.2'] as $version) { 208 if ($data['zabbix_export']['version'] !== $version) { 209 continue; 210 } 211 212 $data = $import_converter_factory 213 ->getObject($version) 214 ->convert($data); 215 216 $data = $validator 217 // Must not use XML_INDEXED_ARRAY key validaiton for the converted data. 218 ->setStrict(false) 219 ->validate($data, '/'); 220 } 221 222 // Get schema for converters. 223 $schema = $import_validator_factory 224 ->getObject(ZABBIX_EXPORT_VERSION) 225 ->getSchema(); 226 227 // Convert human readable import constants to values Zabbix API can work with. 228 $data = (new CConstantImportConverter($schema))->convert($data); 229 230 // Add default values in place of missed tags. 231 $data = (new CDefaultImportConverter($schema))->convert($data); 232 233 // Normalize array keys and strings. 234 $data = (new CImportDataNormalizer($schema))->normalize($data); 235 236 // Transform converter. 237 $data = (new CTransformImportConverter($schema))->convert($data); 238 239 $adapter = new CImportDataAdapter(); 240 $adapter->load($data); 241 242 $configuration_import = new CConfigurationImport( 243 $params['rules'], 244 new CImportReferencer(), 245 new CImportedObjectContainer() 246 ); 247 248 return $configuration_import->import($adapter); 249 } 250 251 /** 252 * Preview changes that would be done to templates. 253 * 254 * @param array $params Same params, as for import. 255 * 256 * @return array 257 * 258 * @throws APIException 259 * @throws Exception 260 */ 261 public function importcompare(array $params): array { 262 $this->validateImport($params); 263 264 $import_reader = CImportReaderFactory::getReader($params['format']); 265 $data = $import_reader->read($params['source']); 266 267 $import_validator_factory = new CImportValidatorFactory($params['format']); 268 $import_converter_factory = new CImportConverterFactory(); 269 270 $validator = new CXmlValidator($import_validator_factory, $params['format']); 271 272 $data = $validator 273 ->setStrict(true) 274 ->setPreview(true) 275 ->validate($data, '/'); 276 277 foreach (['1.0', '2.0', '3.0', '3.2', '3.4', '4.0', '4.2', '4.4', '5.0', '5.2'] as $version) { 278 if ($data['zabbix_export']['version'] !== $version) { 279 continue; 280 } 281 282 $data = $import_converter_factory 283 ->getObject($version) 284 ->convert($data); 285 286 $data = $validator 287 // Must not use XML_INDEXED_ARRAY key validation for the converted data. 288 ->setStrict(false) 289 ->setPreview(true) 290 ->validate($data, '/'); 291 } 292 293 // Get schema for converters. 294 $schema = $import_validator_factory 295 ->getObject(ZABBIX_EXPORT_VERSION) 296 ->getSchema(); 297 298 // Normalize array keys and strings. 299 $data = (new CImportDataNormalizer($schema))->normalize($data); 300 301 // Transform converter. 302 $data = (new CTransformImportConverter($schema))->convert($data); 303 304 $adapter = new CImportDataAdapter(); 305 $adapter->load($data); 306 307 $import = $adapter->getData(); 308 $imported_uuids = []; 309 foreach (['groups', 'templates'] as $first_level) { 310 if (array_key_exists($first_level, $import)) { 311 $imported_uuids[$first_level] = array_column($import[$first_level], 'uuid'); 312 } 313 } 314 315 $imported_ids = []; 316 foreach ($imported_uuids as $entity => $uuids) { 317 switch ($entity) { 318 case 'groups': 319 $imported_ids['groups'] = API::HostGroup()->get([ 320 'filter' => [ 321 'uuid' => $uuids 322 ], 323 'preservekeys' => true 324 ]); 325 $imported_ids['groups'] = array_keys($imported_ids['groups']); 326 327 break; 328 329 case 'templates': 330 $imported_ids['templates'] = API::Template()->get([ 331 'filter' => [ 332 'uuid' => $uuids 333 ], 334 'preservekeys' => true 335 ]); 336 $imported_ids['templates'] = array_keys($imported_ids['templates']); 337 338 break; 339 340 default: 341 break; 342 } 343 } 344 345 // Get current state of templates in same format, as import to compare this data. 346 $export = API::Configuration()->export([ 347 'format' => CExportWriterFactory::RAW, 348 'prettyprint' => false, 349 'options' => $imported_ids 350 ]); 351 // Normalize array keys and strings. 352 $export = (new CImportDataNormalizer($schema))->normalize($export); 353 $export = $export['zabbix_export']; 354 355 $importcompare = new CConfigurationImportcompare($params['rules']); 356 return $importcompare->importcompare($export, $import); 357 } 358} 359