1<?php 2 3/** 4 * 5 * phpIPAM API client to work with phpIPAM APIv2 6 * 7 * 8 */ 9class phpipam_api_client { 10 11 /** 12 * Debug flag for curl 13 * 14 * (default value: false) 15 * 16 * @var bool 17 * @access public 18 */ 19 public $debug = false; 20 21 /** 22 * API server URL 23 * 24 * (default value: false) 25 * 26 * @var bool|string 27 * @access private 28 */ 29 private $api_url = false; 30 31 /** 32 * API APP identifier 33 * 34 * (default value: false) 35 * 36 * @var bool|string 37 * @access private 38 */ 39 private $api_app_id = false; 40 41 /** 42 * API key 43 * 44 * (default value: false) 45 * 46 * @var bool|string 47 * @access private 48 */ 49 private $api_key = false; 50 51 /** 52 * Flag if we need to encrypt API communication 53 * 54 * (default value: false) 55 * 56 * @var bool 57 * @access private 58 */ 59 private $api_encrypt = false; 60 61 /** 62 * phpipam account username / passwork for authentication 63 * 64 * (default value: false) 65 * 66 * @var bool|string 67 * @access private 68 */ 69 private $api_username = false; 70 private $api_password = false; 71 72 /** 73 * Holder for CUrL connection 74 * 75 * (default value: false) 76 * 77 * @var bool|mixed 78 * @access private 79 */ 80 private $Connection = false; 81 82 /** 83 * Access token for phpipam 84 * 85 * (default value: false) 86 * 87 * @var bool|string 88 * @access private 89 */ 90 private $token = false; 91 92 /** 93 * When token expires 94 * 95 * (default value: false) 96 * 97 * @var bool|string 98 * @access private 99 */ 100 private $token_expires = false; 101 102 /** 103 * api_server_method 104 * 105 * (default value: false) 106 * 107 * @var bool|string 108 * @access private 109 */ 110 private $api_server_method = false; 111 112 /** 113 * api_server_controller (sections, subnets, ...) 114 * 115 * (default value: false) 116 * 117 * @var bool|string 118 * @access private 119 */ 120 private $api_server_controller = false; 121 122 /** 123 * Identifiers to add to URL 124 * 125 * (default value: false) 126 * 127 * @var bool 128 * @access private 129 */ 130 private $api_server_identifiers = false; 131 132 /** 133 * List of valid API methods 134 * 135 * @var mixed 136 * @access private 137 */ 138 private $api_server_valid_methods = array( 139 "OPTIONS", "GET", "POST", "PATCH", "DELETE", 140 ); 141 142 /** 143 * HTTP error codes for responses 144 * 145 * @var mixed 146 * @access public 147 */ 148 public $error_codes = array( 149 // OK 150 200 => "OK", 151 201 => "Created", 152 202 => "Accepted", 153 204 => "No Content", 154 // Client errors 155 400 => "Bad Request", 156 401 => "Unauthorized", 157 403 => "Forbidden", 158 404 => "Not Found", 159 405 => "Method Not Allowed", 160 415 => "Unsupported Media Type", 161 // Server errors 162 500 => "Internal Server Error", 163 501 => "Not Implemented", 164 503 => "Service Unavailable", 165 505 => "HTTP Version Not Supported", 166 511 => "Network Authentication Required", 167 ); 168 169 /** 170 * Set result format 171 * 172 * (default value: array("json", "array", "object", "xml")) 173 * 174 * @var mixed 175 * @access private 176 */ 177 private $result_format_available = array( 178 "json", "array", "object", "xml", 179 ); 180 181 /** 182 * Result format 183 * 184 * value in $result_format_available 185 * 186 * (default value: "json") 187 * 188 * @var string 189 * @access private 190 */ 191 private $result_format = "json"; 192 193 /** 194 * To store result 195 * 196 * @var mixed 197 * @access private 198 */ 199 private $result = array( 200 "success" => true, 201 "code" => 204, 202 "message" => "" 203 ); 204 205 /** 206 * Reponse headers 207 * 208 * @var mixed 209 */ 210 public $response_headers = array (); 211 212 213 /** 214 * class constructor... 215 * 216 * @access public 217 * @param bool|string $api_url (default: false) 218 * @param bool|string $app_id (default: false) 219 * @param bool|string $api_key (default: false) 220 * @param bool|string $username (default: false) 221 * @param bool|string $password (default: false) 222 * @param string $result_format (default: "json") 223 */ 224 public function __construct($api_url = false, $app_id = false, $api_key = false, $username = false, $password = false, $result_format = "json") { 225 // set app server URL if provided 226 if ($api_url!==false) { 227 $this->set_api_url ($api_url); 228 } 229 // set app_id if provided 230 if ($app_id!==false) { 231 $this->set_api_app_id ($app_id); 232 } 233 // set api key if provided 234 if ($api_key!==false) { 235 $this->set_api_key ($api_key); 236 } 237 // set user/pass if provided 238 if ($username!==false && $password!==false) { 239 $this->set_api_authparams ($username, $password); 240 } 241 // set result format if provided 242 if (strlen($result_format)>0) { 243 $this->set_result_format ($result_format); 244 } 245 // check for required php extensions 246 $this->validate_php_extensions (); 247 } 248 249 /** 250 * Saves error and exits script 251 * 252 * @access public 253 * @param mixed $content 254 * @return void 255 */ 256 public function exception ($content) { 257 //set result parameters 258 $this->result = array( 259 'code' => 400, 260 'success' => false, 261 'message' => $content 262 ); 263 // print result 264 $this->print_result (); 265 // exit 266 #die(); 267 return; 268 } 269 270 /** 271 * Returns last result 272 * 273 * @access public 274 * @return void 275 */ 276 public function get_result () { 277 # output result 278 if ($this->result_format=="json") { 279 return json_encode($this->result); 280 } 281 elseif ($this->result_format=="array") { 282 return $this->result; 283 } 284 elseif ($this->result_format=="object") { 285 return (object) $this->result; 286 } 287 elseif ($this->result_format=="xml") { 288 // new SimpleXMLElement object 289 $xml = new SimpleXMLElement('<'.$_GET['controller'].'/>'); 290 // generate xml from result 291 $this->array_to_xml($xml, $this->result); 292 // return XML result 293 return $xml->asXML(); 294 } 295 } 296 297 /** 298 * Prints last result 299 * 300 * @access public 301 * @return void 302 */ 303 public function print_result () { 304 # output result 305 if ($this->result_format=="json") { 306 print json_encode($this->result); 307 } 308 elseif ($this->result_format=="array") { 309 var_dump($this->result); 310 } 311 elseif ($this->result_format=="object") { 312 var_dump( (object) $this->result); 313 } 314 elseif ($this->result_format=="xml") { 315 // new SimpleXMLElement object 316 $xml = new SimpleXMLElement('<apiclient/>'); 317 // generate xml from result 318 $this->array_to_xml($xml, $this->result); 319 // return XML result 320 print $xml->asXML(); 321 } 322 } 323 324 /** 325 * Transforms array to XML 326 * 327 * @access private 328 * @param SimpleXMLElement $object 329 * @param array $data 330 * @return void 331 */ 332 private function array_to_xml(SimpleXMLElement $object, array $data) { 333 // loop through values 334 foreach ($data as $key => $value) { 335 // if spaces exist in key replace them with underscores 336 if(strpos($key, " ")>0) { 337 $key = str_replace(" ", "_", $key); 338 } 339 340 // if key is numeric append item 341 if(is_numeric($key)) { 342 $key = "item".$key; 343 } 344 345 // if array add child 346 if (is_array($value)) { 347 $new_object = $object->addChild($key); 348 $this->array_to_xml($new_object, $value); 349 } 350 // else write value 351 else { 352 $object->addChild($key, $value); 353 } 354 } 355 } 356 357 /** 358 * Check if all extensions are present 359 * 360 * @access private 361 * @return void 362 */ 363 private function validate_php_extensions () { 364 // Required extensions 365 $required_ext = array("openssl", "curl"); 366 // mcrypt for crypted extensions 367 if($this->api_key !== false) 368 $required_ext[] = "mcrypt"; 369 // json 370 if($this->result_format == "json") 371 $required_ext[] = "json"; 372 // xml 373 if($this->result_format == "xml") 374 $required_ext[] = "xmlreader"; 375 376 // Available extensions 377 $available_ext = get_loaded_extensions(); 378 379 // check 380 foreach ($required_ext as $e) { 381 if(!in_array($e, $available_ext)) { 382 $this->exception("Missing php extension ($e)"); 383 } 384 } 385 } 386 387 /** 388 * Debugging flag 389 * 390 * @access public 391 * @param bool $debug (default: false) 392 * @return void 393 */ 394 public function set_debug ($debug = false) { 395 if(is_bool($debug)) { 396 $this->debug = $debug; 397 } 398 } 399 400 /** 401 * Checks requested result format and saves it 402 * 403 * @access public 404 * @param string $result_format (default: "json") 405 * @return void 406 */ 407 public function set_result_format ($result_format = "json") { 408 if (strlen($result_format)>0) { 409 if (!in_array($result_format, $this->result_format_available)) { 410 $this->exception ("Invalid result format"); 411 } 412 else { 413 // recheck extensions 414 $this->validate_php_extensions (); 415 // set 416 $this->result_format = $result_format; 417 } 418 } 419 } 420 421 /** 422 * Set API url parameter 423 * 424 * @access public 425 * @param mixed $api_url 426 * @return void 427 */ 428 public function set_api_url ($api_url) { 429 // we need http/https 430 if(strpos($api_url, "http://")!==false || strpos($api_url, "https://")!==false) { 431 // trim 432 $api_url = trim($api_url); 433 // add last / if missing 434 if (substr($api_url, -1)!=="/") { $api_url .= "/"; } 435 // save 436 $this->api_url = $api_url; 437 } 438 else { 439 $this->exception("Invalid API URL"); 440 } 441 } 442 443 /** 444 * Sets api app_id variable 445 * 446 * @access public 447 * @param bool $id (default: false) 448 * @return void 449 */ 450 public function set_api_app_id ($app_id = false) { 451 if ($app_id!==false) { 452 // name must be more than 2 and alphanumberic 453 if(strlen($app_id)<3 || strlen($app_id)>12 || !ctype_alnum($app_id)) { 454 $this->exception("Invalid APP id"); 455 } 456 else { 457 $this->api_app_id = $app_id; 458 } 459 } 460 else { 461 $this->exception("Invalid APP id"); 462 } 463 } 464 465 /** 466 * Set api key 467 * 468 * @access public 469 * @param bool $api_key (default: false) 470 * @return void 471 */ 472 public function set_api_key ($api_key = false) { 473 #if ($api_key!==false) { 474 if ($api_key!=false) { 475 $this->api_key = $api_key; 476 477 // set encrypt flag 478 $this->api_encrypt = true; 479 } 480 else { 481 $this->exception("Invalid APP id"); 482 } 483 } 484 485 /** 486 * Sets username/password for URL auth 487 * 488 * @access public 489 * @param bool $username (default: false) 490 * @param bool $password (default: false) 491 * @return void 492 */ 493 public function set_api_authparams ($username = false, $password = false) { 494 if($username===false || $password===false) { 495 $this->exception("Invalid username or password"); 496 } 497 else { 498 $this->api_username = $username; 499 $this->api_password = $password; 500 } 501 } 502 503 /** 504 * Sreets api method. 505 * 506 * @access public 507 * @param string $method (default: "GET") 508 * @return void 509 */ 510 public function set_api_method ($method = "GET") { 511 // validate 512 $this->set_api_method_validate ($method); 513 // set 514 $this->api_server_method = strtoupper($method); 515 } 516 517 /** 518 * Validates API method against available 519 * 520 * @access private 521 * @param mixed $method 522 * @return void 523 */ 524 private function set_api_method_validate ($method) { 525 if(!in_array(strtoupper($method), $this->api_server_valid_methods)) { 526 $this->exception("Invalid method $method"); 527 } 528 } 529 530 /** 531 * Sets API controller - required 532 * 533 * @access public 534 * @param bool|string $controller (default: false) 535 * @return void 536 */ 537 public function set_api_controller ($controller = false) { 538 #if($controller!==false) { 539 if($controller!=false) { 540 $this->api_server_controller = strtolower($controller); 541 } 542 } 543 544 /** 545 * Sets additional identifiers to be passed to URL directly 546 * 547 * e.g.: /api/appid/controller/<identifier1>/<identifier2>/ 548 * 549 * @access public 550 * @param mixed $identifiers 551 * @return void 552 */ 553 public function set_api_identifiers ($identifiers) { 554 $this->api_server_identifiers = false; // clear this to forget any previous settings 555 if(is_array($identifiers)) { 556 if(sizeof($identifiers)>0 && !$this->api_encrypt) { 557 // reset 558 $this->api_server_identifiers = implode("/", $identifiers); 559 } 560 elseif (sizeof($identifiers)>0 && $this->api_encrypt) { 561 $this->api_server_identifiers = array(); 562 foreach ($identifiers as $cnt=>$i) { 563 if($cnt==0) { 564 $this->api_server_identifiers['id'] = $i; 565 } 566 else { 567 $this->api_server_identifiers['id'.($cnt+1)] = $i; 568 } 569 } 570 } 571 } 572 } 573 574 575 /* @api-server communication --------------- */ 576 577 /** 578 * Executes request to API server 579 * 580 * @access public 581 * @param bool|string $method (default: false) 582 * @param bool|string $controller (default: false) 583 * @param mixed $identifiers (default: array()) 584 * @param mixed $params (default: array()) 585 * @param bool|string $token_file (default: false) 586 * @return void 587 */ 588 public function execute ($method = false, $controller = false, $identifiers = array(), $params = array(), $token_file = false) { 589 // check and set method 590 $this->set_api_method ($method); 591 // set api controller 592 $this->set_api_controller ($controller); 593 // set api identifiers 594 $this->set_api_identifiers ($identifiers); 595 596 // set connection 597 $this->curl_set_connection ($token_file); 598 // save params 599 $this->curl_set_params ($params); 600 // set HTTP method 601 $this->curl_set_http_method (); 602 603 // if not encrypted set params 604 if(!$this->api_encrypt) { 605 // add token to header, authenticate if it fails 606 $this->curl_add_token_header ($token_file); 607 } 608 // if token is set execute 609 if ($this->token !== false) { 610 // execute 611 $res = $this->curl_execute (); 612 // save result 613 $this->result = (array) $res; 614 615 // check for invalid token and retry 616 if ($this->result['code']=="401" && $token_file!==false) { 617 // remove old token 618 $this->delete_token_file ($token_file); 619 // auth again 620 $this->curl_add_token_header ($token_file); 621 // execute 622 $res = $this->curl_execute (); 623 // save result 624 $this->result = (array) $res; 625 } 626 } 627 // exncrypted request 628 elseif ($this->api_encrypt) { 629 // execute 630 $res = $this->curl_execute (); 631 // save result 632 $this->result = (array) $res; 633 } 634 // save reult 635 $this->curl_save_headers (); 636 } 637 638 /** 639 * Opens cURL resource and sets initial parameters 640 * 641 * @access private 642 * @param mixed $token_file 643 * @return void 644 */ 645 private function curl_set_connection ($token_file) { 646 // check if it exists 647 #if ($this->Connection===false) { 648 if ($this->Connection==false) { 649 // Get cURL resource 650 $this->Connection = curl_init(); 651 652 // set URL 653 if($this->api_server_controller===false) { 654 $url = $this->api_url.$this->api_app_id."/"; 655 } 656 else { 657 $url = $this->api_url.$this->api_app_id.str_replace("//", "/", "/".$this->api_server_controller."/".$this->api_server_identifiers."/"); 658 } 659 660 // set default curl options and params 661 curl_setopt_array($this->Connection, array( 662 CURLOPT_RETURNTRANSFER => true, 663 CURLOPT_URL => $url, 664 CURLOPT_HEADER => false, 665 CURLOPT_VERBOSE => $this->debug, 666 CURLOPT_TIMEOUT => 9, 667 CURLOPT_HTTPHEADER => array( 668 'Content-Type: application/json', 669 # https://stackoverflow.com/a/33550871 670 'Connection: Keep-Alive', 671 ), 672 CURLOPT_USERAGENT => 'phpipam-api php class', 673 # https://stackoverflow.com/a/43277274 674 CURLOPT_ENCODING => 'gzip,deflate', 675 #CURLOPT_ENCODING => 'deflate', 676 // ssl 677 CURLOPT_SSL_VERIFYHOST => false, 678 CURLOPT_SSL_VERIFYPEER => false, 679 // save headers 680 CURLINFO_HEADER_OUT => true, 681 ) ); 682 } 683 } 684 685 /** 686 * Adds params to request if required 687 * 688 * @access private 689 * @param mixed $params 690 * @return void 691 */ 692 private function curl_set_params ($params) { 693 // params set ? 694 if (is_array($params) && !$this->api_encrypt ) { 695 if (sizeof($params)>0) { 696 # 9163a784aff4e9689dd0a5330a826f70247a2ce2 697 if ($this->api_server_method == 'GET') 698 curl_setopt($this->Connection, CURLOPT_URL, $this->api_url.$this->api_app_id.str_replace("//", "/", "/".$this->api_server_controller."/".$this->api_server_identifiers."/?".http_build_query($params))); 699 else 700 curl_setopt($this->Connection, CURLOPT_POSTFIELDS, json_encode($params)); 701 } 702 } 703 // encrypt 704 elseif ($this->api_encrypt) { 705 // empty 706 if(!is_array($params)) 707 $params = array(); 708 if(!is_array($this->api_server_identifiers)) 709 $this->api_server_identifiers = array(); 710 711 // join identifiers and parameters 712 $params = array_merge($this->api_server_identifiers, $params); 713 $params['controller'] = $this->api_server_controller; 714 715 // create encrypted request 716 $encrypted_request = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->api_key, json_encode($params), MCRYPT_MODE_ECB)); 717 718 // escape + 719 $encrypted_request = urlencode($encrypted_request); 720 721 // reset url 722 curl_setopt($this->Connection, CURLOPT_URL, $this->api_url."?app_id=".$this->api_app_id."&enc_request=".$encrypted_request); 723 } 724 } 725 726 /** 727 * Sets HTTP method to use for queries 728 * 729 * @access private 730 * @return void 731 */ 732 private function curl_set_http_method () { 733 curl_setopt($this->Connection, CURLOPT_CUSTOMREQUEST, $this->api_server_method); 734 } 735 736 /** 737 * Adds token to http header 738 * 739 * @access private 740 * @param mixed $token_file 741 * @return void 742 */ 743 private function curl_add_token_header ($token_file) { 744 if($token_file!==false) { 745 // open file and save token 746 $token = @file($token_file); 747 // save token 748 if(isset($token[0])) { 749 $this->token = trim($token[0]); 750 $this->token_expires = trim($token[1]); 751 // is token still valid ? 752 if (strlen($this->token)<2 && $this->token_expires < time()) { 753 // initiate authentication 754 $this->curl_authenticate (); 755 //save token to file 756 $this->write_token_file ($token_file); 757 } 758 } 759 else { 760 $this->curl_authenticate (); 761 //save token to file 762 $this->write_token_file ($token_file); 763 764 } 765 } 766 // token not saved, try to retrieve it 767 else { 768 $this->curl_authenticate (); 769 } 770 771 // add token to headers 772 $this->curl_add_http_header ("token", $this->token); 773 } 774 775 /** 776 * Adds http headers 777 * 778 * @access private 779 * @param mixed $name 780 * @param mixed $value 781 * @return void 782 */ 783 private function curl_add_http_header ($name, $value) { 784 $headers = array( 785 "Content-Type: application/json", 786 # https://stackoverflow.com/a/33550871 787 #'Connection: Keep-Alive', 788 "$name: $value" 789 ); 790 // save 791 curl_setopt($this->Connection, CURLOPT_HTTPHEADER, $headers); 792 } 793 794 /** 795 * Writes token to token file 796 * 797 * @access private 798 * @param mixed $filename 799 * @return void 800 */ 801 private function write_token_file ($filename) { 802 //save token 803 try { 804 $myfile = fopen($filename, "w"); 805 fwrite($myfile, $this->token); 806 fwrite($myfile, "\n"); 807 fwrite($myfile, $this->token_expires); 808 // close file 809 fclose($myfile); 810 } 811 catch ( Exception $e ) { 812 $this->exception("Cannot write file $filename"); 813 } 814 } 815 816 /** 817 * Removes token file if expired / invalid 818 * 819 * @access private 820 * @param mixed $token_file 821 * @return void 822 */ 823 private function delete_token_file ($token_file) { 824 //save token 825 try { 826 $myfile = fopen($token_file, "w"); 827 fwrite($myfile, ""); 828 // close file 829 fclose($myfile); 830 } 831 catch ( Exception $e ) { 832 $this->exception("Cannot write file $token_file"); 833 } 834 } 835 836 /** 837 * Executes request. 838 * 839 * @access private 840 * @return void 841 */ 842 private function curl_execute () { 843 // send request and save response 844 $resp = curl_exec($this->Connection); 845 846 // curl error check 847 if (curl_errno($this->Connection)) { 848 $this->exception("Curl error: ".curl_error($this->Connection)); 849 } 850 else { 851 // return result object 852 return json_decode($resp); 853 } 854 } 855 856 /** 857 * Store result code 858 * 859 * @method curl_save_result_code 860 * @return void 861 */ 862 private function curl_save_headers () { 863 // save result and result code 864 $this->response_headers = curl_getinfo($this->Connection); 865 } 866 867 /** 868 * send authenticate request and save token if provided, otherwise throw error. 869 * 870 * @access private 871 * @return void 872 */ 873 private function curl_authenticate () { 874 // Get cURL resource 875 $c_auth = curl_init(); 876 877 // set default curl options and params 878 curl_setopt_array($c_auth, array( 879 CURLOPT_RETURNTRANSFER => true, 880 CURLOPT_URL => $this->api_url.$this->api_app_id."/user/", 881 #CURLOPT_HEADER => true, 882 CURLOPT_HEADER => false, 883 # useless due to libcurl internal design 884 # https://bugs.php.net/bug.php?id=65348 885 #CURLOPT_VERBOSE => $this->debug, 886 CURLOPT_TIMEOUT => 9, 887 CURLOPT_USERAGENT => 'phpipam-api php class', 888 # https://stackoverflow.com/a/43277274 889 CURLOPT_ENCODING => 'gzip,deflate', 890 #CURLOPT_ENCODING => 'deflate', 891 // ssl 892 CURLOPT_SSL_VERIFYHOST => false, 893 CURLOPT_SSL_VERIFYPEER => false, 894 CURLOPT_POST => true, 895 # https://github.com/phpipam/phpipam-api-clients/issues/17 896 CURLOPT_POSTFIELDS => '', 897 # https:// 898 #CURLOPT_FOLLOWLOCATION => true, 899 # http://epic.grnet.gr/guides/api-auth/ 900 CURLOPT_USERPWD => $this->api_username.":".$this->api_password, 901 CURLOPT_HTTPAUTH => CURLAUTH_BASIC, 902 # https://stackoverflow.com/a/33550871 903 # https://github.com/phpipam/phpipam/issues/1191#issuecomment-309450838 904 CURLOPT_HTTPHEADER => array( 905 #'Content-Length: 0', 906 'Connection: Keep-Alive', 907 #'Content-Type: application/x-www-form-urlencoded;charset=UTF-8', 908 # charset => 415 : Invalid content type 909 'Content-Type: application/x-www-form-urlencoded', 910 ), 911 ) 912 ); 913 // send request and save response 914 $resp = curl_exec($c_auth); 915 916 // curl error check 917 #if (curl_errno($c_auth)) { 918 if ($resp===false) { 919 $this->exception("Curl error: ".curl_error($c_auth)); 920 } 921 else { 922 // return result object 923 $auth_resp = json_decode($resp); 924 // ok ? 925 if ($auth_resp->code == 200) { 926 if (isset($auth_resp->data->token)) { 927 // save token 928 $this->token = $auth_resp->data->token; 929 $this->token_expires = strtotime($auth_resp->data->expires); 930 } 931 else { 932 $this->exception("Cannot obtain access token"); 933 } 934 } 935 // error 936 else { 937 // save response 938 $this->result = $auth_resp; 939 } 940 } 941 #curl_close($c_auth); 942 } 943 944} 945 946/** modelines 947 * vi: tabstop=4 shifwidth=4 autoindent beautify 948 * vim: ff=unix ts=4 sts=4 sw=4 ai et sta fenc=utf-8 bf ft=php 949 * atom:set useSoftTabs tabLength=4 lineending=lf encoding=utf-8 950 * -*- Mode: tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- 951 */ 952?> 953