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