Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UniFi Network Application API #626

Closed
SinisterSpatula opened this issue Apr 16, 2023 · 24 comments
Closed

UniFi Network Application API #626

SinisterSpatula opened this issue Apr 16, 2023 · 24 comments

Comments

@SinisterSpatula
Copy link
Contributor

SinisterSpatula commented Apr 16, 2023

Hello, the current UniFi code doesn't work well with UniFi Network Application due to specific differences in it's API:
the login route must use: /api/login (not /api/auth/login). The health route must use: /api/s/default/stat/health (not /proxy/network). There is no data for wan availability or lan availability on the health route (at least for my use case, I'm running the Network Application within an LXC container under proxmox). I found "uptime" available from route: /api/s/default/stat/sysinfo. I'm running UniFi Network Application ver 7.3.83 under linux (LXC).

@SinisterSpatula
Copy link
Contributor Author

output of /api/s/default/stat/health:

"data": [
    {
      "subsystem": "wlan",
      "num_user": 16,
      "num_guest": 0,
      "num_iot": 0,
      "tx_bytes-r": 625557,
      "rx_bytes-r": 12012,
      "status": "ok",
      "num_ap": 2,
      "num_adopted": 2,
      "num_disabled": 0,
      "num_disconnected": 0,
      "num_pending": 0
    },
    {
      "subsystem": "wan",
      "num_gw": 0,
      "num_adopted": 0,
      "num_disconnected": 0,
      "num_pending": 0,
      "status": "unknown"
    },
    {
      "subsystem": "www",
      "status": "unknown"
    },
    {
      "subsystem": "lan",
      "status": "unknown",
      "num_sw": 0,
      "num_adopted": 0,
      "num_disconnected": 0,
      "num_pending": 0
    },
    {
      "subsystem": "vpn",
      "status": "unknown"
    }
  ]
}

ouput of /api/s/default/stat/sysinfo:

"data": [
    {
      "timezone": "America/Chicago",
      "autobackup": false,
      "build": "atag_7.3.83_19645",
      "version": "7.3.83",
      "data_retention_days": 90,
      "data_retention_time_in_hours_for_5minutes_scale": 24,
      "data_retention_time_in_hours_for_hourly_scale": 168,
      "data_retention_time_in_hours_for_daily_scale": 2160,
      "data_retention_time_in_hours_for_monthly_scale": 8760,
      "data_retention_time_in_hours_for_others": 2160,
      "update_available": false,
      "update_downloaded": false,
      "live_chat": "super-only",
      "store_enabled": "super-only",
      "hostname": "example-domain.ui.com",
      "name": "x.home.arpa",
      "ip_addrs": [
        "10.x.x.x"
      ],
      "inform_port": 8080,
      "https_port": 8443,
      "override_inform_host": false,
      "image_maps_use_google_engine": false,
      "radius_disconnect_running": false,
      "facebook_wifi_registered": false,
      "sso_app_id": "redacted",
      "sso_app_sec": "redacted",
      "uptime": 81997,
      "anonymous_controller_id": "redacted",
      "has_webrtc_support": true,
      "debug_setting_preference": "auto",
      "debug_mgmt": "warn",
      "debug_system": "warn",
      "debug_device": "warn",
      "debug_sdn": "warn",
      "unsupported_device_count": 0,
      "unsupported_device_list": [],
      "unifi_go_enabled": false,
      "default_site_device_auth_password_alert": false
    }
  ]
}

@SinisterSpatula
Copy link
Contributor Author

There is also:
/v2/api/site/default/network_status
with output of:

{
  "average_satisfaction": 98,
  "health": "excellent",
  "historical_satisfaction": [
    98,
    98,
    98,
    98,
    98,
    98,
    99,
    98,
    98,
    98,
    98,
    98
  ],
  "reasons": []
}

I think average_satisfaction would be a good one to report on.

@keriati keriati transferred this issue from linuxserver/Heimdall Apr 26, 2023
@vampywiz17
Copy link

@SinisterSpatula

I try to change API endpoints locally, now it connect it, but not get any data. with browser, i able to read the data that Heimdall want to show it.

@igorkulman
Copy link

Trying this also, changing the endpoint reports a successful login but I get no data changing the health endpoint. Looking at the code and the health endpoint data I would say everything expect for the uptime should work, the name of the properties (num_user, etc) look the same.

@igorkulman
Copy link

igorkulman commented Jul 24, 2023

With a bit of trial an error I was able to get to this hardcoding $data['wan_avail'] = 100 changing $data['lan_users'] = isset($detail->num_user) ? $detail->num_user : 0 ; (I have no lan users so the property is missing)

Screenshot 2023-07-24 at 11 44 45

here are my changes to ./config/www/SupportedApps/UniFi/UniFi.php

<?php namespace App\SupportedApps\UniFi;

/**
 * Implementation based on
 * https://ubntwiki.com/products/software/unifi-controller/api
 */
class UniFi extends \App\SupportedApps
{
	public $config;

	protected $method = 'POST';

	function __construct()
	{
		$this->jar = new \GuzzleHttp\Cookie\CookieJar;
	}

