Art-of-WiFi / UniFi-API-client

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

Unable to delete radius account. #86

Closed paciks closed 3 years ago

paciks commented 3 years ago

I am unable to delete radius account.

UDM Pro v. 1.8.4 UniFi Network v. 6.0.43 (Build: atag_6.0.43_14348)

List radius accounts:

-------URL & PAYLOAD---------
https://10.28.0.167/proxy/network/api/s/default/rest/account
empty payload
----------RESPONSE-----------
{"meta":{"rc":"ok"},"data":[{"_id":"5fefc567c8028604215ed7cf","name":"john.doe","x_password":"secret-password","tunnel_type":13,"tunnel_medium_type":6,"vlan":2810,"site_id":"12345679123456789123456"}]}
-----------------------------
</pre>
[
    {
        "_id": "5fefc567c8028604215ed7cf",
        "name": "john.doe",
        "x_password": "secret-password",
        "tunnel_type": 13,
        "tunnel_medium_type": 6,
        "vlan": 2810,
        "site_id": "12345679123456789123456"
    }
]

Trying to delete that account:

<pre>
---------cURL INFO-----------
Array
(
    [url] => https://10.28.0.167/proxy/network/api/s/default/rest/account/5fefc567c8028604215ed7cf
    [content_type] => text/plain; charset=utf-8
    [http_code] => 404
    [header_size] => 473
    [request_size] => 426
    [filetime] => -1
    [ssl_verify_result] => 18
    [redirect_count] => 0
    [total_time] => 0.039487
    [namelookup_time] => 1.8E-5
    [connect_time] => 0.000208
    [pretransfer_time] => 0.02417
    [size_upload] => 0
    [size_download] => 9
    [speed_download] => 227
    [speed_upload] => 0
    [download_content_length] => 9
    [upload_content_length] => -1
    [starttransfer_time] => 0.039404
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => 10.28.0.167
    [certinfo] => Array
        (
        )

    [primary_port] => 443
    [local_ip] => 10.28.0.51
    [local_port] => 54870
)

-------URL & PAYLOAD---------
https://10.28.0.167/proxy/network/api/s/default/rest/account/5fefc567c8028604215ed7cf
empty payload
----------RESPONSE-----------
Not Found
-----------------------------
</pre>
PHP Notice:  JSON decode error: Syntax error, malformed JSON in /path/to/git/UniFi-API-client/src/Client.php on line 3606

account deletetion FAILED ...

Used code:

/**
 * initialize the UniFi API connection class and log in to the controller and do our thing
 */
$unifi_connection = new UniFi_API\Client($controlleruser, $controllerpassword, $controllerurl, $site_id, $controllerversion);
$set_debug_mode   = $unifi_connection->set_debug($debug);
$loginresults     = $unifi_connection->login();
if ($loginresults === 400) {
        print "UniFi controller login failure, please check your credentials in config.php.\n";
        exit(-1);
} 

$data             = $unifi_connection->list_radius_accounts();
/**
 * provide feedback in json format
 */
echo json_encode($data, JSON_PRETTY_PRINT);
$account_id = '5fefc567c8028604215ed7cf';

if ($unifi_connection->delete_radius_account($account_id)) {
        echo "\naccount deletion succeeds ...\n";
}
else {
        echo "\naccount deletetion FAILED ...\n";
}
malle-pietje commented 3 years ago

We use this method in several projects where 5.X software controllers are involved and see no issues there. Can you verify through the developer tools what happens when you delete a Radius account through the web interface? URL, request method (DELETE, GET, POST, PUT) and payload if that exists are relevant.

For multiple reasons we don’t deploy UDM PROs so I’m unable to replicate/test...

paciks commented 3 years ago

Here is what I got from Chromium developer tools, I dont see any payload:

Request URL: https://10.28.0.167/proxy/network/api/s/default/rest/account/5fefc567c8028604215ed7cf
Request Method: DELETE
Status Code: 200 OK
Remote Address: 10.28.0.167:443
Referrer Policy: no-referrer-when-downgrade

HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
x-frame-options: DENY
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
content-type: application/json;charset=UTF-8
content-length: 30
date: Sat, 02 Jan 2021 12:27:16 GMT
connection: close
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI5NjI2NmNjYS1kODNhLTQ4NDYtYWViMS05YjdlMDdlMmU1MjEiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk1OTA0MzYsImV4cCI6MTYwOTU5NDAzNn0.AyiDurDJ94RFsVCLHcrZlkdsCXpeRRrNXfpH1qx07SU; path=/; secure; httponly

DELETE /proxy/network/api/s/default/rest/account/5fefc567c8028604215ed7cf HTTP/1.1
Host: 10.28.0.167
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36
x-csrf-token: 96266cca-d83a-4846-aeb1-9b7e07e2e521
Accept: */*
Origin: https://10.28.0.167
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://10.28.0.167/network/site/default/settings/services/radius/users/list
Accept-Encoding: gzip, deflate, br
Accept-Language: pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: _delighted_web={%22pEYwuKMrCvrhUQyO%22:{%22_delighted_fst%22:{%22t%22:%221609590270581%22}}}; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI5NjI2NmNjYS1kODNhLTQ4NDYtYWViMS05YjdlMDdlMmU1MjEiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk1OTAzODEsImV4cCI6MTYwOTU5Mzk4MX0.30p_YsjBDAXqhtw_Jl1dqQEMxlSb0lqfyAWCDmymtSs

RESPONSE:
{"meta":{"rc":"ok"},"data":[]}
paciks commented 3 years ago

I just tested editing (set_radius_account_base) and it worked without any hassle.

malle-pietje commented 3 years ago

Interesting. Can you shared the modified function?

paciks commented 3 years ago

That is what I use for testing, creating account works, editing (I change password to be precise) works but deletion is not.

if ($argc==2){
        $account_id = $argv[1];

        if ($unifi_connection->delete_radius_account($account_id)) {
                echo "\naccount deletion succeeds ...\n";
        }   
        else {
                echo "\naccount deletion FAILED ...\n";
        }   
}
if ($argc==3) {
        if ($unifi_connection->create_radius_account($argv[1], $argv[2], 13, 6, 2810)) {
                echo "\naccount creation succeeds ...\n";
        }   
        else {
                echo "\naccount creation FAILED ...\n";
        }   
}
if ($argc==4) {
        $payload = [ 
                'name'                  => $argv[1],
                'x_password'            => $argv[2],
                'tunnel_type'           => 13, 
                'tunnel_medium_type'    => 6,
                'vlan'                  => 2810
        ];  
        $account_id = $argv[3];
        if ($unifi_connection->set_radius_account_base($account_id, $payload)) {
                echo "\naccount edition succeeds ...\n";
        }   
        else {
                echo "\naccount edition FAILED ...\n";
        }   
}
malle-pietje commented 3 years ago

I just ran a test with the delete_radius_account() method against a software-based 5.14.23 controller and it works fine.

-------URL & PAYLOAD---------
https://FQDN:8443/api/s/SITE_ID/rest/account/5ff30599bd35eb1c66c7eb3a
empty payload
----------RESPONSE-----------
{"meta":{"rc":"ok"},"data":[]}
-----------------------------

I need to find some time to add a USG to one of the sites on our 6.0.43 test controller and run the test there again.

paciks commented 3 years ago

I would be happy to run some test just let me know how can I help.

malle-pietje commented 3 years ago

Thanks. First I need to figure out what we're looking for...

paciks commented 3 years ago

I am not an expert but I was able to reproduce "NOT FOUND" response playing with BurpSuite, to do that I had to modify x-csrf-token in repeated request:

DELETE /proxy/network/api/s/default/rest/account/5ff33eaf1cba35042641e806 HTTP/1.1
Host: 10.28.0.167
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
x-csrf-token: 8ba0c82d-f803-4c89-b8ea-67a31a906b6c
Accept: */*
Origin: https://10.28.0.167
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://10.28.0.167/network/site/default/settings/services/radius/users/list
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _delighted_web={%22pEYwuKMrCvrhUQyO%22:{%22_delighted_fst%22:{%22t%22:%221609774625325%22}}}; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI4YmEwYzgyZC1mODAzLTRjODktYjhlYS02N2EzMWE5MDZiNmIiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3NzQ5ODEsImV4cCI6MTYwOTc3ODU4MX0.CXv_czQu-WSC4wvyJrzNKM-vtCCa13JBQZsa8QvT9cQ

