diff --git a/src/tests/Infrastructure/ActivesyncTest.php b/src/tests/Infrastructure/ActivesyncTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Infrastructure/ActivesyncTest.php @@ -0,0 +1,150 @@ +loadXML($xml); + $encoder->encode($dom); + rewind($outputStream); + return stream_get_contents($outputStream); + } + + private static function fromWbxml($binary) + { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $binary); + rewind($stream); + $decoder = new \Syncroton_Wbxml_Decoder($stream); + return $decoder->decode(); + } + + /** + * {@inheritDoc} + */ + public function setUp(): void + { + parent::setUp(); + + //FIXME test users are probably not created in imap, + //which is why this fails. So we're using john@kolab.org for now. + // $user = $this->getTestUser('jane@kolabnow.com'); + // $user->password = "simple123"; + // $user->save(); + + $this->client = new \GuzzleHttp\Client([ + 'http_errors' => false, // No exceptions + 'base_uri' => "http://roundcube/Microsoft-Server-ActiveSync/", + 'verify' => false, + // 'auth' => [$user->email, $user->password], + 'auth' => ['john@kolab.org', 'simple123'], + 'connect_timeout' => 10, + 'timeout' => 10, + 'headers' => [ + "Content-Type" => "application/xml; charset=utf-8" + ] + ]); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + // $this->deleteTestUser('jane@kolabnow.com'); + parent::tearDown(); + } + + public function testOptions() + { + $response = $this->client->request('OPTIONS', ''); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue(str_contains($response->getHeader('MS-Server-ActiveSync')[0], '14')); + $this->assertTrue(str_contains($response->getHeader('MS-ASProtocolVersions')[0], '14.1')); + $this->assertTrue(str_contains($response->getHeader('MS-ASProtocolCommands')[0], 'FolderSync')); + } + + public function testFolderList() + { + $request = << + + + 0 + + EOF; + $body = self::toWbxml($request); + $response = $this->client->request('POST', '?Cmd=FolderSync&User=john@kolab.org&DeviceId=v140Device&DeviceType=iphone', [ + 'headers' => [ + "Content-Type" => "application/vnd.ms-sync.wbxml", + 'MS-ASProtocolVersion' => "14.0" + ], + 'body' => $body + ]); + $this->assertEquals(200, $response->getStatusCode()); + $result = self::fromWbxml($response->getBody())->saveXML(); + $this->assertTrue(str_contains($result, 'INBOX')); + $this->assertTrue(str_contains($result, 'Drafts')); + $this->assertTrue(str_contains($result, 'Sent')); + $this->assertTrue(str_contains($result, 'Trash')); + $this->assertTrue(str_contains($result, 'Calendar')); + $this->assertTrue(str_contains($result, 'Contacts')); + + //TODO extract the collectionid for the next step + } + + public function testInitialSync() + { + $request = << + + + + + 0 + cca1b81c734abbcd669bea90d23e08ae + 0 + 0 + 512 + + 0 + + 1 + 1 + + + + + 16 + + EOF; + $body = self::toWbxml($request); + $response = $this->client->request('POST', '?Cmd=Sync&User=john@kolab.org&DeviceId=v140Device&DeviceType=iphone', [ + 'headers' => [ + "Content-Type" => "application/vnd.ms-sync.wbxml", + 'MS-ASProtocolVersion' => "14.0" + ], + 'body' => $body + ]); + $this->assertEquals(200, $response->getStatusCode()); + $dom = self::fromWbxml($response->getBody()); + $collections = $dom->getElementsByTagName('Collection'); + $this->assertEquals(1, $collections->length); + $collection = $collections->item(0); + $this->assertEquals("Class", $collection->childNodes->item(0)->nodeName); + $this->assertEquals("Calendar", $collection->childNodes->item(0)->nodeValue); + $this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName); + $this->assertEquals("1", $collection->childNodes->item(1)->nodeValue); + $this->assertEquals("Status", $collection->childNodes->item(3)->nodeName); + $this->assertEquals("1", $collection->childNodes->item(3)->nodeValue); + } +} diff --git a/src/tests/Infrastructure/AutodiscoverTest.php b/src/tests/Infrastructure/AutodiscoverTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Infrastructure/AutodiscoverTest.php @@ -0,0 +1,86 @@ +client = new \GuzzleHttp\Client([ + 'http_errors' => false, // No exceptions + 'base_uri' => "http://roundcube/", + 'verify' => false, + 'connect_timeout' => 10, + 'timeout' => 10, + ]); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + parent::tearDown(); + } + + public function testWellKnownOutlook() + { + $body = << + + admin@example.local + + http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a + + + + EOF; + $response = $this->client->request('POST', 'autodiscover/autodiscover.xml', [ + 'headers' => [ + "Content-Type" => "text/xml; charset=utf-8" + ], + 'body' => $body + ]); + $this->assertEquals($response->getStatusCode(), 200); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, 'example.local')); + $this->assertTrue(str_contains($data, 'admin@example.local')); + } + + public function testWellKnownActivesync() + { + $body = << + + admin@example.local + + http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006 + + + + EOF; + $response = $this->client->request('POST', 'autodiscover/autodiscover.xml', [ + 'headers' => [ + "Content-Type" => "text/xml; charset=utf-8" + ], + 'body' => $body + ]); + $this->assertEquals($response->getStatusCode(), 200); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, 'https://example.local/Microsoft-Server-ActiveSync')); + $this->assertTrue(str_contains($data, 'admin@example.local')); + } + + public function testWellKnownMail() + { + $response = $this->client->request('GET', '.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=fred@example.com'); + $this->assertEquals($response->getStatusCode(), 200); + } +} diff --git a/src/tests/Infrastructure/ChwalaTest.php b/src/tests/Infrastructure/ChwalaTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Infrastructure/ChwalaTest.php @@ -0,0 +1,70 @@ +getTestUser('jane@kolabnow.com'); + // $user->password = "simple123"; + // $user->save(); + + $this->client = new \GuzzleHttp\Client([ + 'base_uri' => "http://roundcube/chwala/", + 'verify' => false, + // 'auth' => [$user->email, $user->password], + 'auth' => ['john@kolab.org', 'simple123'], + 'connect_timeout' => 10, + 'timeout' => 10 + ]); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + $this->deleteTestUser('jane@kolabnow.com'); + + parent::tearDown(); + } + + public function testAccess() + { + //FIXME IF we use this application as a proxy this would look neater and could swap the implementation without anyone noticing. + //What other benefits could there be? + //Performance is the drawback + + $response = $this->client->request('GET', 'api/?method=authenticate&version=4'); + $this->assertEquals($response->getStatusCode(), 200); + $json = json_decode($response->getBody(), true); + + $this->assertEquals('OK', $json['status']); + $token = $json['result']['token']; + $this->assertTrue(!empty($token)); + + //FIXME the session token doesn't seem to be required + $response = $this->client->request('GET', 'api/?method=mimetypes', [ + 'headers' => [ + 'X-Session_token' => $token + ] + ]); + $this->assertEquals($response->getStatusCode(), 200); + $json = json_decode($response->getBody(), true); + $this->assertEquals('OK', $json['status']); + $this->assertEquals('OK', $json['status']); + $this->assertContains('image/png', $json['result']['view']); + $this->assertArrayHasKey('text/plain', $json['result']['edit']); + } +} diff --git a/src/tests/Infrastructure/DavTest.php b/src/tests/Infrastructure/DavTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Infrastructure/DavTest.php @@ -0,0 +1,227 @@ +getTestUser('jane@kolabnow.com'); + // $user->password = "simple123"; + // $user->save(); + + $this->client = new \GuzzleHttp\Client([ + 'http_errors' => false, // No exceptions + 'base_uri' => "http://roundcube/", + 'verify' => false, + // 'auth' => [$user->email, $user->password], + 'auth' => ['john@kolab.org', 'simple123'], + 'connect_timeout' => 10, + 'timeout' => 10, + 'headers' => [ + "Content-Type" => "application/xml; charset=utf-8", + "Depth" => "1", + ] + ]); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + // $this->deleteTestUser('jane@kolabnow.com'); + + parent::tearDown(); + } + + public function testDiscoverPrincipal() + { + + $body = ""; + $response = $this->client->request('PROPFIND', '/iRony/', ['body' => $body]); + $this->assertEquals(207, $response->getStatusCode()); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, '/iRony/principals/john@kolab.org/')); + $this->assertTrue(str_contains($data, '/iRony/calendars/')); + $this->assertTrue(str_contains($data, '/iRony/addressbooks/')); + } + + /** + * + * This codepath is triggerd by MacOS CalDAV when it tries to login. + * Verify we don't crash and end up with a 500 status code. + */ + public function testFailingLogin() + { + $body = ""; + $headers = [ + "Content-Type" => "application/xml; charset=utf-8", + "Depth" => "1", + 'body' => $body, + 'auth' => ['invaliduser@kolab.org', 'invalid'] + ]; + + $response = $this->client->request('PROPFIND', '/iRony/', $headers); + $this->assertEquals(401, $response->getStatusCode()); + } + + /** + * This codepath is triggerd by MacOS CardDAV when it tries to login. + * NOTE: This depends on the username_domain roundcube config option. + */ + public function testShortlogin() + { + $body = ""; + $response = $this->client->request('PROPFIND', '/iRony/', [ + 'body' => $body, + 'auth' => ['john', 'simple123'] + ]); + $this->assertEquals(207, $response->getStatusCode()); + } + + public function testDiscoverCalendarHomeset() + { + $body = << + + + + + EOF; + + $response = $this->client->request('PROPFIND', '/iRony/', ['body' => $body]); + $this->assertEquals(207, $response->getStatusCode()); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, '/iRony/calendars/john@kolab.org/')); + } + + public function testDiscoverCalendars() + { + $body = << + + + + + + + + EOF; + + $response = $this->client->request('PROPFIND', '/iRony/calendars/john@kolab.org', [ + 'headers' => [ + "Depth" => "infinity", + ], + 'body' => $body + ]); + $this->assertEquals(207, $response->getStatusCode()); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, '/iRony/calendars/john@kolab.org/')); + + $doc = new \DOMDocument('1.0', 'UTF-8'); + $doc->loadXML($data); + $response = $doc->getElementsByTagName('response')->item(1); + $doc->getElementsByTagName('href')->item(0); + + $this->assertEquals("d:href", $response->childNodes->item(0)->nodeName); + $href = $response->childNodes->item(0)->nodeValue; + return $href; + } + + /** + * @depends testDiscoverCalendars + */ + public function testPropfindCalendar($href) + { + $body = << + + + + + + + + + + + EOF; + + $response = $this->client->request('PROPFIND', $href, [ + 'headers' => [ + "Depth" => "0", + ], + 'body' => $body, + ]); + $this->assertEquals(207, $response->getStatusCode()); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, "$href")); + } + + /** + * Thunderbird does this and relies on the WWW-Authenticate header response to + * start sending authenticated requests. + * + * @depends testDiscoverCalendars + */ + public function testPropfindCalendarWithoutAuth($href) + { + $body = << + + + + + + + + + + + EOF; + + $response = $this->client->request('PROPFIND', $href, [ + 'headers' => [ + "Depth" => "0", + ], + 'body' => $body, + 'auth' => [] + ]); + $this->assertEquals(401, $response->getStatusCode()); + $this->assertTrue(str_contains($response->getHeader('WWW-Authenticate')[0], 'Basic realm=')); + $data = $response->getBody(); + $this->assertTrue(str_contains($data, "Sabre\DAV\Exception\NotAuthenticated")); + } + + /** + * Required for MacOS autoconfig + */ + public function testOptions() + { + $body = << + + + + + + + + EOF; + + $response = $this->client->request('OPTIONS', '/iRony/principals/john@kolab.org/', ['body' => $body]); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue(str_contains($response->getHeader('Allow')[0], 'PROPFIND')); + } +}