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 * This class should be used to call API services locally using the CApiService classes.
24 */
25class CLocalApiClient extends CApiClient {
26
27	/**
28	 * Factory for creating API services.
29	 *
30	 * @var CRegistryFactory
31	 */
32	protected $serviceFactory;
33
34	/**
35	 * Whether debug mode is enabled.
36	 *
37	 * @var bool
38	 */
39	protected $debug = false;
40
41	/**
42	 * Set service factory.
43	 *
44	 * @param CRegistryFactory $factory
45	 */
46	public function setServiceFactory(CRegistryFactory $factory) {
47		$this->serviceFactory = $factory;
48	}
49
50	/**
51	 * Call the given API service method and return the response.
52	 *
53	 * @param string 	$requestApi			API name
54	 * @param string 	$requestMethod		API method
55	 * @param array 	$params				API parameters
56	 * @param string	$auth				Authentication token
57	 *
58	 * @return CApiClientResponse
59	 */
60	public function callMethod($requestApi, $requestMethod, array $params, $auth) {
61		global $DB;
62
63		$api = strtolower($requestApi);
64		$method = strtolower($requestMethod);
65
66		$response = new CApiClientResponse();
67
68		// check API
69		if (!$this->isValidApi($api)) {
70			$response->errorCode = ZBX_API_ERROR_NO_METHOD;
71			$response->errorMessage = _s('Incorrect API "%1$s".', $requestApi);
72
73			return $response;
74		}
75
76		// check method
77		if (!$this->isValidMethod($api, $method)) {
78			$response->errorCode = ZBX_API_ERROR_NO_METHOD;
79			$response->errorMessage = _s('Incorrect method "%1$s.%2$s".', $requestApi, $requestMethod);
80
81			return $response;
82		}
83
84		$requiresAuthentication = $this->requiresAuthentication($api, $method);
85
86		// check that no authentication token is passed to methods that don't require it
87		if (!$requiresAuthentication && $auth !== null) {
88			$response->errorCode = ZBX_API_ERROR_PARAMETERS;
89			$response->errorMessage = _s('The "%1$s.%2$s" method must be called without the "auth" parameter.',
90				$requestApi, $requestMethod
91			);
92
93			return $response;
94		}
95
96		$newTransaction = false;
97		try {
98			// authenticate
99			if ($requiresAuthentication) {
100				$this->authenticate($auth);
101			}
102
103			// the nopermission parameter must not be available for external API calls.
104			unset($params['nopermissions']);
105
106			// if no transaction has been started yet - start one
107			if ($DB['TRANSACTIONS'] == 0) {
108				DBstart();
109				$newTransaction = true;
110			}
111
112			// call API method
113			$result = call_user_func_array([$this->serviceFactory->getObject($api), $method], [$params]);
114
115			// if the method was called successfully - commit the transaction
116			if ($newTransaction) {
117				DBend(true);
118			}
119
120			$response->data = $result;
121		}
122		catch (Exception $e) {
123			if ($newTransaction) {
124				// if we're calling user.login and authentication failed - commit the transaction to save the
125				// failed attempt data
126				if ($api === 'user' && $method === 'login') {
127					DBend(true);
128				}
129				// otherwise - revert the transaction
130				else {
131					DBend(false);
132				}
133			}
134
135			$response->errorCode = ($e instanceof APIException) ? $e->getCode() : ZBX_API_ERROR_INTERNAL;
136			$response->errorMessage = $e->getMessage();
137
138			// add debug data
139			if ($this->debug) {
140				$response->debug = $e->getTrace();
141			}
142		}
143
144		return $response;
145	}
146
147	/**
148	 * Checks if the authentication token is valid.
149	 *
150	 * @param string $auth
151	 *
152	 * @throws APIException
153	 */
154	protected function authenticate($auth) {
155		if (zbx_empty($auth)) {
156			throw new APIException(ZBX_API_ERROR_NO_AUTH, _('Not authorised.'));
157		}
158
159		$user = $this->serviceFactory->getObject('user')->checkAuthentication(['sessionid' => $auth]);
160		$this->debug = $user['debug_mode'];
161	}
162
163	/**
164	 * Returns true if the given API is valid.
165	 *
166	 * @param string $api
167	 *
168	 * @return bool
169	 */
170	protected function isValidApi($api) {
171		return $this->serviceFactory->hasObject($api);
172	}
173
174	/**
175	 * Returns true if the given method is valid.
176	 *
177	 * @param string $api
178	 * @param string $method
179	 *
180	 * @return bool
181	 */
182	protected function isValidMethod($api, $method) {
183		$apiService = $this->serviceFactory->getObject($api);
184
185		// validate the method
186		$availableMethods = [];
187		foreach (get_class_methods($apiService) as $serviceMethod) {
188			// the comparison must be case insensitive
189			$availableMethods[strtolower($serviceMethod)] = true;
190		}
191
192		return isset($availableMethods[$method]);
193	}
194
195	/**
196	 * Returns true if calling the given method requires a valid authentication token.
197	 *
198	 * @param $api
199	 * @param $method
200	 *
201	 * @return bool
202	 */
203	protected function requiresAuthentication($api, $method) {
204		return !(($api === 'user' && $method === 'login')
205			|| ($api === 'user' && $method === 'checkauthentication')
206			|| ($api === 'apiinfo' && $method === 'version'));
207	}
208}
209