HTTP/1.1 404 Not Found
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
X-CSRF-Token: 8ba0c82d-f803-4c89-b8ea-67a31a906b6b
X-Response-Time: 4ms
Content-Type: text/plain; charset=utf-8
Content-Length: 9
Date: Mon, 04 Jan 2021 16:13:45 GMT
Connection: close

Not Found

And with the correct value of x-csfr-token:

DELETE /proxy/network/api/s/default/rest/account/5ff33eaf1cba35042641e806 HTTP/1.1
Host: 10.28.0.167
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
x-csrf-token: 8ba0c82d-f803-4c89-b8ea-67a31a906b6b
Accept: */*
Origin: https://10.28.0.167
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://10.28.0.167/network/site/default/settings/services/radius/users/list
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _delighted_web={%22pEYwuKMrCvrhUQyO%22:{%22_delighted_fst%22:{%22t%22:%221609774625325%22}}}; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI4YmEwYzgyZC1mODAzLTRjODktYjhlYS02N2EzMWE5MDZiNmIiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3NzQ5ODEsImV4cCI6MTYwOTc3ODU4MX0.CXv_czQu-WSC4wvyJrzNKM-vtCCa13JBQZsa8QvT9cQ

HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
x-frame-options: DENY
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
content-type: application/json;charset=UTF-8
content-length: 30
date: Mon, 04 Jan 2021 16:14:13 GMT
connection: close
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI4YmEwYzgyZC1mODAzLTRjODktYjhlYS02N2EzMWE5MDZiNmIiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3NzY4NTQsImV4cCI6MTYwOTc4MDQ1NH0.N8KpTqXTLbWS2p3wbAPWYkp4ELeoKB6keDIzWIQDnC0; path=/; secure; httponly

{"meta":{"rc":"ok"},"data":[]}

Modified value: x-csrf-token: 8ba0c82d-f803-4c89-b8ea-67a31a906b6c Correct value: x-csrf-token: 8ba0c82d-f803-4c89-b8ea-67a31a906b6b

When I was trying to DELETE already (deleted above) non-existed account the response was:

DELETE /proxy/network/api/s/default/rest/account/5ff33eaf1cba35042641e806 HTTP/1.1
HTTP/1.1 400 Bad Request
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
x-frame-options: DENY
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
content-type: application/json;charset=UTF-8
content-length: 59
date: Mon, 04 Jan 2021 16:19:36 GMT
connection: close
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiI4YmEwYzgyZC1mODAzLTRjODktYjhlYS02N2EzMWE5MDZiNmIiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3NzcxNzUsImV4cCI6MTYwOTc4MDc3NX0.LjmPw1XfMRPWGjCmCCmEgJYBxSO8HnR1IrXLj7H4mcQ; path=/; secure; httponly

{"meta":{"rc":"error","msg":"api.err.IdInvalid"},"data":[]}

I hope it is helpful.

malle-pietje commented 3 years ago

Interesting observation. Can you confirm whether the x-csrf-token that is returned in the response headers changes after the first request which follows the login? This would make sense why the second request after the login fails. Probably the delete request will succeed after you re-login.

paciks commented 3 years ago

I just re-log and it seems that csrf token stays the same as it was in first request after login:

POST /api/auth/login HTTP/1.1
Host: 10.28.0.167
Connection: close
Content-Length: 70
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
x-csrf-token: d6f8b635-4b5a-4a29-ba5d-41ac1b733aa5
Content-Type: application/json; charset=utf-8
Accept: */*
Origin: https://10.28.0.167
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://10.28.0.167/login?redirect=%2F
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _delighted_web={%22pEYwuKMrCvrhUQyO%22:{%22_delighted_fst%22:{%22t%22:%221609774625325%22}}}; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiJkNmY4YjYzNS00YjVhLTRhMjktYmE1ZC00MWFjMWI3MzNhYTUiLCJpYXQiOjE2MDk3ODAxODYsImV4cCI6MTYwOTc4Mzc4Nn0.FYVTJimArUY_6QE_5XWNemAkOS_jf1Zfe6qVhTduhww

