Art-of-WiFi / UniFi-API-client

A PHP API client class to interact with Ubiquiti's UniFi Controller API
MIT License
1.15k stars 226 forks source link

cURL Error upon logout on UDM PRO running 5.14.9 #63

Closed johnluber closed 3 years ago

johnluber commented 4 years ago

Running controller 5.14.9 and now getting the following error from CURL on logout:

PHP Notice: cURL error: Empty reply from server in /var/www/html/UniFi-API-client/src/Client.php on line 273

johnluber commented 4 years ago

I should have added that where stat_client used to return 'ip' it now returns 'fixed_ip' as well. There may be other changes as well but this is the only one I am using at this time.

malle-pietje commented 4 years ago

Thanks for reporting. I don't have that version here to test yet, I'm quite conservative at the moment with new RC candidates😉 Can you share debug output from the logout call? That will help determine whether the URL has changed or returns a different response code as we saw with the introduction of the UDM PROs.

johnluber commented 4 years ago

I'm probably too much on the bleeding edge, but...

Here's what I think is the relevant debug output:

1* Trying 192.168.1.1:443...

malle-pietje commented 4 years ago

I see you are using port 443. Is this a UDM PRO or do you have a reverse proxy in place?

malle-pietje commented 4 years ago

Probably a UDM PRO by the looks of the cookie structure.

johnluber commented 4 years ago

Is it a UDM Pro

malle-pietje commented 4 years ago

OK, clear. I'm afraid we don't have access to a UDM PRO running this version so this will have to wait until I can get access to one. The UDM PRO I do have access to for testing purposes is running 5.13.27.

I wasn't planning on purchasing one because for now we only deploy USGs and cloud-based controllers...

johnluber commented 4 years ago

Ok, I understand. It's been working just fine up until the latest release so it's probably more a 5.14 issue than a Pro issue.

For now, what I am using the code for still works; it just throws the error on logout.

malle-pietje commented 4 years ago

You hardly ever need to actually logout😉

malle-pietje commented 4 years ago

Let's keep this issue open to keep track of it.

johnluber commented 4 years ago

Yes, not overly concerned about logging out. Just wanted to make sure the change(s) are reported.

Thanks!

Olivier6767 commented 4 years ago

Hello, Just to confirm that I have the exact same error, running on UDM PRO release 5.13.30. This is mainstream release that comes with firmware 1.7.2. My script quries list_clients() every minute, but I force a new login every hour. This is where the error message appears.

malle-pietje commented 4 years ago

Hello, Just to confirm that I have the exact same error, running on UDM PRO release 5.13.30. This is mainstream release that comes with firmware 1.7.2. My script quries list_clients() every minute, but I force a new login every hour. This is where the error message appears.

Can you confirm which method in the API client returns the error message? It also helps if you can share debug output so I can determine whether the root cause is the same as for the initial issue here.

Olivier6767 commented 4 years ago

The error message is coming from the logout() method, which is executed automatically when the php script ends.

Here is a small test script I used:

$unifi_connection = new UniFi_API\Client($controlleruser, $controllerpassword, $controllerurl, $site_id, $controllerversion);
$set_debug_mode = $unifi_connection->set_debug(DEBUG);
$loginresults = $unifi_connection->login();
$timeout = $unifi_connection->get_connection_timeout();
exit();

Here is the end of the debug log:

