diff --git a/includes/UniFiController.php b/includes/UniFiController.php index 9ee254f..d0da04b 100644 --- a/includes/UniFiController.php +++ b/includes/UniFiController.php @@ -6,6 +6,8 @@ class UniFiController { private $siteId; private $cookieFile; private $csrfToken = null; + private $sessionCookie = null; + private $loggedIn = false; public function __construct($controllerUrl, $username, $password, $siteId) { $this->controllerUrl = rtrim($controllerUrl, '/'); @@ -23,6 +25,10 @@ class UniFiController { // Login zum Controller private function login() { + if ($this->loggedIn) { + return true; + } + $ch = curl_init(); curl_setopt_array($ch, [ @@ -38,14 +44,26 @@ class UniFiController { CURLOPT_COOKIEFILE => $this->cookieFile, CURLOPT_TIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 5, - CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Origin: ' . $this->controllerUrl, + 'Referer: ' . $this->controllerUrl . '/login', + ], CURLOPT_HEADERFUNCTION => function($ch, $header) { $parts = explode(':', $header, 2); if (count($parts) === 2) { - $name = trim($parts[0]); + $name = strtolower(trim($parts[0])); $value = trim($parts[1]); - if (strtolower($name) === 'x-csrf-token') { + if ($name === 'x-csrf-token') { $this->csrfToken = $value; + } elseif ($name === 'set-cookie') { + // Extract TOKEN value directly — cookie jar may not persist + // cookies with the 'Partitioned' attribute on some libcurl versions + $cookieParts = explode(';', $value); + $first = trim($cookieParts[0]); + if (strpos($first, 'TOKEN=') === 0) { + $this->sessionCookie = $first; + } } } return strlen($header); @@ -57,7 +75,9 @@ class UniFiController { curl_close($ch); if ($httpCode !== 200) { - throw new Exception("Login fehlgeschlagen: HTTP $httpCode"); + $body = json_decode($response, true); + $detail = $body['meta']['msg'] ?? $body['errors'][0] ?? substr(strip_tags($response), 0, 120); + throw new Exception("Login fehlgeschlagen: HTTP $httpCode" . ($detail ? " – $detail" : '')); } $data = json_decode($response, true); @@ -75,6 +95,7 @@ class UniFiController { $this->csrfToken = $this->readCsrfFromCookieFile(); } + $this->loggedIn = true; return true; } @@ -94,12 +115,19 @@ class UniFiController { CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_COOKIEFILE => $this->cookieFile, CURLOPT_TIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_HTTPHEADER => $headers ]; + // Prefer manually extracted cookie over file-based jar — the Partitioned + // attribute on the TOKEN cookie prevents some libcurl versions from writing it + if ($this->sessionCookie !== null) { + $options[CURLOPT_COOKIE] = $this->sessionCookie; + } else { + $options[CURLOPT_COOKIEFILE] = $this->cookieFile; + } + if ($method === 'GET') { $options[CURLOPT_HTTPGET] = true; } elseif ($method === 'POST') { diff --git a/test.php b/test.php index 472b59c..485c158 100644 --- a/test.php +++ b/test.php @@ -84,6 +84,180 @@ echo "
" . htmlspecialchars($site['unifi_controller_url']) . "" . htmlspecialchars($site['site_id']) . "" . htmlspecialchars($site['unifi_username']) . "" . htmlspecialchars($info['url']) . "";
+ foreach ($responseHeaders as $h) {
+ if ($h !== '') echo htmlspecialchars($h) . "\n";
+ }
+ echo "";
+
+ // CSRF token
+ if ($csrfToken !== null) {
+ echo "✓ X-CSRF-Token aus Header: " . htmlspecialchars($csrfToken) . "" . htmlspecialchars($tokenFromCookie) . ""; + echo htmlspecialchars(substr($body, 0, 2000)); + echo ""; + + // Cookie file contents + if (file_exists($cookieFile)) { + $cookieContents = file_get_contents($cookieFile); + echo "Cookie-Datei:
"; + echo htmlspecialchars($cookieContents ?: '(leer — TOKEN hat Partitioned-Attribut, libcurl schreibt es nicht in die Jar-Datei)'); + echo ""; + } + + // Extract TOKEN from Set-Cookie header (the fix for Partitioned cookie issue) + $tokenCookie = null; + foreach ($responseHeaders as $h) { + if (stripos($h, 'set-cookie:') === 0) { + $cookieVal = trim(substr($h, strlen('set-cookie:'))); + $cookieParts = explode(';', $cookieVal); + $first = trim($cookieParts[0]); + if (strpos($first, 'TOKEN=') === 0) { + $tokenCookie = $first; + } + } + } + if ($tokenCookie) { + echo "✓ TOKEN aus Set-Cookie-Header extrahiert:
" . htmlspecialchars(substr($tokenCookie, 0, 40)) . "…"; + echo htmlspecialchars(substr($apiBody, 0, 2000)); + echo ""; + } + + @unlink($cookieFile); + } +} catch (Exception $e) { + echo "✗ Diagnose-Fehler: " . htmlspecialchars($e->getMessage()) . "