BAOBAB
co_basalt.class.php
Go to the documentation of this file.
1 <?php
2 /***************************************************************************************************************************/
27 defined( 'LGV_BASALT_CATCHER' ) or die ( 'Cannot Execute Directly' ); // Makes sure that this file is in the correct context.
28 
29 define('__BASALT_VERSION__', '1.1.3.3000');
30 
31 if (!defined('LGV_ACCESS_CATCHER')) {
32  define('LGV_ACCESS_CATCHER', 1);
33 }
34 
35 if ( !defined('LGV_ANDISOL_CATCHER') ) {
36  define('LGV_ANDISOL_CATCHER', 1);
37 }
38 
39 require_once(CO_Config::andisol_main_class_dir().'/co_andisol.class.php');
40 require_once(CO_Config::main_class_dir().'/a_co_basalt_plugin.class.php');
41 
42 if ( !defined('LGV_LANG_CATCHER') ) {
43  define('LGV_LANG_CATCHER', 1);
44 }
45 
46 require_once(CO_Config::lang_class_dir().'/common.inc.php');
47 
48 define('_PLUGIN_NAME_', 'baseline');
49 
50 /****************************************************************************************************************************/
56  protected $_andisol_instance;
57  protected $_path;
58  protected $_vars;
59  protected $_request_type;
66  protected $_response_type;
67  protected $_plugin_selector;
68 
69  var $version;
70  var $error;
71 
72  /************************************************************************************************************************/
73  /*#################################################### INTERNAL METHODS ################################################*/
74  /************************************************************************************************************************/
75 
76  /***********************/
80  protected static function _extract_csv_data( $in_text_data
81  ) {
82  $csv_array = [];
83  $in_text_data = explode("\n", $in_text_data);
84  if (isset($in_text_data) && is_array($in_text_data) && (1 < count($in_text_data))) {
85  $keys = str_getcsv(array_shift($in_text_data));
86  foreach ($in_text_data as $row) {
87  $row_temp = str_getcsv($row);
88  $row = [];
89  foreach ($row_temp as $element) {
90  if (('"NULL"' == $element) || ('NULL' == $element) || ("'NULL'" == $element) || !trim($element)) {
91  $element = NULL;
92  }
93 
94  $row[] = $element;
95  }
96  if (count($row) == count($keys)) {
97  $row = array_combine($keys, $row);
98  $csv_array[] = $row;
99  }
100  }
101  } else {
102  header('HTTP/1.1 400 Invalid Bulk Data');
103  exit();
104  }
105 
106  return $csv_array;
107  }
108 
109  /***********************/
113  protected static function _output_one_line( $in_line,
114  $in_header_row
115  ) {
116  $empty_array = array_fill(0, count($in_header_row), 'NULL');
117  $template = array_combine($in_header_row, $empty_array);
118 
119  // What we do, is go through each component of the line, format it for CSV, then add it to the "template" array.
120  foreach ($in_line as $key => $value) {
121  if (!trim($value) || ('api_key' == $key)) { // We don't back up API keys or empty strings.
122  $value = 'NULL';
123  } else {
124  // Massage for proper CSV format.
125  $needs_quotes = preg_match('|[\s",]|', $value);
126  $value = str_replace("'", "''", $value);
127  $value = str_replace('"', '""', $value);
128  $value = str_replace('\\""', '""', $value);
129  $value = str_replace('\\\\', '\\', $value);
130  if ($needs_quotes) {
131  $value = "\"$value\"";
132  }
133  }
134 
135  $template[$key] = $value;
136  }
137 
138  // Send it out.
139  echo(implode(',', $template)."\n");
140  }
141 
142  /***********************/
149  protected function _process_basalt_parameters() {
150  $paths = isset($_SERVER['PATH_INFO']) ? explode("/", substr($_SERVER['PATH_INFO'], 1)) : [];
151  $query = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : NULL;
152  $path_final = [];
153  $vars_final = [];
154  $this->_path = [];
155  $this->_vars = [];
156  $this->_response_type = NULL;
157  $this->_plugin_selector = NULL;
158 
159  $this->_request_type = strtoupper(trim($_SERVER['REQUEST_METHOD']));
160 
161  // Look to see if we are doing a login. In that case, we only grab a couple of things.
162  if ((1 < count($paths)) || (isset($paths[0]) && (('use_auth_params' == $paths[0]) || ('login' == $paths[0]) || ('logout' == $paths[0])))) { // We need at least the response and plugin types. Login and Logout get special handling.
163  $response_type = strtolower(trim($paths[0]));
164 
165  if ('use_auth_params' == $response_type) {
166  $this->_path = Array('use_auth_params');
167  } elseif ('login' == $response_type) {
168  $query = explode('&', $query);
169  $this->_path = Array('login');
170  if (isset($query) && is_array($query) && (2 == count($query))) {
171  $vars_final = [];
172 
173  foreach ($query as $param) {
174  // Now, see if we have a bunch of parameters.
175  $key = trim($param);
176  $value = NULL;
177 
178  $parts = explode('=', $param, 2);
179  if (1 < count($parts)) {
180  $key = trim($parts[0]);
181  $value = trim($parts[1]);
182  }
183 
184  if ($key) {
185  if (!isset($value) || !$value) {
186  $value = true;
187  }
188 
189  // remember that if we repeat the key, the first value is overwritten by the second (or third, or so on).
190  $vars_final[$key] = $value;
191  }
192  }
193  $this->_vars = $vars_final;
194  } else {
195  header('HTTP/1.1 403 Unauthorized Login');
196  exit();
197  }
198  } elseif ('logout' == $response_type) { // We simply ignore anything else for logout.
199  $this->_path = Array('logout');
200  } else { // We handle the rest
201  // Get the response type.
202  if (('json' == $response_type) || ('xml' == $response_type) || ('xsd' == $response_type) || ('csv' == $response_type)) {
203  array_shift($paths);
204 
205  $this->_response_type = $response_type;
206 
207  $plugin_selector = strtolower(trim($paths[0]));
208 
209  // Make sure that we are calling a valid plugin.
210  if (in_array($plugin_selector, $this->get_plugin_names())) {
211  $vars_final = [];
212 
213  $this->_plugin_selector = $plugin_selector;
214 
215  array_shift($paths);
216 
217  // We now trim the strings in the remaining paths, and make sure that we don't have any empties.
218  $this->_path = array_filter(array_map('trim', $paths), function($i){return '' != $i;});
219 
220  // Next, we examine any query parameters.
221  $query = explode('&', $query);
222 
223  if (isset($query) && is_array($query) && count($query)) {
224  foreach ($query as $param) {
225  // Now, see if we have a bunch of parameters.
226  $key = trim($param);
227  $value = NULL;
228 
229  $parts = explode('=', $param, 2);
230  if (1 < count($parts)) {
231  $key = trim($parts[0]);
232  $value = urldecode(trim($parts[1]));
233  }
234 
235  if ($key) {
236  if (!isset($value)) {
237  $value = true;
238  }
239 
240  // remember that if we repeat the key, the first value is overwritten by the second (or third, or so on).
241  $vars_final[$key] = $value;
242  }
243  }
244  }
245 
246  $file_data = '';
247 
248  if (!isset($vars_final['remove_payload'])) { // If they did not specify a payload, maybe they want one removed?
249  // POST is handled differently from PUT. POST gets proper background handling, while PUT needs a very raw approach.
250  if ('POST' == $this->_request_type) {
251  if (isset($_FILES['payload']) && (!isset($_FILES['payload']['error']) || is_array($_FILES['payload']['error']))) {
252  header('HTTP/1.1 400 '.print_r($_FILES['payload']['error'], true));
253  exit();
254  } elseif (isset($_FILES['payload'])) {
255  $file_data = file_get_contents($_FILES['payload']['tmp_name']);
256  }
257  } elseif ('PUT' == $this->_request_type) {
258  // See if they have sent any data to us via the standard HTTP channel (PUT).
259  $put_data = fopen('php://input', 'r');
260  if (isset($put_data) && $put_data) {
261  while ($data = fread($put_data, 2048)) { // Read it in 2K chunks.
262  $file_data .= $data;
263  }
264  fclose($put_data);
265  }
266  }
267 
268  // This can only go to payload.
269  if (isset($file_data) && $file_data) {
270  $vars_final['payload'] = base64_decode($file_data);
271  } elseif (isset ($vars_final['payload'])) {
272  // See if the payload is already base64.
273  if (base64_encode(base64_decode($vars_final['payload'])) == $vars_final['payload']) {
274  $vars_final['payload'] = base64_decode($vars_final['payload']);
275  }
276  }
277  }
278 
279  $this->_vars = $vars_final;
280  } else {
281  header('HTTP/1.1 400 Unsupported or Missing Plugin');
282  exit();
283  }
284  } else {
285  header('HTTP/1.1 400 Improper Return Type');
286  exit();
287  }
288  }
289  } else {
290  header('HTTP/1.1 400 Missing Path Components');
291  exit();
292  }
293  }
294 
295  /***********************/
301  protected function _process_command() {
302  $header = '';
303  $result = '';
304 
305  if (isset($this->_andisol_instance) && ($this->_andisol_instance instanceof CO_Andisol) && $this->_andisol_instance->valid()) {
306  if ('baseline' == $this->_plugin_selector) {
307  if (('GET' == $this->_request_type) || ('POST' == $this->_request_type)) {
308  $result = $this->process_command($this->_andisol_instance, $this->_request_type, $this->_response_type, $this->_path, $this->_vars);
309  } else {
310  $header = 'HTTP/1.1 400 Incorrect HTTP Request Method';
311  exit();
312  }
313  } else {
314  $plugin_filename = 'co_'.$this->_plugin_selector.'_basalt_plugin.class.php';
315  $plugin_classname = 'CO_'.$this->_plugin_selector.'_Basalt_Plugin';
316  $plugin_dirs = CO_Config::plugin_dirs();
317  $plugin_file = '';
318 
319  foreach ($plugin_dirs as $plugin_dir) {
320  if (isset($plugin_dir) && is_dir($plugin_dir)) {
321  // Iterate through that directory, and get each plugin directory.
322  foreach (new DirectoryIterator($plugin_dir) as $fileInfo) {
323  if ($plugin_filename == $fileInfo->getBasename()) {
324  $plugin_file = $fileInfo->getPathname();
325  break;
326  }
327  }
328  }
329  }
330 
331  if ($plugin_file) {
332  require_once($plugin_file);
333  $plugin_instance = new $plugin_classname();
334  if ($plugin_instance instanceof A_CO_Basalt_Plugin) {
335  $result = $plugin_instance->process_command($this->_andisol_instance, $this->_request_type, $this->_response_type, $this->_path, $this->_vars);
336  } else {
337  header('HTTP/1.1 400 Unsupported or Missing Plugin');
338  exit();
339  }
340  } else {
341  header('HTTP/1.1 400 Unsupported or Missing Plugin');
342  exit();
343  }
344  }
345 
346  switch ($this->_response_type) {
347  case 'xsd':
348  case 'xml':
349  $header .= 'Content-Type: text/xml';
350  break;
351 
352  case 'json':
353  $header .= 'Content-Type: application/json';
354  break;
355 
356  default:
357  $header = 'HTTP/1.1 400 Improper Return Type';
358  $result = '';
359  }
360  } else {
361  if (isset($this->_andisol_instance) && ($this->_andisol_instance instanceof CO_Andisol)) {
362  $this->error = $this->_andisol_instance->error;
363  if (isset($this->error) && ($this->error->error_code == CO_Lang_Common::$login_error_code_api_key_mismatch) || ($this->error->error_code == CO_Lang_Common::$pdo_error_code_invalid_login)) {
364  $header = 'HTTP/1.1 401 Unauthorized API Key';
365  } elseif (isset($this->error) && ($this->error->error_code == CO_Lang_Common::$login_error_code_api_key_invalid)) {
366  $header = 'HTTP/1.1 408 API Key Timeout';
367  } else {
368  $header = 'HTTP/1.1 400 General Error';
369  }
370  } else {
371  $header = 'HTTP/1.1 400 General Error';
372  }
373  }
374 
375  if ($header) {
376  header($header);
377  }
378 
379  $ob_stat = ob_get_status();
380  $use_ob = true;
381  if (is_array($ob_stat) && isset($ob_stat['level']) && 0 < $ob_stat['level']) {
382  $use_ob = false;
383  }
384 
385  if ($use_ob) {
386  $handler = null;
387 
388  if ( zlib_get_coding_type() === false )
389  {
390  $handler = "ob_gzhandler";
391  }
392 
393  ob_start($handler);
394  }
395 
396  if (is_array($result)) {
397  foreach ($result as $element) {
398  echo($element);
399  }
400  } else {
401  echo($result);
402  }
403 
404  if ($use_ob) {
405  ob_end_flush();
406  }
407  exit();
408  }
409 
410  /***********************/
416  protected function _process_token_command( $in_andisol_instance,
417  $in_http_method,
418  $in_path = [],
419  $in_query = []
420  ) {
421  $ret = NULL;
422  if ($in_andisol_instance->logged_in()) { // We also have to be logged in to have any access to tokens.
423  if (('GET' == $in_http_method) && !isset($in_query['types']) && (!isset($in_path) || !is_array($in_path) || !count($in_path))) { // Do we just want a list of our tokens, or count access to a token?
424  $ret = ['tokens' => $in_andisol_instance->get_available_tokens()];
425  } elseif (('GET' == $in_http_method) && isset($in_query['types'])) { // If we added "types," then we want a catalog.
426  $tokens = $in_andisol_instance->get_available_tokens();
427  $personal_login_array = [];
428 
429  $ret_temp = [];
430  // If we are logged in as God, then we can actually get a list of the "owners" of personal tokens.
431  if ($in_andisol_instance->god()) {
432  $personal_login_array = $in_andisol_instance->get_logins_with_personal_ids();
433  } else { // If not God, then
434  $personal_login_array = [$in_andisol_instance->current_login()->id() => $in_andisol_instance->get_personal_security_ids()];
435  }
436 
437  foreach ($tokens as $token) {
438  $id = NULL;
439 
440  foreach ($personal_login_array as $key => $ids) {
441  if (in_array($token, $ids)) {
442  $id = $key;
443  break;
444  }
445  }
446 
447  $temp_ret = ['id' => $token];
448  if (isset($id)) {
449  $temp_ret['type'] = 'personal';
450  if ($in_andisol_instance->god()) {
451  $temp_ret['login_id'] = $id;
452  }
453  } elseif ($in_andisol_instance->is_this_a_personal_id($token)) { // This will tell us that we have the token, only on sufferance of someone else.
454  $temp_ret['type'] = 'assigned';
455  } elseif ($in_andisol_instance->is_this_a_login_id($token) && $in_andisol_instance->god()) { // Only God can know whether an ID is a login.
456  $temp_ret['type'] = 'login';
457  } else {
458  $temp_ret['type'] = 'token';
459  }
460 
461  $ret_temp[] = $temp_ret;
462  }
463  $ret['tokens'] = $ret_temp;
464  } elseif (('GET' == $in_http_method) && isset($in_query['count_access_to']) && (isset($in_path) && is_array($in_path) && count($in_path))) { // Ask for a response that counts all logins that have access to a given token.
465  $ids = array_map('trim', explode(',', $in_path[0]));
466  $ids = array_map('trim', $ids);
467  $ids = array_map('intval', $ids);
468  $ids = array_unique($ids);
469  $retTemp = [];
470  foreach ($ids as $single_id) {
471  if (1 < $single_id) { // We don't do 0 or 1
472  array_push($retTemp, ['token' => intval($single_id), 'access' => $in_andisol_instance->count_all_login_objects_with_access(intval($single_id))]);
473  }
474  }
475  $ret = ['count_access_to' => $retTemp];
476  } elseif (('POST' == $in_http_method) && $in_andisol_instance->manager()) { // If we are handling POST, then we ignore everything else, and create a new token. However, we need to be a manager to do this.
477  $ret = ['tokens' => [$in_andisol_instance->make_security_token()]];
478  } else {
479  header('HTTP/1.1 403 Unauthorized Command');
480  exit();
481  }
482  } else {
483  header('HTTP/1.1 403 Unauthorized Command');
484  exit();
485  }
486  return $ret;
487  }
488 
489  /***********************/
495  protected function _process_serverinfo_command( $in_andisol_instance,
496  $in_http_method,
497  $in_path = [],
498  $in_query = []
499  ) {
500  $ret = NULL;
501  if ($in_andisol_instance->god()) { // We also have to be logged in as God to have any access to serverinfo.
502  $ret = ['serverinfo' => []];
503  $ret['serverinfo']['basalt_version'] = __BASALT_VERSION__;
504  $ret['serverinfo']['andisol_version'] = __ANDISOL_VERSION__;
505  $ret['serverinfo']['cobra_version'] = __COBRA_VERSION__;
506  $ret['serverinfo']['chameleon_version'] = __CHAMELEON_VERSION__;
507  $ret['serverinfo']['badger_version'] = __BADGER_VERSION__;
508  $ret['serverinfo']['security_db_type'] = CO_Config::$sec_db_type;
509  $ret['serverinfo']['data_db_type'] = CO_Config::$data_db_type;
510  $ret['serverinfo']['lang'] = CO_Config::$lang;
511  $ret['serverinfo']['min_pw_length'] = intval(CO_Config::$min_pw_len);
512  $ret['serverinfo']['regular_timeout_in_seconds'] = intval(CO_Config::$session_timeout_in_seconds);
513  $ret['serverinfo']['god_timeout_in_seconds'] = intval(CO_Config::$god_session_timeout_in_seconds);
514  $ret['serverinfo']['block_repeated_logins'] = CO_Config::$block_logins_for_valid_api_key ? true : false;
515  $ret['serverinfo']['block_differing_ip_address'] = CO_Config::$api_key_includes_ip_address ? true : false;
516  $ret['serverinfo']['ssl_requirement_level'] = intval(CO_Config::$ssl_requirement_level);
517  $ret['serverinfo']['google_api_key'] = CO_Config::$google_api_key;
518  $ret['serverinfo']['allow_address_lookup'] = CO_Config::$allow_address_lookup ? true : false;
519  $ret['serverinfo']['allow_general_address_lookup'] = CO_Config::$allow_general_address_lookup ? true : false;
520  $ret['serverinfo']['default_region_bias'] = CO_Config::$default_region_bias;
521  } else {
522  header('HTTP/1.1 403 Unauthorized Command');
523  exit();
524  }
525  return $ret;
526  }
527 
528  /***********************/
534  protected function _process_ping_command( $in_andisol_instance,
535  $in_path = []
536  ) {
537  $login_id_list = [];
538 
539  if ( isset ($in_path[0]) ) {
540  $login_id_list = array_map('intval', array_map('trim', explode(',', $in_path[0])));
541  } else {
542  $login_id_list = [$in_andisol_instance->current_login()->id()];
543  }
544  if ( 0 < count($login_id_list) ) {
545  $ret = [];
546 
547  foreach ($login_id_list as $id) {
548  $time = floatval($in_andisol_instance->get_remaining_time($id));
549 
550  if ( (-1 == $time) || (0 < $time) ) {
551  $ret["remaining_time"][] = ["id" => $id, "time" => $time];
552  }
553  }
554 
555  if ( 0 < count($ret) ) {
556  return $ret;
557  }
558  }
559 
560  header('HTTP/1.1 403 Unable to Determine Time');
561  exit();
562  }
563 
564  /***********************/
570  protected function _process_baseline_command( $in_andisol_instance,
571  $in_http_method,
572  $in_command,
573  $in_path = [],
574  $in_query = []
575  ) {
576  $ret = [];
577 
578  // No command simply means list the plugins.
579  if (('GET' == $in_http_method) && (!isset($in_command) || !$in_command)) {
580  $ret = Array('plugins' => CO_Config::plugin_names());
581  array_unshift($ret['plugins'], $this->plugin_name());
582  } elseif ( ('GET' == $in_http_method) && ('ping' == $in_command) ) {
583  $ret = $this->_process_ping_command($in_andisol_instance, $in_path);
584  } elseif (('POST' == $in_http_method) && $this->_andisol_instance->god() && ('bulk-loader' == $in_command) && CO_Config::$enable_bulk_upload) { // This is the "bulk-loader." It is POST-only, you must be logged in as God, and the variable needs to be true in the config.
585  $ret = $this->_baseline_bulk_loader();
586  } elseif ('tokens' == $in_command) { // If we are viewing or editing the tokens, then we deal with that here.
587  $ret = $this->_process_token_command($in_andisol_instance, $in_http_method, $in_path, $in_query);
588  } elseif (('serverinfo' == $in_command) && $in_andisol_instance->god()) { // God can ask for information about the server.
589  $ret = $this->_process_serverinfo_command($in_andisol_instance, $in_http_method, $in_path, $in_query);
590  } elseif (('backup' == $in_command) && $in_andisol_instance->god() && ('GET' == $in_http_method)) { // God can ask for a backup of the server (open wide).
591  $ret = $this->_baseline_fetch_backup($in_andisol_instance);
592  } elseif (('handlers' == $in_command) && isset($in_path[0]) && trim($in_path[0])) {
593  $id_list = explode(',', trim($in_path[0]));
594  $id_list = array_map('intval', $id_list);
595  $handlers = [];
596  foreach ($id_list as $id) {
597  if (1 < $id) {
598  $class_name = $in_andisol_instance->get_data_access_class_by_id($id);
599  if ($class_name) {
600  $handler = self::_get_handler($class_name);
601  if ($handler) {
602  $ret[$handler][] = $id;
603  }
604  }
605  }
606  }
607  } elseif (('visibility' == $in_command) && $in_andisol_instance->logged_in()) {
608  if ('token' == trim($in_path[0])) {
609  $token = intval(trim($in_path[1]));
610  if ((0 <= $token) && $in_andisol_instance->i_have_this_token($token)) {
611  if (isset($in_query['users'])) {
612  $objects = $in_andisol_instance->get_all_user_objects_with_access($token);
613  if (count($objects)) {
614  $ret['token']['token'] = $token;
615  $ret['token']['user_ids'] = array_map(function($item){ return intval($item->id()); }, $objects);
616  }
617  } else {
618  $objects = $in_andisol_instance->get_all_login_objects_with_access($token);
619  if (count($objects)) {
620  $ret['token']['token'] = $token;
621  $ret['token']['login_ids'] = array_map(function($item){ return intval($item->id()); }, $objects);
622  }
623  }
624  } else {
625  header('HTTP/1.1 400 Invalid Token');
626  exit();
627  }
628  } else {
629  $id = intval(trim($in_path[0]));
630  // The ID must be generally valid, and we need to be able to see it.
631  if ((1 < $id) && $in_andisol_instance->can_i_see_this_data_record($id)) {
632  $record = $in_andisol_instance->get_single_data_record_by_id($id);
633 
634  if ($record) {
635  $read_login_records = $in_andisol_instance->get_all_login_objects_with_access($record->read_security_id);
636  $write_login_records = $in_andisol_instance->get_all_login_objects_with_access($record->write_security_id);
637  $ret['id']['id'] = $id;
638  $ret['id']['writeable'] = $record->user_can_write();
639  $read_login_records = array_map(function($item){ return intval($item->id()); }, $read_login_records);
640  $write_login_records = array_map(function($item){ return intval($item->id()); }, $write_login_records);
641 
642  if (count($write_login_records)) {
643  foreach ($write_login_records as $id) {
644  if (!in_array($id, $read_login_records)) {
645  $read_login_records[] = $id;
646  }
647  }
648 
649  sort($write_login_records);
650  sort($read_login_records);
651  }
652 
653  if (count($read_login_records)) {
654  $ret['id']['read_login_ids'] = $read_login_records;
655  }
656 
657  if (count($write_login_records)) {
658  $ret['id']['write_login_ids'] = $write_login_records;
659  }
660  }
661  } else {
662  header('HTTP/1.1 400 Invalid ID');
663  exit();
664  }
665  }
666  } elseif ('version' == $in_command) {
667  $ret['version'] = __BASALT_VERSION__;
668  } elseif ('search' == $in_command) {
669  // For a location search, all three of these need to be specified, and radius needs to be nonzero.
670  $radius = isset($in_query) && is_array($in_query) && isset($in_query['search_radius']) && (0.0 < floatval($in_query['search_radius'])) ? floatval($in_query['search_radius']) : NULL;
671  $longitude = isset($in_query) && is_array($in_query) && isset($in_query['search_longitude']) ? floatval($in_query['search_longitude']) : NULL;
672  $latitude = isset($in_query) && is_array($in_query) && isset($in_query['search_latitude']) ? floatval($in_query['search_latitude']) : NULL;
673 
674  $search_page_size = isset($in_query) && is_array($in_query) && isset($in_query['search_page_size']) ? abs(intval($in_query['search_page_size'])) : 0; // This is the size of a page of results (1-based result count. 0 is no page size).
675  $search_page_number = isset($in_query) && is_array($in_query) && isset($in_query['search_page_number']) ? abs(intval($in_query['search_page_number'])) : 0; // Ignored if search_page_size is 0. The page we are interested in (0-based. 0 is the first page).
676  $writeable = isset($in_query) && is_array($in_query) && isset($in_query['writeable']); // Show/list only things this user can modify.
677  $search_name = isset($in_query) && is_array($in_query) && isset($in_query['search_name']) ? trim($in_query['search_name']) : NULL; // Search in the object name.
678 
679  $search_array = [];
680 
681  if (isset($radius) && (0 < $radius) && isset($longitude) && isset($latitude)) {
682  $location_search = Array('radius' => $radius, 'longitude' => $longitude, 'latitude' => $latitude);
683  $search_array['location'] = $location_search;
684  }
685 
686  if (isset($search_name)) {
687  $search_array['name'] = Array($search_name, 'use_like' => 1);
688  }
689 
690  $tags = [];
691 
692  $has_tag = false;
693 
694  for ($tag = 0; $tag < 10; $tag++) {
695  $tag_string = 'search_tag'.$tag;
696  $tag_value = isset($in_query) && is_array($in_query) && isset($in_query[$tag_string]) ? trim($in_query[$tag_string]) : NULL;
697  if ($tag_value !== NULL) {
698  $has_tag = true;
699  }
700  $tags[] = $tag_value;
701  }
702 
703  // If there were any specified tags, we search by tag. Otherwise, we don't bother.
704  if ($has_tag) {
705  $search_array['tags'] = $tags;
706  $search_array['tags']['use_like'] = 1;
707  }
708 
709  $object_list = $in_andisol_instance->generic_search($search_array, false, $search_page_size, $search_page_number, $writeable);
710 
711  if (isset($object_list) && is_array($object_list) && count($object_list)) {
712  foreach ($object_list as $instance) {
713  $class_name = get_class($instance);
714 
715  if ($class_name) {
716  $handler = self::_get_handler($class_name);
717  $ret[$handler][] = $instance->id();
718  }
719  }
720  }
721 
722  if (isset($location_search)) {
723  $ret['search_location'] = $location_search;
724  }
725  }
726 
727  return $ret;
728  }
729 
730  /***********************/
734  protected function _baseline_fetch_backup( $in_andisol_instance
735  ) {
736  $ret = NULL;
737  set_time_limit(3600); // Give us plenty of time.
738 
739  // Have to be "God," and the variable in the config needs to be set.
740  if ($in_andisol_instance->god()) {
741  $backup = $in_andisol_instance->fetch_backup();
742 
743  if (isset($backup) && is_array($backup) && (2 == count($backup)) && isset($backup['security']) && is_array($backup['security']) && count($backup['security']) && isset($backup['data']) && is_array($backup['data']) && count($backup['data'])) {
744  $header_row = ['id','api_key','login_id','access_class','last_access','read_security_id','write_security_id','object_name','access_class_context','owner','longitude','latitude','tag0','tag1','tag2','tag3','tag4','tag5','tag6','tag7','tag8','tag9','ids','payload'];
745  header('Content-Type: text/csv');
746  echo(implode(',', $header_row)."\n");
747  foreach ($backup['security'] as $line) {
748  self::_output_one_line($line, $header_row);
749  }
750  foreach ($backup['data'] as $line) {
751  self::_output_one_line($line, $header_row);
752  }
753  exit();
754  } else {
755  header('HTTP/1.1 500 Internal Server Error');
756  exit();
757  }
758  } else {
759  header('HTTP/1.1 403 Unauthorized User');
760  exit();
761  }
762  }
763 
764  /***********************/
774  protected function _baseline_bulk_loader() {
775  $ret = NULL;
776  set_time_limit(3600); // Give us plenty of time.
777 
778  // Have to be "God," and the variable in the config needs to be set.
779  if ($this->_andisol_instance->god() && CO_Config::$enable_bulk_upload) {
780  if ('POST' == $this->_request_type) { // We must be a POST. There is only the 'loader' command, no query parameters, and no resource path. Just a simple POST, authenticated as "God," and a 'payload' of a CSV file.
781  if (isset($_FILES['payload']) && (!isset($_FILES['payload']['error']) || is_array($_FILES['payload']['error']))) { // Look for any errors in the payload.
782  header('HTTP/1.1 400 '.print_r($_FILES['payload']['error'], true));
783  exit();
784  } elseif (isset($_FILES['payload'])) {
785  $file_data = base64_decode(file_get_contents($_FILES['payload']['tmp_name'])); // We extract the CSV data. It should have been sent as Base64-encoded UTF-8 text, so we decode it first.
786 
787  $csv_data = self::_extract_csv_data($file_data);
788  if (isset($csv_data) && is_array($csv_data) && count($csv_data)) {
789  $ret = ['bulk_upload' => []]; // Prep a response.
790  $records = [];
791  $data_records = [];
792  $security_ids = [];
793  $data_ids = [];
794 
795  // This complex little dance, is so that we make sure that any tokens that used the old ID scheme are moved to the new scheme.
796  foreach ($csv_data as $row) {
797  $in_id = intval($row['id']);
798  $row_result = ['input_id' => $in_id];
799  $new_record = $this->_process_bulk_row($row);
800  // Here's where we track the old scheme. We make a record of each security node that we read.
801  if ($new_record instanceof CO_Security_Node) {
802  $security_ids[$in_id] = intval($new_record->id()); // This is a translation table for the IDs. We save all new security records; whether or not they are a login, as every record is a token.
803  } else {
804  $data_ids[$in_id] = intval($new_record->id()); // We do the same for data IDs.
805  }
806 
807  $records[] = $new_record; // Save the record.
808  $row_result['access_class'] = get_class($new_record);
809  $row_result['output_id'] = $new_record->id();
810  $ret['bulk_upload'][] = $row_result;
811  }
812 
813  // After we're done, we go back through the records, looking for ones with tokens. We then translate each set of tokens.
814  foreach ($records as $object) {
815  if (method_exists($object, 'ids')) { // We look at the token properties of security IDs.
816  $ids = $object->ids();
817  if (isset($ids) && is_array($ids) && count($ids)) { // Look for tokens.
818  $new_ids = [];
819 
820  foreach ($ids as $id) {
821  // This has the added benefit of removing any ones that don't apply to the dataset.
822  if (isset($security_ids[$id]) && (1 < $security_ids[$id])) {
823  $new_ids[] = $security_ids[$id];
824  }
825  }
826 
827  // Replace the object's IDs with the new ones.
828  $object->set_ids($new_ids);
829  }
830  }
831 
832  if (method_exists($object, 'owner_id')) { // We look to see if there is an "owner" record column.
833  $id = $object->owner_id(); // If so, we translate that.
834  if (isset($data_ids[$id]) && (1 < $data_ids[$id])) {
835  $object->set_owner_id(intval($data_ids[$id]));
836  }
837  }
838 
839  $context = $object->context; // Get any context.
840 
841  if (method_exists($object, 'children_ids')) { // We look to see if there is an "owner" record column.
842  $old_ids = $object->children_ids(true);
843  if (isset($old_ids) && is_array($old_ids) && count($old_ids)) {
844  $object->deleteAllChildren();
845  $new_ids = [];
846  foreach ($old_ids as $id) {
847  if (isset($data_ids[$id]) && (1 < $data_ids[$id])) {
848  $new_ids[] = intval($data_ids[$id]);
849  }
850  }
851 
852  if (isset($new_ids) && is_array($new_ids) && count($new_ids)) {
853  if (!$object->set_children_ids($new_ids)) {
854  header('HTTP/1.1 500 Internal Server Error');
855  exit();
856  }
857  }
858  }
859  }
860 
861  if ($object instanceof CO_User_Collection) {
862  $old_id = intval($object->tags()[0]);
863  $new_id = $security_ids[$old_id];
864 
865  if ($new_id) {
866  $object->set_tag(0, $new_id);
867  }
868  }
869 
870  $read = $object->read_security_id;
871 
872  if (1 < $read) {
873  $read = $security_ids[$read];
874  }
875 
876  // All objects have read and write tokens that need to be translated.
877  $object->set_read_security_id($read);
878  if (0 < $object->write_security_id) {
879  $object->set_write_security_id($security_ids[$object->write_security_id]);
880  } else {
881  $object->set_write_security_id(-1);
882  }
883 
884  if (!$object->clear_batch_mode()) {
885  header('HTTP/1.1 500 Internal Server Error');
886  exit();
887  }
888  }
889  } else {
890  header('HTTP/1.1 400 Invalid Bulk Data');
891  exit();
892  }
893  }
894  } else {
895  header('HTTP/1.1 400 Improper HTTP Method');
896  exit();
897  }
898  } else {
899  header('HTTP/1.1 403 Unauthorized User');
900  exit();
901  }
902  return $ret;
903  }
904 
905  /***********************/
908  protected function _process_bulk_row( $in_row_data
909  ) {
910  $access_class = $in_row_data['access_class'];
911 
912  // Make sure that we don't step on any logins.
913  if ($this->_andisol_instance->check_login_exists_by_login_string($in_row_data['login_id'])) {
914  $in_row_data['login_id'] .= '-copy';
915  }
916 
917  $new_record = $this->_andisol_instance->make_new_blank_record($access_class);
918 
919  if (isset($new_record) && ($new_record instanceof $access_class)) {
920  $in_row_data['id'] = $new_record->id();
921  $new_record->set_batch_mode();
922  $new_record->load_from_db($in_row_data);
923  if (!$new_record->update_db()) {
924  header('HTTP/1.1 500 Internal Server Error');
925  exit();
926  }
927  return $new_record;
928  } else {
929  header('HTTP/1.1 400 Invalid Bulk Data');
930  exit();
931  }
932  }
933 
934  /***********************/
940  protected function _get_xsd() {
941  return $this->_process_xsd(dirname(__FILE__).'/schema.xsd');
942  }
943 
944  /************************************************************************************************************************/
945  /*##################################################### PUBLIC METHODS #################################################*/
946  /************************************************************************************************************************/
947 
948  /***********************/
952  public function __construct() {
953  $this->version = __BASALT_VERSION__;
954  $this->error = NULL;
955  $this->_andisol_instance = NULL;
956  try {
957  // IIS puts "off" in the HTTPS field, so we need to test for that.
958  $https = ((!empty($_SERVER['HTTPS']) && (($_SERVER['HTTPS'] !== 'off') || (intval($_SERVER['SERVER_PORT']) == 443)))) ? true : false;
959  if ((CO_CONFIG_HTTPS_ALL > CO_Config::$ssl_requirement_level) || $https) {
961  // This is a test, to see whether or not we should use auth parameters (fastCGI).
962  if ((1 == count($this->_path)) && ('use_auth_params' == $this->_path[0])) {
963  exit('cgi' == substr(php_sapi_name(), 0, 3) ? '1' : '0');
964  // If this is a login, we do nothing else. We simply handle the login.
965  } elseif ((1 == count($this->_path)) && ('login' == $this->_path[0])) {
966  if (isset($this->_vars) && isset($this->_vars['login_id']) && isset($this->_vars['password'])) {
967  // We have the option (default on) of requiring TLS/SSL for logging in, so we check for that now.
968  if ($https || (CO_CONFIG_HTTPS_OFF == CO_Config::$ssl_requirement_level)) {
969  $login_id = $this->_vars['login_id'];
970  $password = $this->_vars['password'];
971 
972  // See if we have our validator in place.
973  if (method_exists('CO_Config', 'call_login_validator_function')) {
974  if (!CO_Config::call_login_validator_function($login_id, $password, $_SERVER)) {
975  header('HTTP/1.1 403 Unauthorized Login');
976  exit();
977  }
978  }
979 
980  // We do a simple login. This will also generate an API key, which is the only response to this command.
981  $andisol_instance = new CO_Andisol($login_id, '', $password);
982 
983  if (isset($andisol_instance) && ($andisol_instance instanceof CO_Andisol) && $andisol_instance->logged_in()) {
984  if (method_exists('CO_Config', 'call_log_handler_function')) {
985  CO_Config::call_log_handler_function($andisol_instance, $_SERVER);
986  }
987  $login_item = $andisol_instance->get_login_item();
988 
989  // If we are logging in, we shortcut the process, and simply return the API key.
990  if (isset($login_item) && ($login_item instanceof CO_Security_Login)) {
991  $api_key = $login_item->get_api_key();
992  // From now on, in order to access the login resources, you'll need to include the API key in the username/password fields.
993  if (isset($api_key)) {
994  echo($api_key);
995  } else {
996  header('HTTP/1.1 403 Unauthorized Login');
997  }
998  } else {
999  header('HTTP/1.1 403 Unauthorized Login');
1000  }
1001  } else {
1002  header('HTTP/1.1 403 Unauthorized Login');
1003  }
1004  } else {
1005  header('HTTP/1.1 401 SSL Connection Required');
1006  }
1007  } else {
1008  header('HTTP/1.1 401 Credentials Required');
1009  }
1010 
1011  exit();
1012  } elseif ((1 == count($this->_path)) && ('logout' == $this->_path[0])) { // See if the user wants to log out a session.
1013  $server_secret = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : NULL;
1014  $api_key = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : NULL;
1015 
1016  if (!$server_secret || !$api_key) {
1017  $auth = explode('&', $_SERVER['QUERY_STRING']);
1018  foreach ($auth as $query) {
1019  $exp = explode('=', $query);
1020  if ('login_server_secret' == $exp[0]) {
1021  $server_secret = trim($exp[1]);
1022  } elseif ('login_api_key' == $exp[0]) {
1023  $api_key = trim($exp[1]);
1024  }
1025  }
1026  }
1027 
1028  // See if an SSL connection is required.
1029  if ($https || (CO_CONFIG_HTTPS_LOGGED_IN_ONLY > CO_Config::$ssl_requirement_level)) {
1030  // If we don't have a valid API key/Server Secret pair, we scrag the process.
1031  if(!(isset($api_key) && $api_key && ($server_secret == Co_Config::server_secret()))) {
1032  header('HTTP/1.1 403 Cannot Logout Without Valid Credentials');
1033  } else {
1034  $andisol_instance = new CO_Andisol('', '', '', $api_key);
1035 
1036  if (isset($andisol_instance) && ($andisol_instance instanceof CO_Andisol) && $andisol_instance->logged_in()) {
1037  if (method_exists('CO_Config', 'call_log_handler_function')) {
1038  CO_Config::call_log_handler_function($andisol_instance, $_SERVER);
1039  }
1040 
1041  $login_item = $andisol_instance->get_login_item();
1042 
1043  // We "log out" by clearing the API key.
1044  if (isset($login_item) && ($login_item instanceof CO_Security_Login)) {
1045  if ($login_item->clear_api_key()) {
1046  header('HTTP/1.1 205 Logout Successful');
1047  } else { // This will probably never happen, but belt and suspenders...
1048  header('HTTP/1.1 200 Logout Unneccessary');
1049  }
1050  } else { // This will probably never happen, but belt and suspenders...
1051  header('HTTP/1.1 500 Internal Server Error');
1052  }
1053  } else {
1054  header('HTTP/1.1 403 Unauthorized Login');
1055  }
1056  }
1057  } else {
1058  header('HTTP/1.1 401 SSL Connection Required');
1059  }
1060 
1061  exit();
1062  } else { // Handle the rest of the requests here.
1063  // Look for authentication.
1064 
1065  $server_secret = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : NULL; // Supplied to the client by the Server Admin.
1066  $api_key = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : NULL; // Generated by the server for this session.
1067 
1068  if (!$server_secret || !$api_key) {
1069  $auth = explode('&', $_SERVER['QUERY_STRING']);
1070  foreach ($auth as $query) {
1071  $exp = explode('=', $query);
1072  if ('login_server_secret' == $exp[0]) {
1073  $server_secret = trim($exp[1]);
1074  } elseif ('login_api_key' == $exp[0]) {
1075  $api_key = trim($exp[1]);
1076  }
1077  }
1078  }
1079 
1080  // If we don't have a valid API key/Server Secret pair, we just forget about API keys.
1081  if(!(isset($api_key) && $api_key && ($server_secret == Co_Config::server_secret()))) {
1082  $api_key = NULL;
1083  }
1084 
1085  $https_requirement = true;
1086 
1087  // Make sure we are HTTPS, or SSL is not required.
1088  switch (CO_Config::$ssl_requirement_level) {
1089  case CO_CONFIG_HTTPS_ALL:
1090  // Yeah, it's required.
1091  break;
1092 
1093  case CO_CONFIG_HTTPS_LOGGED_IN_ONLY:
1094  // Only if we have an authentication header.
1095  $https_requirement = (NULL != $api_key);
1096  break;
1097 
1098  default:
1099  // Not necessary if we are login only or off.
1100  $https_requirement = false;
1101  break;
1102  }
1103 
1104  if ($https || !$https_requirement) {
1105  $andisol_instance = new CO_Andisol('', '', '', $api_key);
1106  if (isset($andisol_instance) && ($andisol_instance instanceof CO_Andisol)) {
1107  if (method_exists('CO_Config', 'call_log_handler_function')) {
1108  CO_Config::call_log_handler_function($andisol_instance, $_SERVER);
1109  }
1110  $this->_andisol_instance = $andisol_instance;
1111  } else {
1112  header('HTTP/1.1 500 Internal Server Error');
1113  exit();
1114  }
1115  } else {
1116  header('HTTP/1.1 401 SSL Connection Required');
1117  exit();
1118  }
1119  }
1120  } else {
1121  header('HTTP/1.1 401 SSL Connection Required');
1122  exit();
1123  }
1124  // OK. By the time we get here, we are either logged in, or not logged in, and have a valid ANDISOL instance. We're ready to go. Put on our shades. We're on a mission for Glod.
1125  $this->_process_command();
1126  } catch (Exception $e) {
1127  header('HTTP/1.1 500 Internal Server Error');
1128  exit();
1129  }
1130  }
1131 
1132  /***********************/
1136  public function get_plugin_names() {
1137  $ret = CO_Config::plugin_names();
1138  array_unshift($ret, $this->plugin_name());
1139  return $ret;
1140  }
1141 
1142  /***********************/
1146  public function valid() {
1147  return isset($this->_andisol_instance) ? $this->_andisol_instance->valid() : false;
1148  }
1149 
1150  /***********************/
1154  public function logged_in() {
1155  return isset($this->_andisol_instance) ? $this->_andisol_instance->logged_in() : false;
1156  }
1157 
1158  /***********************/
1162  public function plugin_name() {
1163  return _PLUGIN_NAME_;
1164  }
1165 
1166  /***********************/
1172  static public function classes_managed() {
1173  return [];
1174  }
1175 
1176  /***********************/
1182  public function process_command( $in_andisol_instance,
1183  $in_http_method,
1184  $in_response_type,
1185  $in_path = [],
1186  $in_query = []
1187  ) {
1188  $ret = NULL;
1189 
1190  if (is_array($in_path) && (3 >= count($in_path))) {
1191  $command = isset($in_path[0]) ? strtolower(trim(array_shift($in_path))) : [];
1192  // Backup needs God Admin, GET method, and CSV response format.
1193  if ((('csv' == $in_response_type) && ('backup' != $command)) || (('backup' == $command) && (!$in_andisol_instance->god() || ('GET' != $in_http_method) || ('csv' != $in_response_type)))) {
1194  header('HTTP/1.1 400 Improper Baseline Command');
1195  exit();
1196  }
1197  $ret = $this->_process_baseline_command($in_andisol_instance, $in_http_method, $command, $in_path, $in_query);
1198  } else {
1199  header('HTTP/1.1 400 Improper Baseline Command');
1200  exit();
1201  }
1202 
1203  return $this->_condition_response($in_response_type, $ret);
1204  }
1205 };
if(!defined('LGV_ACCESS_CATCHER')) if(!defined( 'LGV_ANDISOL_CATCHER'))
const __BASALT_VERSION__
const _PLUGIN_NAME_
const __ANDISOL_VERSION__
const __COBRA_VERSION__
const __CHAMELEON_VERSION__
const __BADGER_VERSION__
_condition_response( $in_response_type, $in_response_as_associative_array=NULL)
static _get_handler( $in_classname)
_process_xsd( $in_schema_file_path)
$version
The version indicator.
$_path
This array will contain any path components that are received via GET, PUT, POST or DELETE.
_process_bulk_row( $in_row_data)
$_response_type
This is the reponse type. It is 'json', 'xml' or 'xsd'.
_process_ping_command( $in_andisol_instance, $in_path=[])
_process_basalt_parameters()
static _output_one_line( $in_line, $in_header_row)
process_command( $in_andisol_instance, $in_http_method, $in_response_type, $in_path=[], $in_query=[])
_process_baseline_command( $in_andisol_instance, $in_http_method, $in_command, $in_path=[], $in_query=[])
$error
Any errors that occured are kept here.
$_andisol_instance
This contains the instance of ANDISOL used by this instance.
$_vars
This associative array will contain any query variables that are received via GET,...
_process_serverinfo_command( $in_andisol_instance, $in_http_method, $in_path=[], $in_query=[])
static classes_managed()
static _extract_csv_data( $in_text_data)
_baseline_fetch_backup( $in_andisol_instance)
_process_token_command( $in_andisol_instance, $in_http_method, $in_path=[], $in_query=[])
$_plugin_selector
This will be a lowercase string, denoting the plugin selected for the operation.
static $pdo_error_code_invalid_login
Definition: common.inc.php:44
static $login_error_code_api_key_mismatch
Definition: common.inc.php:58
static $login_error_code_api_key_invalid
Definition: common.inc.php:57