	public function test()
	{
		$test = parent::appTest(
			$this->url("/api/login"),
			$this->getLoginAttributes(),
        );

		echo $test->status;
	}

	public function livestats()
	{
		$status = "inactive";

		parent::execute(
			$this->url("/api/login"),
			$this->getLoginAttributes(),
			null,
			'POST'
		);

		$res = parent::execute(
			$this->url("/api/s/default/stat/health"),
			$this->getAttributes(),
			null,
			'GET'
		);

		$details = json_decode($res->getBody());

		$data = [];

		if (isset($details->data)) {
			$data['error'] = false;
			foreach ($details->data as $key => $detail) {
				if ($detail->subsystem === 'wlan') {
					$data['wlan_users'] = isset($detail->num_user) ? $detail->num_user : 0;
					$data['wlan_ap'] = isset($detail->num_ap) ? $detail->num_ap : 0;
					$data['wlan_dc'] = isset($detail->num_disconnected) ? $detail->num_disconnected : 0;
				}

				if ($detail->subsystem === 'lan') {
					$data['lan_users'] = isset($detail->num_user) ? $detail->num_user : 0;
				}

				if ($detail->subsystem === 'wan') {
					$data['wan_avail'] = 100; //$detail->uptime_stats->WAN->availability;
				}
			}
		} else {
			$data['error'] = true;
		}

		return parent::getLiveStats($status, $data);
	}

	public function url($endpoint)
	{
		$url = parse_url(parent::normaliseurl($this->config->url));
		$scheme = $url["scheme"];
		$domain = $url["host"];
		$port = isset($url["port"]) ? $url["port"] : "443";

		$api_url =
			$scheme .
			"://" .
			$domain .
			":" .
			$port .
			$endpoint;

		return $api_url;
	}

	public function getConfigValue($key, $default = null)
	{
		return isset($this->config) && isset($this->config->$key)
			? $this->config->$key
			: $default;
	}

	public function getLoginAttributes()
	{
		$ignoreTls = $this->getConfigValue("ignore_tls", false);
		$username = $this->config->username;
		$password = $this->config->password;

		$body = [
			"username" => $username,
			"password" => $password,
		];

		$attrs = [
			"body" => json_encode($body),
			"cookies" => $this->jar,
			"headers" => [
				"Content-Type" => "application/json"
			]
		];

		if ($ignoreTls) {
			$attrs["verify"] = false;
		}

		return $attrs;
	}

	public function getAttributes()
	{
		$attrs = [
			"cookies" => $this->jar,
		];

		$ignoreTls = $this->getConfigValue("ignore_tls", false);

		if ($ignoreTls) {
			$attrs["verify"] = false;
		}

		return $attrs;
	}
}

@igorkulman
Copy link

@vampywiz17 @SinisterSpatula

I think I fixed it completly, try this, seems to work for me fine

Screenshot 2023-07-24 at 12 01 56
<?php namespace App\SupportedApps\UniFi;

/**
 * Implementation based on
 * https://ubntwiki.com/products/software/unifi-controller/api
 */
class UniFi extends \App\SupportedApps
{
	public $config;

	protected $method = 'POST';

	function __construct()
	{
		$this->jar = new \GuzzleHttp\Cookie\CookieJar;
	}

	public function test()
	{
		$test = parent::appTest(
			$this->url("/api/login"),
			$this->getLoginAttributes(),
        );

		echo $test->status;
	}

	public function livestats()
	{
		$status = "inactive";

		parent::execute(
			$this->url("/api/login"),
			$this->getLoginAttributes(),
			null,
			'POST'
		);

		$res = parent::execute(
			$this->url("/api/s/default/stat/health"),
			$this->getAttributes(),
			null,
			'GET'
		);

               $res_sat = parent::execute(
			$this->url("/v2/api/site/default/network_status"),
			$this->getAttributes(),
			null,
			'GET'
		);

		$details = json_decode($res->getBody());
                $details_sat = json_decode($res_sat->getBody());

		$data = [];

		if (isset($details->data)) {
			$data['error'] = false;
			foreach ($details->data as $key => $detail) {
				if ($detail->subsystem === 'wlan') {
					$data['wlan_users'] = isset($detail->num_user) ? $detail->num_user : 0;
					$data['wlan_ap'] = isset($detail->num_ap) ? $detail->num_ap : 0;
					$data['wlan_dc'] = isset($detail->num_disconnected) ? $detail->num_disconnected : 0;
				}

				if ($detail->subsystem === 'lan') {
					$data['lan_users'] = isset($detail->num_user) ? $detail->num_user : 0;
				}
			}
		} else {
			$data['error'] = true;
		}

               $data['wan_avail'] = isset($details_sat->average_satisfaction) ? $details_sat->average_satisfaction : 0;

		return parent::getLiveStats($status, $data);
	}

	public function url($endpoint)
	{
		$url = parse_url(parent::normaliseurl($this->config->url));
		$scheme = $url["scheme"];
		$domain = $url["host"];
		$port = isset($url["port"]) ? $url["port"] : "443";

		$api_url =
			$scheme .
			"://" .
			$domain .
			":" .
			$port .
			$endpoint;

		return $api_url;
	}

