assertNotEmpty($entries, 'resolvePinned() liefert Einträge für einen gültigen Hostnamen'); foreach ($entries as $entry) { $this->assert( str_starts_with($entry, 'example.com:443:'), 'CURLOPT_RESOLVE-Eintrag beginnt mit "example.com:443:" (Eintrag: ' . $entry . ')' ); } // IP-Literal als Host: kein Pinning nötig/möglich, resolvePinned() liefert leeres Array, // validiert aber weiterhin gegen private/interne Bereiche. $entries = SsrfGuard::resolvePinned('https://8.8.8.8/fints'); $this->assertEquals([], $entries, 'resolvePinned() liefert kein Pinning für ein bereits öffentliches IP-Literal'); try { SsrfGuard::resolvePinned('https://127.0.0.1/fints'); $this->assert(false, 'resolvePinned() lehnt private IP-Literale weiterhin ab'); } catch (\RuntimeException $e) { $this->assert(true, 'resolvePinned() lehnt private IP-Literale weiterhin ab'); } // NextcloudImportService: pinnedResolve wird beim Konstruieren aus der geprüften // baseUrl befüllt und für alle eigenen cURL-Requests verwendet (newCurl()). $this->configure('https://example.com', 'importuser', 'geheim'); $svc = new NextcloudImportService(); $pinned = $this->readProperty($svc, 'pinnedResolve'); $this->assertNotEmpty($pinned, 'NextcloudImportService pinnt die IP(s) der geprüften baseUrl'); $this->assert( str_starts_with($pinned[0], 'example.com:443:'), 'Gepinnter Eintrag bezieht sich auf den Host der baseUrl' ); } 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); } }