assertRejected('http://cloud.example.com', 'http:// wird abgelehnt'); $this->assertRejected('https://127.0.0.1', 'Loopback-IP (127.0.0.1) wird abgelehnt'); $this->assertRejected('https://169.254.169.254', 'Link-Local/Metadata-IP (169.254.169.254) wird abgelehnt'); $this->assertRejected('https://10.0.0.5', 'RFC1918-IP (10.0.0.5) wird abgelehnt'); $this->assertRejected('https://localhost', 'localhost wird abgelehnt'); // Gültiges Ziel: baseUrl bleibt gesetzt, kein configError, davBase korrekt gebildet. // Hinweis: SsrfGuard löst den Hostnamen per echtem DNS auf (Schutz vor DNS-Rebinding) – // "cloud.example.com" ist keine registrierte Subdomain und liefert NXDOMAIN. Die // Apex-Domain "example.com" ist dagegen laut IANA dauerhaft real auflösbar. $this->configure('https://example.com', 'importuser', 'geheim'); $svc = new NextcloudImportService(); $baseUrl = $this->readProperty($svc, 'baseUrl'); $davBase = $this->readProperty($svc, 'davBase'); $cfgError = $this->readProperty($svc, 'configError'); $this->assertEquals('https://example.com', $baseUrl, 'Gültige https-URL bleibt als baseUrl erhalten'); $this->assert($cfgError === null, 'Kein configError bei gültiger URL'); $this->assert( str_contains((string)$davBase, '/remote.php/dav/files/importuser'), 'davBase wird aus der gültigen baseUrl gebildet' ); // Ungültiges Ziel: baseUrl wird geleert, configError gesetzt, import() bricht // sofort ab (kein PROPFIND-Versuch gegen das interne Ziel). $this->configure('https://127.0.0.1:9999', 'importuser', 'geheim'); $svc = new NextcloudImportService(); $baseUrl = $this->readProperty($svc, 'baseUrl'); $cfgError = $this->readProperty($svc, 'configError'); $this->assertEquals('', $baseUrl, 'Ungültige URL: baseUrl wird geleert (fail closed)'); $this->assert( is_string($cfgError) && str_contains($cfgError, 'ungültig'), 'Ungültige URL: configError beschreibt die Ablehnung' ); $result = $svc->import(); $this->assertEquals(1, $result['errors'], 'import() meldet einen Fehler statt einen Request abzusetzen'); $this->assert( !empty($result['details']) && str_contains($result['details'][0], 'ungültig'), 'import()-Fehlermeldung verweist auf die ungültige Nextcloud-URL' ); } private function configure(string $url, string $user, string $pass): void { Database::execute( 'UPDATE company_settings SET nextcloud_url=?, nextcloud_user=?, nextcloud_pass=? WHERE id=1', [$url, $user, $pass] ); } private function readProperty(object $obj, string $name): mixed { $prop = new \ReflectionProperty($obj, $name); $prop->setAccessible(true); return $prop->getValue($obj); } private function assertRejected(string $url, string $message): void { $this->configure($url, 'importuser', 'geheim'); $svc = new NextcloudImportService(); $baseUrl = $this->readProperty($svc, 'baseUrl'); $this->assert($baseUrl === '', $message); } }