	public function getConfigValue($key, $default = null)
	{
		return isset($this->config) && isset($this->config->$key)
			? $this->config->$key
			: $default;
	}

	public function getLoginAttributes()
	{
		$ignoreTls = $this->getConfigValue("ignore_tls", false);
		$username = $this->config->username;
		$password = $this->config->password;

		$body = [
			"username" => $username,
			"password" => $password,
		];

		$attrs = [
			"body" => json_encode($body),
			"cookies" => $this->jar,
			"headers" => [
				"Content-Type" => "application/json"
			]
		];

		if ($ignoreTls) {
			$attrs["verify"] = false;
		}

		return $attrs;
	}

	public function getAttributes()
	{
		$attrs = [
			"cookies" => $this->jar,
		];

		$ignoreTls = $this->getConfigValue("ignore_tls", false);

		if ($ignoreTls) {
			$attrs["verify"] = false;
		}

		return $attrs;
	}
}

@vampywiz17
Copy link

@igorkulman thanks!

@gitterdoneplease
Copy link

Works perfectly. Thanks. Will this get pulled into the source?

@igorkulman
Copy link

Thanks for testing, I have opened a PR with this #645

@nerakhon
Copy link
Contributor

Have you guys tested this with a UDM or other unifi HW? Because per my PR, the only problem there was the decimals. (I am running the lastest firmware). I will test your code against my UDM, to see if there is really a difference between the appliance and the real HW.

@igorkulman
Copy link

I only tested in my home setup which is Unifi Controller 7.4.162 running in a docker container on Linux.

@adam-rudd
Copy link

I only tested in my home setup which is Unifi Controller 7.4.162 running in a docker container on Linux.

Can you quote exactly what goes in the enhanced api url field? I have this right now: "https://192.168.40.2/api/login" Says API successful but no stats

@igorkulman
Copy link

Can you quote exactly what goes in the enhanced api url field? I have this right now: "https://192.168.40.2/api/login" Says API successful but no stats

just https://192.168.1.187:8443

@livioh
Copy link

livioh commented Oct 15, 2023

I've try that, still can't get it to connect?

@gavinmcfall
Copy link

Hello,
I am also experiencing this issue.
Heimdall v2.5.7
Unfi UDM Pro OS v3.1.16
Unifi Network v7.5.187

I tried editing the ./config/www/SupportedApps/UniFi/UniFi.php
With the fix from https://github.com/igorkulman from July 25th

@gavinmcfall
Copy link

I got this to work. here are the steps I took.

  1. First I changed the PHP file in my heimdall container (/config/www/SupportedApps/UniFi/UniFi.php)
  2. I copied the code provided by @igorkulman above.
  3. Then I edited the file and changed the URL Paths because I have a UDM Pro. It stipulated here that the endpoint are different: https://ubntwiki.com/products/software/unifi-controller/api
  4. After changing the paths in the PHP file I was still getting errors and moved over to testing in Postman.
  5. That is when I discovered that the API still expects 2fa...
  6. So I logged into my UniFi OS console and created a new user
  7. Set the user to "restrict to local access only = true"
  8. Give it a username and password
  9. Untick the pre-defined role and set all to "View Only"
  10. Save and go back to heimdall and change the username and password in the config
  11. Ensure the URL in the config looks like this: https://10.90.254.1/
  12. Don't add ports or anything, the application uses 443 so its not needed.

@ZoltrixGFC
Copy link

@igorkulman fix also worked for me. Pity it isn't being rolled into the official Heimdall release. I guess when updating to a newer version of Heimdal in the future, it will break again?

@mvdkleijn
Copy link
Collaborator

@ZoltrixGFC The reason its not being rolled into the official release is likely because the fix appears incomplete. It would break things for people with UDM. (based on comment from @gavinmcfall ) In other words: it needs work.

@ZoltrixGFC
Copy link

@mvdkleijn all good, I think maybe it has already been rolled in. The last update to Heimdall didn't remove the changes.

@mvdkleijn
Copy link
Collaborator

Then I'll close this issue if no-one objects. Feel free to comment if that's a problem, I'll reopen.

@neocharles
Copy link

For what It's worth, I am running Heimdall 2.6.1 and I had to perform the fix listed above in order for the API to connect. Unifi Controller Network 8.0.26 and 8.1.113

@obriencj
Copy link

Maybe this could just be split out so that UniFi Network is one type (with this fix) and UniFi remains the old Controller type (ie. without this fix) ?

@zaepho
Copy link
Contributor

zaepho commented Nov 3, 2024

Maybe this could just be split out so that UniFi Network is one type (with this fix) and UniFi remains the old Controller type (ie. without this fix) ?

or a checkbox to select which type you're running
or some form of automatic detection

I am having to revert these changes to connect to my self hosted Unifi controller but would prefer to stay vanilla.

@zaepho
Copy link
Contributor

zaepho commented Nov 3, 2024

I created a PR #798 for the above (added a config switch for Self Hosted controllers).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.