*   Trying 10.1.1.1:443...
* Connected to unifi.home.com (10.1.1.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=unifi.local
*  start date: Mar  7 08:19:39 2020 GMT
*  expire date: Jun 10 08:19:39 2022 GMT
*  issuer: CN=unifi.local
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> POST /api/auth/logout HTTP/1.1
Host: unifi.home.com
Accept: */*
Accept-Encoding: deflate, gzip
Transfer-Encoding: chunked
Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiIzMzYyNDk4Ni02NDMxLTQ2YTQtYmMxZC1mYjY0ZjNkZGVhZmUiLCJ1c2VySWQiOiI0NDY1ZTFjOS1jNDFhLTQxYTAtOTRjNy1iNzEzMGZkMDdkNjAiLCJpYXQiOjE1OTY3OTI1OTgsImV4cCI6MTU5Njc5NjE5OH0.KiFZd-s1In44YsuPJi0R_WsoRWbe4JkqWKaoMSEG_MQ
Content-Length: 0
x-csrf-token: 33624986-6431-46a4-bc1d-fb64f3ddeafe
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

* Empty reply from server
* Connection #0 to host unifi.home.com left intact
PHP Notice:  cURL error: Empty reply from server in /app/unifi/php/src/Client.php on line 284
malle-pietje commented 4 years ago

OK, so in your case, the error occurs when the __destruct() method is called. Apparently Ubiquiti has changed something with the logout route of the API, when I get my hands on a UDM PRO I'll be able to debug this in more detail but that can take a while (I'm not a big fan of them, yet).

Until that time, if someone can record the exact HTTP request that is issued by the Web UI when logging off from the UniFi controller on a UDM PRO that can be of help. The URL, headers, request type, payload, and raw response are important to have.

Olivier6767 commented 4 years ago

Here is some information, let me know if you need anything else.

General

Request URL: https://unifi.home.com/api/auth/logout
Request Method: POST
Status Code: 200 OK
Remote Address: 10.1.1.1:443
Referrer Policy: no-referrer-when-downgrade

Request

Accept-Ranges: bytes
Access-Control-Allow-Origin: https://unifi.home.com
Connection: keep-alive
Content-Length: 21
Content-Type: application/json; charset=utf-8
Date: Fri, 07 Aug 2020 10:00:18 GMT
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI1ZmFjNWI1NC01MTExLTRiNTYtOGMzNC00MmIzNWY2YWJlMzYiLCJ1c2VySWQiOm51bGwsImlhdCI6MTU5Njc5NDQxOCwiZXhwIjoxNTk2Nzk4MDE4fQ.PCJZ_qzjP8qvu_rFi_j22g_sopeIgJuQ1mhe25QM1i4; path=/; secure; httponly
Strict-Transport-Security: max-age=15552000; includeSubDomains
Vary: Origin
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Response-Time: 4ms
X-XSS-Protection: 1; mode=block

Response

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 0
Cookie: _ga=GA1.2.1033417061.1588101450; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI1ZmFjNWI1NC01MTExLTRiNTYtOGMzNC00MmIzNWY2YWJlMzYiLCJ1c2VySWQiOiI0NDY1ZTFjOS1jNDFhLTQxYTAtOTRjNy1iNzEzMGZkMDdkNjAiLCJpYXQiOjE1OTY3OTQ0MTEsImV4cCI6MTU5Njc5ODAxMX0.RtwNIE4fRU3wT996yL55McECSYUfX9sjWmKD6MNQJBU
DNT: 1
Host: unifi.home.com
Origin: https://unifi.home.com
Pragma: no-cache
Referer: https://unifi.home.com/network/site/default/dashboard
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36

Raw Response

{
  "success": true
}
malle-pietje commented 4 years ago

@Olivier6767 Thanks, looks as if they no longer accept the CSRF token in the header but now require the full JWT value as the cookie. That does make more sense.

Can you try and see what happens when you (temporarily) change this code from the logout method in Client.php:

            $csrf_token = $this->extract_csrf_token_from_cookie();
            if ($csrf_token) {
                $headers[] = 'x-csrf-token: ' . $csrf_token;
            }

to:

            $cookie_bits = explode('=', $this->cookies);
            $jwt = $cookie_bits[1];
            $headers[] = 'Set-Cookie: ' . $jwt;
malle-pietje commented 4 years ago

If that works I'll implement cleaner code with the necessary checks.

Olivier6767 commented 4 years ago

Unfortuantely this does not work, here is the log

Here is the code I changed

        /**
         * constuct HTTP request headers as required
         */
        $headers = ['Content-Length: 0'];
        if ($this->is_unifi_os) {
            $logout_path = '/api/auth/logout';
            $curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';

            // $csrf_token = $this->extract_csrf_token_from_cookie();
            // if ($csrf_token) {
                // $headers[] = 'x-csrf-token: ' . $csrf_token;
            // }

            $cookie_bits = explode('=', $this->cookies);
            $jwt = $cookie_bits[1];
            $headers[] = 'Set-Cookie: ' . $jwt;

        } else {
            $logout_path = '/logout';
        }

        $curl_options[CURLOPT_HTTPHEADER] = $headers;
        $curl_options[CURLOPT_URL]        = $this->baseurl . $logout_path;

        curl_setopt_array($ch, $curl_options);
*   Trying 10.1.1.1:443...
* Connected to unifi.home.com (10.1.1.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=unifi.local
*  start date: Mar  7 08:19:39 2020 GMT
*  expire date: Jun 10 08:19:39 2022 GMT
*  issuer: CN=unifi.local
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> POST /api/auth/logout HTTP/1.1
Host: unifi.home.com
Accept: */*
Accept-Encoding: deflate, gzip
Transfer-Encoding: chunked
Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI2ODJjYjQ5Ny1lMGVhLTQzOGUtOTU1Mi00ZGUwNjIzMjg0MjMiLCJ1c2VySWQiOiI0NDY1ZTFjOS1jNDFhLTQxYTAtOTRjNy1iNzEzMGZkMDdkNjAiLCJpYXQiOjE1OTY3OTY4NzQsImV4cCI6MTU5NjgwMDQ3NH0.2Wb9iUZRX39BE-3BI6e3Zisl1KoKMJG0xNp2nWZaIUE
Content-Length: 0
Set-Cookie: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI2ODJjYjQ5Ny1lMGVhLTQzOGUtOTU1Mi00ZGUwNjIzMjg0MjMiLCJ1c2VySWQiOiI0NDY1ZTFjOS1jNDFhLTQxYTAtOTRjNy1iNzEzMGZkMDdkNjAiLCJpYXQiOjE1OTY3OTY4NzQsImV4cCI6MTU5NjgwMDQ3NH0.2Wb9iUZRX39BE-3BI6e3Zisl1KoKMJG0xNp2nWZaIUE
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

* Empty reply from server
* Connection #0 to host unifi.home.com left intact
PHP Notice:  cURL error: Empty reply from server in /app/unifi/php/src/Client.php on line 289

FYI, Line 289 is trigger_error('cURL error: ' . curl_error($ch));

malle-pietje commented 4 years ago

Hmm, disappointing. Can you perhaps change this (current) line:

            $headers[] = 'Set-Cookie: ' . $jwt;

to:

            $headers[] = 'Set-Cookie: TOKEN=' . $jwt;

Hopefully this header isn't overwritten by the JWT alone.

Olivier6767 commented 4 years ago

Sorry, it is still not working, same error.

If you want, I can send you by email the url with a user account and password for testing.

malle-pietje commented 4 years ago

Sorry, it is still not working, same error.

If you want, I can send you by email the url with a user account and password for testing.

That would be great, and read-only should be fine. Please send to my business email account: user erik AT artofwifi DOT net

Olivier6767 commented 4 years ago

Ok, credentials sent.

malle-pietje commented 4 years ago

thanks

malle-pietje commented 4 years ago

@Olivier6767 It took a bit longer to find some time for this but it looks as if all is good now:

* Hostname XXXXXXX.XXXXXXX.com was found in DNS cache
*   Trying XXXXXXXXXXXX...
* TCP_NODELAY set
* Connected to XXXXXXX.XXXXXXX.com (82.64.140.233) port 443 (#0)
* ALPN, offering http/1.1
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=XXXXXXX.com
*  start date: Jul 11 06:53:00 2020 GMT
*  expire date: Oct  9 06:53:00 2020 GMT
*  subjectAltName: host "XXXXXXX.XXXXXXX.com" matched cert's "*.XXXXXXX.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
> POST /api/auth/logout HTTP/1.1
Host: XXXXXXX.XXXXXXX.com
Accept: */*
Accept-Encoding: deflate, gzip
Cookie: TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Length: 0
x-csrf-token: XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 21
< Content-Type: application/json; charset=utf-8
< Date: Sat, 22 Aug 2020 15:17:59 GMT
< Set-Cookie: TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
< Strict-Transport-Security: max-age=15552000; includeSubDomains
< Vary: Origin
< X-Content-Type-Options: nosniff
< X-Dns-Prefetch-Control: off
< X-Download-Options: noopen
< X-Frame-Options: SAMEORIGIN
< X-Response-Time: 6ms
< X-Xss-Protection: 1; mode=block

Especially the HTTP/1.1 200 OK response is important.

I will do some further testing before I push this to the GitHub repo.

malle-pietje commented 4 years ago

@Olivier6767 Apologies for that slip of the keyboard...now corrected

malle-pietje commented 4 years ago

Closing, please re-open if needed.

Olivier6767 commented 4 years ago

Hi, I tested using the latest release (1.1.57) and I still get the same error:

*   Trying 10.1.1.1:443...
* Connected to unifi.home.com (10.1.1.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=unifi.local
*  start date: Mar  7 08:19:39 2020 GMT
*  expire date: Jun 10 08:19:39 2022 GMT
*  issuer: CN=unifi.local
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> POST /api/auth/logout HTTP/1.1
Host: unifi.home.com
Accept: */*
Accept-Encoding: deflate, gzip
Transfer-Encoding: chunked
Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiIxN2IzODk0MS1lYmQxLTRkZTQtYjc1Mi0yZjBkY2E3NWEzODIiLCJ1c2VySWQiOiI0NDY1ZTFjOS1jNDFhLTQxYTAtOTRjNy1iNzEzMGZkMDdkNjAiLCJpYXQiOjE1OTgyMDYxMzgsImV4cCI6MTU5ODIwOTczOH0.H2m_PyVg2oLOuSANuBidKOQ-rIyYsJWdzHXTs-UITsI
Content-Length: 0
x-csrf-token: 17b38941-ebd1-4de4-b752-2f0dca75a382
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

* Empty reply from server
* Connection #0 to host unifi.home.com left intact
**PHP Notice:  cURL error: Empty reply from server in /app/unifi/php/src/Client.php on line 279**

Here is the code:

<?php
require_once '/app/unifi/php/src/Client.php';

$controlleruser="MYUSER";
$controllerpassword="MYPASSWORD";
$controllerurl="https://unifi.home.com";
$site_id="default";
$controllerversion="5.13.30";

define ('DEBUG', true);

$unifi_connection = new UniFi_API\Client($controlleruser, $controllerpassword, $controllerurl, $site_id, $controllerversion);
$set_debug_mode = $unifi_connection->set_debug(DEBUG);
$loginresults = $unifi_connection->login();
$timeout = $unifi_connection->get_connection_timeout();

print("\r\n\r\n");

?>

This is the same UDM Pro you had access to for testing (verison unchanged 5.13.30).

malle-pietje commented 4 years ago

Hmm, are you connecting to the UDM PRO directly and nit through your reverse proxy?

malle-pietje commented 4 years ago

I’ll share the code I used to test first thing tomorrow.

Olivier6767 commented 4 years ago

Yes I confirm that the error occurs when connecting to the UDM-PRO directly. When going thru the reverse proxy I have no error.

malle-pietje commented 4 years ago

@Olivier6767 Hmm, that’s a bit of a disappointment. Further work on a fix will have to wait until I can get direct access to a UDM PRO I’m afraid. I can’t risk implementing changes that can potentially break when talking to a UDM PRO direct...

malle-pietje commented 3 years ago

@Olivier6767 Can you check if this issue is fixed in 1.1.63?

Olivier6767 commented 3 years ago

@malle-pietje Yes I confirm that it is fixed in 1.1.64 (current version I'm on). Thanks!

Olivier6767 commented 2 years ago

UDM-PRO release 6.5.52 Unifi-API release 1.1.74 PHP Releaser 7.4.25 PHP Curl release 7.79.1 Platform Alpine Linux 3.14

The Logout function in Client.php generates again an error, this time it's an error 400 (HTTP/1.1 400 Bad Request).

By playing around with the CURL options in thelogout() function in Client.php, I was able to find a way to make it work. If I comment out the line 250 ('content-length: 0',) in the following section, I get rid of the HTTP error 400. It then returns OK (HTTP/1.1 200 OK).

$this->curl_headers = [
    // 'content-length: 0',
    'Expect:',
];
malle-pietje commented 2 years ago

Pfff, it is becoming a real PITA how the Ubiquiti developers are changing the app server's behavior on UniFi OS consoles with almost every release... In the early releases, that content-length header was actually required for the API client to log out without any errors...

I will apply this change with the next release, thanks for reporting it.

Olivier6767 commented 2 years ago

Thanks. What's weird is that I watched the session logout using the browser's dev tools and I could see that content-length: 0 was present in the request header... Doesn't make sense to me.

malle-pietje commented 2 years ago

Exactly..., anyway, with the change the API client should work.