3 * Bacula(R) - The Network Backup Solution
4 * Baculum - Bacula web interface
6 * Copyright (C) 2013-2016 Kern Sibbald
8 * The main author of Baculum is Marcin Haba.
9 * The original author of Bacula is Kern Sibbald, with contributions
10 * from many others, a complete list can be found in the file AUTHORS.
12 * You may use this file and others of this release according to the
13 * license defined in the LICENSE file, which includes the Affero General
14 * Public License, v3.0 ("AGPLv3") and some additional permissions and
15 * terms pursuant to its AGPLv3 Section 7.
17 * This notice must be preserved when any source code is
18 * conveyed and/or propagated.
20 * Bacula(R) is a registered trademark of Kern Sibbald.
23 Prado::using('Application.Class.Errors');
26 * Internal API client module.
30 class API extends TModule {
33 * API version (used in HTTP header)
35 const API_VERSION = '0.1';
38 * Store configuration data from settings file
45 * These errors are allowed in API response and they do not cause
46 * disturb application working (no direction to error page)
50 private $allowed_errors = array(
51 GenericError::ERROR_NO_ERRORS,
52 BconsoleError::ERROR_INVALID_COMMAND,
53 PoolError::ERROR_NO_VOLUMES_IN_POOL_TO_UPDATE
57 * Get connection request handler.
58 * For data requests is used cURL interface.
61 * @return resource connection handler on success, false on errors
63 public function getConnection() {
65 $userpwd = sprintf('%s:%s', $this->app_cfg['baculum']['login'], $this->app_cfg['baculum']['password']);
66 curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
67 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
68 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
69 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
70 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
71 curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=' . md5(session_id()));
76 * Get API specific headers used in HTTP requests.
79 * @return API specific headers
81 private function getAPIHeaders() {
83 'X-Baculum-API: ' . self::API_VERSION,
84 'X-Baculum-User: ' . $this->Application->User->getName(),
85 'X-Baculum-Pwd: ' . $this->Application->User->getPwd(),
86 'Accept: application/json'
92 * Initializes API module (framework module constructor)
95 * @param TXmlElement $config API module configuration
97 public function init($config) {
98 $this->initSessionCache();
99 $this->app_cfg = $this->Application->getModule('configuration')->getApplicationConfig();
103 * Get URL to use by internal API client's request.
106 * @return string URL to internal API server
108 private function getURL() {
109 $protocol = !empty($_SERVER['HTTPS']) ? 'https' : 'http';
110 $host = $_SERVER['SERVER_NAME'];
111 $port = $_SERVER['SERVER_PORT'];
113 // support for document root subdirectory
114 $urlPrefix = $this->Application->getModule('friendly-url')->getUrlPrefix();
116 $url = sprintf('%s://%s:%d%s/', $protocol, $host, $port, $urlPrefix);
121 * Set URL parameters and prepare URL to request send.
124 * @param string &$url reference to URL string variable
126 private function setUrlParams(&$url) {
127 $url .= (preg_match('/\?/', $url) === 1 ? '&' : '?');
129 if (array_key_exists('director', $_SESSION)) {
130 $url .= $_SESSION['director'];
133 $this->Application->getModule('logging')->log(
135 PHP_EOL . PHP_EOL . 'EXECUTE URL ==> ' . $url . ' <==' . PHP_EOL . PHP_EOL,
136 Logging::CATEGORY_APPLICATION,
143 * Internal API GET request.
146 * @param array $params GET params to send in request
147 * @param bool $use_cache if true then try to use session cache, if false then always use fresh data
148 * @return object stdClass with request result as two properties: 'output' and 'error'
150 public function get(array $params, $use_cache = false) {
153 if ($use_cache === true) {
154 $cached = $this->getSessionCache($params);
156 if (!is_null($cached)) {
159 $url = $this->getURL() . implode('/', $params);
160 $this->setUrlParams($url);
161 $ch = $this->getConnection();
162 curl_setopt($ch, CURLOPT_URL, $url);
163 curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getAPIHeaders());
164 $result = curl_exec($ch);
166 $ret = $this->preParseOutput($result);
167 if ($use_cache === true && $ret->error === 0) {
168 $this->setSessionCache($params, $ret);
175 * Internal API SET request.
178 * @param array $params GET params to send in request
179 * @param array $options POST params to send in request
180 * @return object stdClass with request result as two properties: 'output' and 'error'
182 public function set(array $params, array $options) {
183 $url = $this->getURL() . implode('/', $params);
184 $this->setUrlParams($url);
185 $data = http_build_query(array('update' => $options));
186 $ch = $this->getConnection();
187 curl_setopt($ch, CURLOPT_URL, $url);
188 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
189 curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(
190 $this->getAPIHeaders(),
191 array('X-HTTP-Method-Override: PUT', 'Content-Length: ' . strlen($data), 'Expect:')
193 curl_setopt($ch, CURLOPT_POST, true);
194 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
195 $result = curl_exec($ch);
197 return $this->preParseOutput($result);
201 * Internal API CREATE request.
204 * @param array $params GET params to send in request
205 * @param array $options POST params to send in request
206 * @return object stdClass with request result as two properties: 'output' and 'error'
208 public function create(array $params, array $options) {
209 $url = $this->getURL() . implode('/', $params);
210 $this->setUrlParams($url);
211 $data = http_build_query(array('create' => $options));
212 $ch = $this->getConnection();
213 curl_setopt($ch, CURLOPT_URL, $url);
214 curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($this->getAPIHeaders(), array('Expect:')));
215 curl_setopt($ch, CURLOPT_POST, true);
216 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
217 $result = curl_exec($ch);
219 return $this->preParseOutput($result);
223 * Internal API REMOVE request.
226 * @param array $params GET params to send in request
227 * @return object stdClass with request result as two properties: 'output' and 'error'
229 public function remove(array $params) {
230 $url = $this->getURL() . implode('/', $params);
231 $this->setUrlParams($url);
232 $ch = $this->getConnection();
233 curl_setopt($ch, CURLOPT_URL, $url);
234 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
235 curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($this->getAPIHeaders(), array('X-HTTP-Method-Override: DELETE')));
236 $result = curl_exec($ch);
238 return $this->preParseOutput($result);
242 * Initially parse and prepare every Internal API response.
243 * If a error occurs then redirect to appropriate error page.
246 * @param string $result response output as JSON string (not object yet)
247 * @return object stdClass parsed response with two top level properties 'output' and 'error'
249 private function preParseOutput($result) {
250 // first write log with that what comes
251 $this->Application->getModule('logging')->log(
254 Logging::CATEGORY_APPLICATION,
259 // decode JSON to object
260 $resource = json_decode($result);
264 if(is_object($resource) && property_exists($resource, 'error')) {
265 if(!in_array($resource->error, $this->allowed_errors)) {
266 $error = $resource->error;
269 $error = AuthorizationError::ERROR_AUTHORIZATION_TO_WEBGUI_PROBLEM;
272 $this->Application->getModule('logging')->log(
275 Logging::CATEGORY_APPLICATION,
280 // if other than allowed errors exist then show error page (redirect)
281 if(!is_null($error)) {
282 // Note! Redirection to error page takes place here.
283 $this->Response->redirect(
284 $this->Service->constructUrl(
286 array('error' => $error),
296 * Initialize session cache.
299 * @param bool $force if true then cache is force initialized
302 public function initSessionCache($force = false) {
303 if (!isset($_SESSION) || !array_key_exists('cache', $_SESSION) || !is_array($_SESSION['cache']) || $force === true) {
304 $_SESSION['cache'] = array();
309 * Get session cache value by params.
312 * @param array $params command parameters as numeric array
313 * @return mixed if cache exists then returned is cached data, otherwise null
315 private function getSessionCache(array $params) {
317 $key = $this->getSessionKey($params);
318 if ($this->isSessionValue($key)) {
319 $cached = $_SESSION['cache'][$key];
325 * Save data to session cache.
328 * @param array $params command parameters as numeric array
329 * @param mixed $value value to save in cache
332 private function setSessionCache(array $params, $value) {
333 $key = $this->getSessionKey($params);
334 $_SESSION['cache'][$key] = $value;
338 * Get session key by command parameters.
341 * @param array $params command parameters as numeric array
342 * @return string session key for given command
344 private function getSessionKey(array $params) {
345 $key = implode(';', $params);
346 $key = base64_encode($key);
351 * Check if session key exists in session cache.
354 * @param string $key session key
355 * @return bool true if session key exists, otherwise false
357 private function isSessionValue($key) {
358 $is_value = array_key_exists($key, $_SESSION['cache']);