Compare commits

...

5 Commits

Author SHA1 Message Date
malle-pietje
021d01ba86 API client class v1.1.68
- fixed a bug that was introduced with 1.1.67 and would only occur in certain corner cases
2021-01-24 18:09:38 +01:00
malle-pietje
caf838abb9 API client class v1.1.67
- fixed a bug where the request headers for subsequent function calls within the same Client instance would not always be cleared
2021-01-24 17:58:23 +01:00
malle-pietje
aa778c9b7b API client class v1.1.66
- simplified code based on Scrutinizer reports
2021-01-24 14:21:26 +01:00
malle-pietje
0e9ee66cef updated README to include new functions/methods 2021-01-23 11:44:12 +01:00
malle-pietje
721ba7d084 API client class v1.1.65
- applied minor improvements based on Scrutinizer reports
- applied minor changes to `authorize_guest()` to further improve handling of 0/null/empty values passed
- restricted protocols to be used by cURL to only allow http and https for improved security
- added methods/functions `list_device_name_mappings()` and `stat_full_status()`
2021-01-23 11:41:52 +01:00
2 changed files with 146 additions and 98 deletions

View File

@@ -4,6 +4,7 @@ A PHP class that provides access to Ubiquiti's [**UniFi Network Controller**](ht
The package can be installed manually or by using composer/[packagist](https://packagist.org/packages/art-of-wifi/unifi-api-client) for easy inclusion in your projects.
## Requirements
- a server with PHP, version 5.5.0 or higher, and the PHP cURL module installed (tested on Apache 2.4 with PHP Version 5.6.1 and cURL 7.42.1 and with PHP 7.2.24 and cURL 7.58.0)
@@ -17,10 +18,12 @@ Support for UniFi OS-based controllers (UniFi Dream Machine Pro or Cloud Key Gen
Please test all methods you plan on using thoroughly before using the API Client with UniFi OS devices in a production environment.
## Installation
Use [Composer](#composer), [Git](#git) or simply [Download the Release](#download-the-release) to install the API client class.
### Composer
The preferred installation method is through [composer](https://getcomposer.org). Follow these [installation instructions](https://getcomposer.org/doc/00-intro.md) if you do not already have composer installed.
@@ -47,6 +50,7 @@ Finally, be sure to include the autoloader in your code:
require_once 'vendor/autoload.php';
```
### Git
Execute the following `git` command from the shell in your project directory:
@@ -61,6 +65,7 @@ When git is done cloning, include the file containing the class like so in your
require_once 'path/to/src/Client.php';
```
### Download the Release
If you prefer not to use composer or git, simply [download the package](https://github.com/Art-of-WiFi/UniFi-API-client/archive/master.zip), uncompress the zip file, then include the file containing the class in your code like so:
@@ -69,6 +74,7 @@ If you prefer not to use composer or git, simply [download the package](https://
require_once 'path/to/src/Client.php';
```
## Example usage
A basic example how to use the class:
@@ -90,6 +96,7 @@ $results = $unifi_connection->list_alarms(); // returns a PHP array con
Please refer to the `examples/` directory for some more detailed examples which can be used as a starting point for your own PHP code.
#### IMPORTANT NOTES:
1. In the above example, `$site_id` is the short site "name" (usually 8 characters long) that is visible in the URL when managing the site in the UniFi Network Controller. For example with this URL:
@@ -98,7 +105,8 @@ Please refer to the `examples/` directory for some more detailed examples which
`jl3z2shm` is the short site "name" and the value to assign to $site_id.
2. The last optional parameter that is passed to the constructor in the above example (`true`), enables validation of the controller's SSL certificate which is otherwise **disabled** by default. It is highly recommended to enable this feature in production environments where you have a valid SSL cert installed on the UniFi Controller that is associated with the FQDN in the `controller_url` parameter. This option was added with API client version 1.1.16.
2. The last optional parameter that is passed to the constructor in the above example (`true`), enables validation of the controller's SSL certificate which is otherwise **disabled** by default. It is **highly recommended** to enable this feature in production environments where you have a valid SSL cert installed on the UniFi Controller that is associated with the FQDN in the `controller_url` parameter. This option was added with API client version 1.1.16.
## Functions/methods supported
@@ -156,6 +164,7 @@ The class currently supports the following functions/methods to GET/POST/PUT/DEL
- list_current_channels()
- list_dashboard()
- list_devices()
- list_device_name_mappings()
- list_dpi_stats()
- list_dynamicdns()
- list_events()
@@ -247,6 +256,7 @@ The class currently supports the following functions/methods to GET/POST/PUT/DEL
- stat_speedtest_results()
- stat_sta_sessions_latest()
- stat_status()
- stat_full_status()
- stat_sysinfo()
- stat_voucher()
- stat_monthly_aps()
@@ -287,10 +297,12 @@ Other functions, getters/setters:
There is still work to be done to add functionality and further improve the usability of this class, so all suggestions/comments are welcome. Please use the GitHub [issue list](https://github.com/Art-of-WiFi/UniFi-API-client/issues) or the Ubiquiti Community forums (https://community.ubnt.com/t5/UniFi-Wireless/PHP-class-to-access-the-UniFi-controller-API-updates-and/td-p/1512870) to share your suggestions and questions.
## Contribute
If you would like to contribute code (improvements), please open an issue and include your code there or else create a pull request.
## Credits
This class is based on the initial work by the following developers:
@@ -302,6 +314,7 @@ and the API as published by Ubiquiti:
- https://dl.ui.com/unifi/6.0.41/unifi_sh_api
## Important Disclaimer
Many of the functions in this API client class are not officially supported by UBNT and as such, may not be supported in future versions of the UniFi Controller API.

View File

@@ -12,7 +12,7 @@ namespace UniFi_API;
*
* @package UniFi_Controller_API_Client_Class
* @author Art of WiFi <info@artofwifi.net>
* @version Release: 1.1.64
* @version Release: 1.1.68
* @license This class is subject to the MIT license that is bundled with this package in the file LICENSE.md
* @example This directory in the package repository contains a collection of examples:
* https://github.com/Art-of-WiFi/UniFi-API-client/tree/master/examples
@@ -20,27 +20,27 @@ namespace UniFi_API;
class Client
{
/**
* protected and private properties
* private and protected properties
*/
protected $baseurl = 'https://127.0.0.1:8443';
protected $user = '';
protected $password = '';
protected $site = 'default';
protected $version = '6.0.43';
protected $debug = false;
protected $is_loggedin = false;
protected $is_unifi_os = false;
protected $exec_retries = 0;
protected $class_version = '1.1.64';
private $cookies = '';
private $headers = [];
private $request_method = 'GET';
private $request_methods_allowed = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
private $connect_timeout = 10;
private $last_results_raw = null;
private $last_error_message = null;
private $curl_ssl_verify_peer = false;
private $curl_ssl_verify_host = false;
private $class_version = '1.1.68';
protected $baseurl = 'https://127.0.0.1:8443';
protected $user = '';
protected $password = '';
protected $site = 'default';
protected $version = '6.0.43';
protected $debug = false;
protected $curl_ssl_verify_peer = false;
protected $curl_ssl_verify_host = false;
protected $is_loggedin = false;
protected $is_unifi_os = false;
protected $exec_retries = 0;
protected $cookies = '';
protected $headers = [];
protected $request_method = 'GET';
protected $request_methods_allowed = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
protected $connect_timeout = 10;
protected $last_results_raw = null;
protected $last_error_message = null;
/**
* Construct an instance of the UniFi API client class
@@ -314,19 +314,19 @@ class Client
/**
* if we have received values for up/down/megabytes/ap_mac we append them to the payload array to be submitted
*/
if (!is_null($up)) {
if (!empty($up)) {
$payload['up'] = intval($up);
}
if (!is_null($down)) {
if (!empty($down)) {
$payload['down'] = intval($down);
}
if (!is_null($megabytes)) {
if (!empty($megabytes)) {
$payload['bytes'] = intval($megabytes);
}
if (!is_null($ap_mac)) {
if (!empty($ap_mac) && filter_var($ap_mac, FILTER_VALIDATE_MAC)) {
$payload['ap_mac'] = strtolower($ap_mac);
}
@@ -1193,7 +1193,7 @@ class Client
/**
* Fetch AP groups
*
* @return array returns an array containing the current AP groups on success
* @return array containing the current AP groups on success
*/
public function list_apgroups()
{
@@ -1255,7 +1255,7 @@ class Client
* @param string $group_id optional, _id value of the single firewall group to list
* @return array containing the current firewall groups or the selected firewall group on success
*/
public function list_firewallgroups($group_id = null)
public function list_firewallgroups($group_id = '')
{
return $this->fetch_results('/api/s/' . $this->site . '/rest/firewallgroup/' . trim($group_id));
}
@@ -1632,7 +1632,7 @@ class Client
/**
* Fetch admins
*
* @return array containing administrator objects for selected site
* @return array containing administrator objects for selected site
*/
public function list_admins()
{
@@ -1644,7 +1644,7 @@ class Client
/**
* Fetch all admins
*
* @return array containing administrator objects for all sites
* @return array containing administrator objects for all sites
*/
public function list_all_admins()
{
@@ -1772,7 +1772,7 @@ class Client
/**
* Fetch wlan_groups
*
* @return array containing known wlan_groups
* @return array containing known wlan_groups
*/
public function list_wlan_groups()
{
@@ -1782,7 +1782,7 @@ class Client
/**
* Fetch sysinfo
*
* @return array containing known sysinfo data
* @return array containing known sysinfo data
*/
public function stat_sysinfo()
{
@@ -1792,8 +1792,8 @@ class Client
/**
* Fetch controller status
*
* NOTES: in order to get useful results (e.g. controller version) you can call get_last_results_raw()
* immediately after this method. Login not required.
* NOTES:
* login not required
*
* @return bool true upon success (controller is online)
*/
@@ -1802,10 +1802,40 @@ class Client
return $this->fetch_results_boolean('/status', null, false);
}
/**
* Fetch full controller status
*
* NOTES:
* login not required
*
* @return bool|array staus array upon success, false upon failure
*/
public function stat_full_status()
{
$this->fetch_results_boolean('/status', null, false);
return json_decode($this->get_last_results_raw());
}
/**
* Fetch device name mappings
*
* NOTES:
* login not required
*
* @return bool|array mappings array upon success, false upon failure
*/
public function list_device_name_mappings()
{
$this->fetch_results_boolean('/dl/firmware/bundles.json', null, false);
return json_decode($this->get_last_results_raw());
}
/**
* Fetch self
*
* @return array containing information about the logged in user
* @return array containing information about the logged in user
*/
public function list_self()
{
@@ -1820,7 +1850,7 @@ class Client
*/
public function stat_voucher($create_time = null)
{
$payload = trim($create_time) != null ? ['create_time' => intval($create_time)] : [];
$payload = isset($create_time) ? ['create_time' => intval($create_time)] : [];
return $this->fetch_results('/api/s/' . $this->site . '/stat/voucher', $payload);
}
@@ -1833,7 +1863,7 @@ class Client
*/
public function stat_payment($within = null)
{
$path_suffix = $within != null ? '?within=' . intval($within) : '';
$path_suffix = isset($within) ? '?within=' . intval($within) : '';
return $this->fetch_results('/api/s/' . $this->site . '/stat/payment' . $path_suffix);
}
@@ -1849,7 +1879,7 @@ class Client
public function create_hotspotop($name, $x_password, $note = null)
{
$payload = ['name' => $name, 'x_password' => $x_password];
if (!is_null($note)) {
if (!isset($note)) {
$payload['note'] = trim($note);
}
@@ -1859,7 +1889,7 @@ class Client
/**
* Fetch hotspot operators (using REST)
*
* @return array containing hotspot operators
* @return array containing hotspot operators
*/
public function list_hotspotop()
{
@@ -1874,7 +1904,7 @@ class Client
* @param int $minutes minutes the voucher is valid after activation (expiration time)
* @param int $count number of vouchers to create, default value is 1
* @param int $quota single-use or multi-use vouchers, value '0' is for multi-use, '1' is for single-use,
* 'n' is for multi-use n times
* 'n' is for multi-use n times
* @param string $note note text to add to voucher when printing
* @param int $up upload speed limit in kbps
* @param int $down download speed limit in kbps
@@ -1883,11 +1913,11 @@ class Client
*/
public function create_voucher(
$minutes,
$count = 1,
$quota = 0,
$note = null,
$up = null,
$down = null,
$count = 1,
$quota = 0,
$note = null,
$up = null,
$down = null,
$megabytes = null
) {
$payload = [
@@ -1945,7 +1975,7 @@ class Client
/**
* Fetch port forwarding stats
*
* @return array containing port forwarding stats
* @return array containing port forwarding stats
*/
public function list_portforward_stats()
{
@@ -1955,7 +1985,7 @@ class Client
/**
* Fetch DPI stats
*
* @return array containing DPI stats
* @return array containing DPI stats
*/
public function list_dpi_stats()
{
@@ -1989,7 +2019,7 @@ class Client
/**
* Fetch current channels
*
* @return array containing currently allowed channels
* @return array containing currently allowed channels
*/
public function list_current_channels()
{
@@ -2003,7 +2033,7 @@ class Client
* these codes following the ISO standard:
* https://en.wikipedia.org/wiki/ISO_3166-1_numeric
*
* @return array containing available country codes
* @return array containing available country codes
*/
public function list_country_codes()
{
@@ -2013,7 +2043,7 @@ class Client
/**
* Fetch port forwarding settings
*
* @return array containing port forwarding settings
* @return array containing port forwarding settings
*/
public function list_portforwarding()
{
@@ -2023,7 +2053,7 @@ class Client
/**
* Fetch port configurations
*
* @return array containing port configurations
* @return array containing port configurations
*/
public function list_portconf()
{
@@ -2033,7 +2063,7 @@ class Client
/**
* Fetch VoIP extensions
*
* @return array containing VoIP extensions
* @return array containing VoIP extensions
*/
public function list_extension()
{
@@ -2043,7 +2073,7 @@ class Client
/**
* Fetch site settings
*
* @return array containing site configuration settings
* @return array containing site configuration settings
*/
public function list_settings()
{
@@ -2401,7 +2431,7 @@ class Client
/**
* Fetch dynamic DNS settings (using REST)
*
* @return array containing dynamic DNS settings
* @return array containing dynamic DNS settings
*/
public function list_dynamicdns()
{
@@ -3041,7 +3071,7 @@ class Client
}
/**
* Toggle Element Adoption ON or OFF
* Toggle Element Adoption ON or OFF
*
* @param bool $enable true enables Element Adoption, false disables Element Adoption
* @return bool true on success
@@ -3472,7 +3502,7 @@ class Client
}
/****************************************************************
* internal (private and protected) functions from here:
* private and protected functions from here:
****************************************************************/
/**
@@ -3496,44 +3526,48 @@ class Client
return false;
}
$response = json_decode($this->exec_curl($path, $payload));
$this->catch_json_last_error();
$this->last_results_raw = $response;
if (isset($response->meta->rc)) {
if ($response->meta->rc === 'ok') {
$this->last_error_message = null;
if (is_array($response->data) && !$boolean) {
return $response->data;
}
$this->last_results_raw = $this->exec_curl($path, $payload);
return true;
} elseif ($response->meta->rc === 'error') {
/**
* we have an error:
* set $this->set last_error_message if the returned error message is available
*/
if (isset($response->meta->msg)) {
$this->last_error_message = $response->meta->msg;
if ($this->debug) {
trigger_error('Debug: Last error message: ' . $this->last_error_message);
if (is_string($this->last_results_raw)) {
$response = json_decode($this->last_results_raw);
$this->catch_json_last_error();
if (isset($response->meta->rc)) {
if ($response->meta->rc === 'ok') {
$this->last_error_message = null;
if (is_array($response->data) && !$boolean) {
return $response->data;
}
return true;
} elseif ($response->meta->rc === 'error') {
/**
* we have an error:
* set $this->set last_error_message if the returned error message is available
*/
if (isset($response->meta->msg)) {
$this->last_error_message = $response->meta->msg;
if ($this->debug) {
trigger_error('Debug: Last error message: ' . $this->last_error_message);
}
}
}
}
}
/**
* to deal with a response coming from the new v2 API
*/
if (strpos($path, '/v2/api/') === 0) {
if (isset($response->errorCode)) {
if (isset($response->message)) {
$this->last_error_message = $response->message;
if ($this->debug) {
trigger_error('Debug: Last error message: ' . $this->last_error_message);
/**
* to deal with a response coming from the new v2 API
*/
if (strpos($path, '/v2/api/') === 0) {
if (isset($response->errorCode)) {
if (isset($response->message)) {
$this->last_error_message = $response->message;
if ($this->debug) {
trigger_error('Debug: Last error message: ' . $this->last_error_message);
}
}
} else {
return $response;
}
} else {
return $response;
}
}
@@ -3560,7 +3594,7 @@ class Client
*
* @return bool returns true upon success, false upon failure
*/
private function catch_json_last_error()
protected function catch_json_last_error()
{
if ($this->debug) {
switch (json_last_error()) {
@@ -3622,7 +3656,7 @@ class Client
* @param string $baseurl the base URL to validate
* @return bool true if base URL is a valid URL, else returns false
*/
private function check_base_url($baseurl)
protected function check_base_url($baseurl)
{
if (!filter_var($baseurl, FILTER_VALIDATE_URL) || substr($baseurl, -1) === '/') {
trigger_error('The URL provided is incomplete, invalid or ends with a / character!');
@@ -3639,7 +3673,7 @@ class Client
* @param string $site the (short) site name to check
* @return bool true if (short) site name is valid, else returns false
*/
private function check_site($site)
protected function check_site($site)
{
if ($this->debug && preg_match("/\s/", $site)) {
trigger_error('The provided (short) site name may not contain any spaces');
@@ -3658,7 +3692,7 @@ class Client
*
* @return bool true when unificookie was updated, else returns false
*/
private function update_unificookie()
protected function update_unificookie()
{
if (session_status() === PHP_SESSION_ACTIVE && isset($_SESSION['unificookie']) && !empty($_SESSION['unificookie'])) {
$this->cookies = $_SESSION['unificookie'];
@@ -3681,7 +3715,7 @@ class Client
*
* @return bool true upon success or false when unable to extract the CSRF token
*/
private function create_x_csrf_token_header()
protected function create_x_csrf_token_header()
{
if (!empty($this->cookies)) {
$cookie_bits = explode('=', $this->cookies);
@@ -3711,7 +3745,7 @@ class Client
*
* @param string $path path for the request
* @param object|array $payload optional, payload to pass with the request
* @return bool|array response returned by the controller API, false upon error
* @return bool|array|string response returned by the controller API, false upon error
*/
protected function exec_curl($path, $payload = null)
{
@@ -3720,12 +3754,13 @@ class Client
}
if (!($ch = $this->get_curl_resource())) {
trigger_error('$ch as returned by get_curl_resource() is not a resource');
trigger_error('get_curl_resource() did not return a resource');
return false;
}
$json_payload = '';
$this->headers = [];
$json_payload = '';
if ($this->is_unifi_os) {
$url = $this->baseurl . '/proxy/network' . $path;
@@ -3743,9 +3778,8 @@ class Client
/**
* what we do when a payload is passed
*/
if (!is_null($payload)) {
if (!empty($payload)) {
$json_payload = json_encode($payload, JSON_UNESCAPED_SLASHES);
$curl_options[CURLOPT_POST] = true;
$curl_options[CURLOPT_POSTFIELDS] = $json_payload;
$this->headers = [
@@ -3876,13 +3910,14 @@ class Client
/**
* Create a new cURL resource and return a cURL handle
*
* @return object|bool cURL handle upon success, false upon failure
* @return object|bool|resource cURL handle upon success, false upon failure
*/
protected function get_curl_resource()
{
$ch = curl_init();
if (is_object($ch) || is_resource($ch)) {
$curl_options = [
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
CURLOPT_SSL_VERIFYPEER => $this->curl_ssl_verify_peer,
CURLOPT_SSL_VERIFYHOST => $this->curl_ssl_verify_host,
CURLOPT_CONNECTTIMEOUT => $this->connect_timeout,