{"username":"user","password":"i-dont-want-to-share","rememberMe":false}

response:

HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
Content-Type: application/json; charset=utf-8
Content-Length: 4897
X-Response-Time: 174ms
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiJkNmY4YjYzNS00YjVhLTRhMjktYmE1ZC00MWFjMWI3MzNhYTUiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJpYXQiOjE2MDk3ODAxOTYsImV4cCI6MTYwOTc4Mzc5Nn0.VUrwBzVuUkP4QTjDKiYzkJT6tTV11wUta30vuP2M85I; path=/; secure; httponly
Date: Mon, 04 Jan 2021 17:09:56 GMT
Connection: close

{
EDITED OUT 
}

I copied csrf token, cookie and user id and delete (via BurpSuite) is a success :

DELETE /proxy/network/api/s/default/rest/account/5ff34cef1cba35042641e87f HTTP/1.1
Host: 10.28.0.167
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
x-csrf-token: d6f8b635-4b5a-4a29-ba5d-41ac1b733aa5
Accept: */*
Origin: https://10.28.0.167
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://10.28.0.167/network/site/default/settings/services/radius/users/list
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _delighted_web={%22pEYwuKMrCvrhUQyO%22:{%22_delighted_fst%22:{%22t%22:%221609774625325%22}}}; TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiJkNmY4YjYzNS00YjVhLTRhMjktYmE1ZC00MWFjMWI3MzNhYTUiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3ODA0NTIsImV4cCI6MTYwOTc4NDA1Mn0.xv6pa5Xf7Oa_uXOqWagigImwBV_qPVc29vAUcPw0Be8

HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://10.28.0.167
X-DNS-Prefetch-Control: off
x-frame-options: DENY
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Accept-Ranges: bytes
content-type: application/json;charset=UTF-8
content-length: 30
date: Mon, 04 Jan 2021 17:15:25 GMT
connection: close
Set-Cookie: TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjc3JmVG9rZW4iOiJkNmY4YjYzNS00YjVhLTRhMjktYmE1ZC00MWFjMWI3MzNhYTUiLCJ1c2VySWQiOiJhZTM5YTk1Yi1hMzdlLTRiOGUtOTc5Yi0zOTBkOWM2M2E0NmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNjA4MDM4NDA4LCJpYXQiOjE2MDk3ODA1MjYsImV4cCI6MTYwOTc4NDEyNn0.HCxZPmkqUOTmEqcPRIQaC3r1HD2yJRt75Eguxte5Z8E; path=/; secure; httponly

{"meta":{"rc":"ok"},"data":[]}
malle-pietje commented 3 years ago

@paciks Can you see whether this version of the exec_curl function/method fixes the issue?

    /**
     * Execute the cURL request
     *
     * @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
     */
    protected function exec_curl($path, $payload = null)
    {
        if (!in_array($this->request_type, $this->request_types_allowed)) {
            trigger_error('an invalid HTTP request type was used: ' . $this->request_type);
        }

        if (!($ch = $this->get_curl_resource())) {
            trigger_error('$ch as returned by get_curl_resource() is not a resource');

            return false;
        }

        /**
         * assigne default values to these vars
         */
        $json_payload = '';
        $headers      = [];

        if ($this->is_unifi_os) {
            $url = $this->baseurl . '/proxy/network' . $path;
        } else {
            $url = $this->baseurl . $path;
        }

        /**
         * prepare cURL options
         */
        $curl_options = [
            CURLOPT_URL => $url
        ];

        if (!is_null($payload)) {
            $json_payload                     = json_encode($payload, JSON_UNESCAPED_SLASHES);
            $curl_options[CURLOPT_POST]       = true;
            $curl_options[CURLOPT_POSTFIELDS] = $json_payload;

            $headers = [
                'Content-Type: application/json',
                'Content-Length: ' . strlen($json_payload)
            ];

            /**
             * we shouldn't be using GET (the default request type) or DELETE when passing a payload,
             * switch to POST instead
             */
            switch ($this->request_type) {
                case 'GET':
                    $this->request_type = 'POST';
                    break;
                case 'DELETE':
                    $this->request_type = 'POST';
                    break;
                case 'PUT':
                    $curl_options[CURLOPT_CUSTOMREQUEST] = 'PUT';
                    break;
            }
        }

        switch ($this->request_type) {
            case 'DELETE':
                $curl_options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
                break;
            case 'PATCH':
                $curl_options[CURLOPT_CUSTOMREQUEST] = 'PATCH';
                break;
            case 'POST':
                $curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';
                break;
        }

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

        if (count($headers) > 0) {
            $curl_options[CURLOPT_HTTPHEADER] = $headers;
        }

        curl_setopt_array($ch, $curl_options);

        /**
         * execute the cURL request
         */
        $content = curl_exec($ch);
        if (curl_errno($ch)) {
            trigger_error('cURL error: ' . curl_error($ch));
        }

        /**
         * fetch the HTTP response code
         */
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        /**
         * an HTTP response code 401 (Unauthorized) indicates the Cookie/Token has expired in which case
         * we need to login again.
         */
        if ($http_code == 401) {
            if ($this->debug) {
                error_log(__FUNCTION__ . ': needed to reconnect to UniFi controller');
            }

            if ($this->exec_retries == 0) {
                /**
                 * explicitly clear the expired Cookie/Token, update other properties and log out before logging in again
                 */
                if (isset($_SESSION['unificookie'])) {
                    $_SESSION['unificookie'] = '';
                }

                $this->is_loggedin = false;
                $this->exec_retries++;
                curl_close($ch);

                /**
                 * then login again
                 */
                $this->login();

                /**
                 * when re-login was successful, simply execute the same cURL request again
                 */
                if ($this->is_loggedin) {
                    if ($this->debug) {
                        error_log(__FUNCTION__ . ': re-logged in, calling exec_curl again');
                    }

                    return $this->exec_curl($path, $payload);
                }

                if ($this->debug) {
                    error_log(__FUNCTION__ . ': re-login failed');
                }
            }

            return false;
        }

        if ($this->debug) {
            print PHP_EOL . '<pre>';
            print PHP_EOL . '---------cURL INFO-----------' . PHP_EOL;
            print_r(curl_getinfo($ch));
            print PHP_EOL . '-------URL & PAYLOAD---------' . PHP_EOL;
            print $url . PHP_EOL;
            if (empty($json_payload)) {
                print 'empty payload';
            } else {
                print $json_payload;
            }

            print PHP_EOL . '----------RESPONSE-----------' . PHP_EOL;
            print $content;
            print PHP_EOL . '-----------------------------' . PHP_EOL;
            print '</pre>' . PHP_EOL;
        }

        curl_close($ch);

        /**
         * set request_type value back to default, just in case
         */
        $this->request_type = 'GET';

        return $content;
    }
malle-pietje commented 3 years ago

@paciks I applied a small change to the above function/method to only pass the headers to cURL when the $headers array isn't empty. This is to prevent possible issues with future controller versions.

paciks commented 3 years ago

Success! Deletion is working now, I also checked modifying existing account and creating new one and all of them succeeded.

malle-pietje commented 3 years ago

Thanks for confirming! Basically the controller now also requires the x-csrf-token headers for other requests other than POST. I'll push an update later today with this change included.

malle-pietje commented 3 years ago

Fixed with v1.1.63