diff --git a/README.md b/README.md index 55f59c2..a7ef7aa 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Example - Return status array for each Proxmox Host in this cluster. require_once("./pve2-api-php-client/pve2_api.class.php"); # You can try/catch exception handle the constructor here if you want. + + ## EXAMPLE: Login with username and password ## $pve2 = new PVE2_API("hostname", "username", "realm", "password"); # realm above can be pve, pam or any other realm available. @@ -28,6 +30,22 @@ Example - Return status array for each Proxmox Host in this cluster. exit; } + + ## EXAMPLE: Login with API Token ## + $pve2 = new PVE2_API("hostname", "username", "realm", "TOKENID:TOKENVALUE"); + # realm above can be pve, pam or any other realm available. + + if ($pve2->login()) { + foreach ($pve2->get_node_list() as $node_name) { + print_r($pve2->get("/nodes/".$node_name."/status")); + } + } else { + print("Login to Proxmox Host failed.\n"); + exit; + } + + + Example - Create a new Linux Container (LXC) on the first host in the cluster. require_once("./pve2-api-php-client/pve2_api.class.php"); diff --git a/pve2_api.class.php b/pve2_api.class.php index 2e206b1..1a6a565 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -62,6 +62,14 @@ public function __construct ($hostname, $username, $realm, $password, $port = 80 $this->password = $password; $this->port = $port; $this->verify_ssl = $verify_ssl; + + // parse password if there a : in password, it seems to be token authentification + $password_parser = explode(":", $this->password); + + if(count($password_parser) >= 2){ + $this->authToken = "{$this->username}@{$this->realm}!{$password_parser[0]}={$password_parser[1]}"; + } + } /* @@ -69,56 +77,83 @@ public function __construct ($hostname, $username, $realm, $password, $port = 80 * Performs login to PVE Server using JSON API, and obtains Access Ticket. */ public function login () { - // Prepare login variables. - $login_postfields = array(); - $login_postfields['username'] = $this->username; - $login_postfields['password'] = $this->password; - $login_postfields['realm'] = $this->realm; - $login_postfields_string = http_build_query($login_postfields); - unset($login_postfields); + if(!$this->authToken){ - // Perform login request. - $prox_ch = curl_init(); - curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket"); - curl_setopt($prox_ch, CURLOPT_POST, true); - curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl); + // Prepare login variables. + $login_postfields = array(); + $login_postfields['username'] = $this->username; + $login_postfields['password'] = $this->password; + $login_postfields['realm'] = $this->realm; - $login_ticket = curl_exec($prox_ch); - $login_request_info = curl_getinfo($prox_ch); + $login_postfields_string = http_build_query($login_postfields); + unset($login_postfields); - curl_close($prox_ch); - unset($prox_ch); - unset($login_postfields_string); - if (!$login_ticket) { - // SSL negotiation failed or connection timed out - $this->login_ticket_timestamp = null; - return false; - } + // Perform login request. + $prox_ch = curl_init(); + curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket"); + curl_setopt($prox_ch, CURLOPT_POST, true); + curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl); - $login_ticket_data = json_decode($login_ticket, true); - if ($login_ticket_data == null || $login_ticket_data['data'] == null) { - // Login failed. - // Just to be safe, set this to null again. - $this->login_ticket_timestamp = null; - if ($login_request_info['ssl_verify_result'] == 1) { - throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); + $login_ticket = curl_exec($prox_ch); + $login_request_info = curl_getinfo($prox_ch); + + curl_close($prox_ch); + unset($prox_ch); + unset($login_postfields_string); + + if (!$login_ticket) { + // SSL negotiation failed or connection timed out + $this->login_ticket_timestamp = null; + return false; } - return false; - } else { - // Login success. - $this->login_ticket = $login_ticket_data['data']; - // We store a UNIX timestamp of when the ticket was generated here, - // so we can identify when we need a new one expiration-wise later - // on... - $this->login_ticket_timestamp = time(); - $this->reload_node_list(); - return true; + + $login_ticket_data = json_decode($login_ticket, true); + if ($login_ticket_data == null || $login_ticket_data['data'] == null) { + // Login failed. + // Just to be safe, set this to null again. + $this->login_ticket_timestamp = null; + if ($login_request_info['ssl_verify_result'] == 1) { + throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); + } + return false; + } else { + // Login success. + $this->login_ticket = $login_ticket_data['data']; + // We store a UNIX timestamp of when the ticket was generated here, + // so we can identify when we need a new one expiration-wise later + // on... + $this->login_ticket_timestamp = time(); + $this->reload_node_list(); + return true; + } + + }else{ + + $prox_ch = curl_init(); + curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/version"); + curl_setopt($prox_ch, CURLOPT_POST, false); + curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($prox_ch, CURLOPT_HTTPHEADER, array("Authorization: PVEAPIToken={$this->authToken}")) ; + + $login_ticket = curl_exec($prox_ch); + $login_ticket_data = json_decode($login_ticket, true); + + if(!$login_ticket_data["data"]["version"]){ + return false; + }else{ + return true; + } + } + + } # Sets the PVEAuthCookie @@ -138,6 +173,7 @@ public function setCookie() { * Method of checking is purely by age of ticket right now... */ protected function check_login_ticket () { + if ($this->login_ticket == null) { // Just to be safe, set this to null again. $this->login_ticket_timestamp = null; @@ -163,17 +199,29 @@ private function action ($action_path, $http_method, $put_post_parameters = null if (substr($action_path, 0, 1) != "/") { $action_path = "/".$action_path; } + + + // Prepare cURL resource. + $prox_ch = curl_init(); + + $put_post_http_headers = array(); + + if(!$this->authToken){ + + if (!$this->check_login_ticket()) { + throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3); + } - if (!$this->check_login_ticket()) { - throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3); + $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}"; + curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']); + + }else{ + $put_post_http_headers[] = "Authorization: PVEAPIToken={$this->authToken}"; } - // Prepare cURL resource. - $prox_ch = curl_init(); + // generate API URL curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}"); - $put_post_http_headers = array(); - $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}"; // Lets decide what type of action we are taking... switch ($http_method) { case "GET": @@ -186,9 +234,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null $action_postfields_string = http_build_query($put_post_parameters); curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); unset($action_postfields_string); - - // Add required HTTP headers. - curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); + break; case "POST": curl_setopt($prox_ch, CURLOPT_POST, true); @@ -213,9 +259,12 @@ private function action ($action_path, $http_method, $put_post_parameters = null return false; } + // Add required HTTP headers. + curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); + curl_setopt($prox_ch, CURLOPT_HEADER, true); curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false);