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

Unable to delete radius account. #86

Closed
paciks opened this issue Jan 2, 2021 · 16 comments
Closed

Unable to delete radius account. #86

paciks opened this issue Jan 2, 2021 · 16 comments

Comments

@paciks
Copy link

paciks commented Jan 2, 2021

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
Copy link
Collaborator

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
Copy link
Author

paciks commented Jan 2, 2021

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
Copy link
Author

paciks commented Jan 2, 2021

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

@malle-pietje
Copy link
Collaborator

Interesting. Can you shared the modified function?

@paciks
Copy link
Author

paciks commented Jan 2, 2021

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
Copy link
Collaborator

malle-pietje commented Jan 4, 2021

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
Copy link
Author

paciks commented Jan 4, 2021

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

@malle-pietje
Copy link
Collaborator

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

@paciks
Copy link
Author

paciks commented Jan 4, 2021

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
Copy link
Collaborator

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
Copy link
Author

paciks commented Jan 4, 2021

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
Copy link
Collaborator

malle-pietje commented Jan 6, 2021

@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
Copy link
Collaborator

malle-pietje commented Jan 6, 2021

@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
Copy link
Author

paciks commented Jan 6, 2021

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

@malle-pietje
Copy link
Collaborator

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 added a commit that referenced this issue Jan 6, 2021
- changed references to *UbiOS* back to *UniFi OS*
- removed capitalization from all header strings (per RFC, header fields are case-insensitive: https://tools.ietf.org/html/rfc7230#section-3.2)
- removed charset parameter from headers (not required per RFC)
- added x-csrf-token header to all requests except GET when talking to UniFi OS-based controllers, thanks go to @paciks for raising #86
@malle-pietje
Copy link
Collaborator

Fixed with v1.1.63

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

No branches or pull requests

2 participants