diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php index 5f5bbd91..ea41bf10 100644 --- a/src/app/Backends/IMAP.php +++ b/src/app/Backends/IMAP.php @@ -1,650 +1,704 @@ 'lrs', 'read-write' => 'lrswitedn', 'full' => 'lrswipkxtecdn', ]; /** * Delete a group. * * @param \App\Group $group Group * * @return bool True if a group was deleted successfully, False otherwise * @throws \Exception */ public static function deleteGroup(Group $group): bool { $domainName = explode('@', $group->email, 2)[1]; // Cleanup ACL // FIXME: Since all groups in Kolab4 have email address, // should we consider using it in ACL instead of the name? // Also we need to decide what to do and configure IMAP appropriately, // right now groups in ACL does not work for me at all. \App\Jobs\IMAP\AclCleanupJob::dispatch($group->name, $domainName); return true; } /** * Create a mailbox. * * @param \App\User $user User * * @return bool True if a mailbox was created successfully, False otherwise * @throws \Exception */ public static function createUser(User $user): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $mailbox = self::toUTF7('user/' . $user->email); // Mailbox already exists if (self::folderExists($imap, $mailbox)) { $imap->closeConnection(); + self::createDefaultFolders($user); return true; } // Create the mailbox if (!$imap->createFolder($mailbox)) { \Log::error("Failed to create mailbox {$mailbox}"); $imap->closeConnection(); return false; } // Wait until it's propagated (for Cyrus Murder setup) // FIXME: Do we still need this? if (strpos($imap->conn->data['GREETING'] ?? '', 'Cyrus IMAP Murder') !== false) { $tries = 30; while ($tries-- > 0) { $folders = $imap->listMailboxes('', $mailbox); if (is_array($folders) && count($folders)) { break; } sleep(1); $imap->closeConnection(); $imap = self::initIMAP($config); } } // Set quota $quota = $user->countEntitlementsBySku('storage') * 1048576; if ($quota) { $imap->setQuota($mailbox, ['storage' => $quota]); } + self::createDefaultFolders($user); + $imap->closeConnection(); return true; } + /** + * Create default folders for the user. + * + * @param \App\User $user User + */ + public static function createDefaultFolders(User $user): void + { + if ($defaultFolders = \config('imap.default_folders')) { + $config = self::getConfig(); + // Log in as user to set private annotations and subscription state + $imap = self::initIMAP($config, $user->email); + foreach ($defaultFolders as $name => $folderconfig) { + try { + $mailbox = self::toUTF7($name); + self::createFolder($imap, $mailbox, true, $folderconfig['metadata']); + } catch (\Exception $e) { + \Log::warning("Failed to create the default folder" . $e->getMessage()); + } + } + $imap->closeConnection(); + } + } + /** * Delete a mailbox. * * @param \App\User $user User * * @return bool True if a mailbox was deleted successfully, False otherwise * @throws \Exception */ public static function deleteUser(User $user): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $mailbox = self::toUTF7('user/' . $user->email); // To delete the mailbox cyrus-admin needs extra permissions $imap->setACL($mailbox, $config['user'], 'c'); // Delete the mailbox (no need to delete subfolders?) $result = $imap->deleteFolder($mailbox); $imap->closeConnection(); // Cleanup ACL \App\Jobs\IMAP\AclCleanupJob::dispatch($user->email); return $result; } /** * Update a mailbox (quota). * * @param \App\User $user User * * @return bool True if a mailbox was updated successfully, False otherwise * @throws \Exception */ public static function updateUser(User $user): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $mailbox = self::toUTF7('user/' . $user->email); $result = true; // Set quota $quota = $user->countEntitlementsBySku('storage') * 1048576; if ($quota) { $result = $imap->setQuota($mailbox, ['storage' => $quota]); } $imap->closeConnection(); return $result; } /** * Create a resource. * * @param \App\Resource $resource Resource * * @return bool True if a resource was created successfully, False otherwise * @throws \Exception */ public static function createResource(Resource $resource): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $resource->getSettings(['invitation_policy', 'folder']); $mailbox = self::toUTF7($settings['folder']); - // Mailbox already exists - if (self::folderExists($imap, $mailbox)) { - $imap->closeConnection(); - return true; - } - - // Create the shared folder - if (!$imap->createFolder($mailbox)) { - \Log::error("Failed to create mailbox {$mailbox}"); - $imap->closeConnection(); - return false; - } - - // Set folder type - $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => 'event']); - - // Set ACL + $acl = null; if (!empty($settings['invitation_policy'])) { if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) { - self::aclUpdate($imap, $mailbox, ["{$m[1]}, full"]); + $acl = ["{$m[1]}, full"]; } } + self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => 'event'], $acl); $imap->closeConnection(); return true; } /** * Update a resource. * * @param \App\Resource $resource Resource * @param array $props Old resource properties * * @return bool True if a resource was updated successfully, False otherwise * @throws \Exception */ public static function updateResource(Resource $resource, array $props = []): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $resource->getSettings(['invitation_policy', 'folder']); $folder = $settings['folder']; $mailbox = self::toUTF7($folder); // Rename the mailbox (only possible if we have the old folder) if (!empty($props['folder']) && $props['folder'] != $folder) { $oldMailbox = self::toUTF7($props['folder']); if (!$imap->renameFolder($oldMailbox, $mailbox)) { \Log::error("Failed to rename mailbox {$oldMailbox} to {$mailbox}"); $imap->closeConnection(); return false; } } // ACL $acl = []; if (!empty($settings['invitation_policy'])) { if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) { $acl = ["{$m[1]}, full"]; } } self::aclUpdate($imap, $mailbox, $acl); $imap->closeConnection(); return true; } /** * Delete a resource. * * @param \App\Resource $resource Resource * * @return bool True if a resource was deleted successfully, False otherwise * @throws \Exception */ public static function deleteResource(Resource $resource): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $resource->getSettings(['folder']); $mailbox = self::toUTF7($settings['folder']); // To delete the mailbox cyrus-admin needs extra permissions $imap->setACL($mailbox, $config['user'], 'c'); // Delete the mailbox (no need to delete subfolders?) $result = $imap->deleteFolder($mailbox); $imap->closeConnection(); return $result; } /** * Create a shared folder. * * @param \App\SharedFolder $folder Shared folder * * @return bool True if a falder was created successfully, False otherwise * @throws \Exception */ public static function createSharedFolder(SharedFolder $folder): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $folder->getSettings(['acl', 'folder']); $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null; $mailbox = self::toUTF7($settings['folder']); - // Mailbox already exists - if (self::folderExists($imap, $mailbox)) { - $imap->closeConnection(); - return true; - } - - // Create the mailbox - if (!$imap->createFolder($mailbox)) { - \Log::error("Failed to create mailbox {$mailbox}"); - $imap->closeConnection(); - return false; - } - - // Set folder type - $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => $folder->type]); - - // Set ACL - self::aclUpdate($imap, $mailbox, $acl); + self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => $folder->type], $acl); $imap->closeConnection(); return true; } /** * Update a shared folder. * * @param \App\SharedFolder $folder Shared folder * @param array $props Old folder properties * * @return bool True if a falder was updated successfully, False otherwise * @throws \Exception */ public static function updateSharedFolder(SharedFolder $folder, array $props = []): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $folder->getSettings(['acl', 'folder']); $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null; $folder = $settings['folder']; $mailbox = self::toUTF7($folder); // Rename the mailbox if (!empty($props['folder']) && $props['folder'] != $folder) { $oldMailbox = self::toUTF7($props['folder']); if (!$imap->renameFolder($oldMailbox, $mailbox)) { \Log::error("Failed to rename mailbox {$oldMailbox} to {$mailbox}"); $imap->closeConnection(); return false; } } // Note: Shared folder type does not change // ACL self::aclUpdate($imap, $mailbox, $acl); $imap->closeConnection(); return true; } /** * Delete a shared folder. * * @param \App\SharedFolder $folder Shared folder * * @return bool True if a falder was deleted successfully, False otherwise * @throws \Exception */ public static function deleteSharedFolder(SharedFolder $folder): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $settings = $folder->getSettings(['folder']); $mailbox = self::toUTF7($settings['folder']); // To delete the mailbox cyrus-admin needs extra permissions $imap->setACL($mailbox, $config['user'], 'c'); // Delete the mailbox $result = $imap->deleteFolder($mailbox); $imap->closeConnection(); return $result; } /** * Check if a shared folder is set up. * * @param string $folder Folder name, e.g. shared/Resources/Name@domain.tld * * @return bool True if a folder exists and is set up, False otherwise */ public static function verifySharedFolder(string $folder): bool { $config = self::getConfig(); $imap = self::initIMAP($config); // Convert the folder from UTF8 to UTF7-IMAP if (\preg_match('#^(shared/|shared/Resources/)(.+)(@[^@]+)$#', $folder, $matches)) { $folderName = self::toUTF7($matches[2]); $folder = $matches[1] . $folderName . $matches[3]; } // FIXME: just listMailboxes() does not return shared folders at all $metadata = $imap->getMetadata($folder, ['/shared/vendor/kolab/folder-type']); $imap->closeConnection(); // Note: We have to use error code to distinguish an error from "no mailbox" response if ($imap->errornum === \rcube_imap_generic::ERROR_NO) { return false; } if ($imap->errornum !== \rcube_imap_generic::ERROR_OK) { throw new \Exception("Failed to get folder metadata from IMAP"); } return true; } /** * Convert UTF8 string to UTF7-IMAP encoding */ public static function toUTF7(string $string): string { return \mb_convert_encoding($string, 'UTF7-IMAP', 'UTF8'); } /** * Check if an account is set up * * @param string $username User login (email address) * * @return bool True if an account exists and is set up, False otherwise */ public static function verifyAccount(string $username): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $mailbox = self::toUTF7('user/' . $username); // Mailbox already exists if (self::folderExists($imap, $mailbox)) { $imap->closeConnection(); return true; } $imap->closeConnection(); return false; } + /** + * Check if an account is set up + * + * @param string $username User login (email address) + * + * @return bool True if an account exists and is set up, False otherwise + */ + public static function verifyDefaultFolders(string $username): bool + { + $config = self::getConfig(); + $imap = self::initIMAP($config, $username); + + foreach (\config('imap.default_folders') as $mb => $_metadata) { + $mailbox = self::toUTF7($mb); + if (!self::folderExists($imap, $mailbox)) { + $imap->closeConnection(); + return false; + } + } + + $imap->closeConnection(); + return true; + } + /** * Check if we can connect to the imap server * * @return bool True on success */ public static function healthcheck(): bool { $config = self::getConfig(); $imap = self::initIMAP($config); $imap->closeConnection(); return true; } /** * Remove ACL for a specified user/group anywhere in the IMAP * * @param string $ident ACL identifier (user email or e.g. group name) * @param string $domain ACL domain */ public static function aclCleanup(string $ident, string $domain = ''): void { $config = self::getConfig(); $imap = self::initIMAP($config); if (strpos($ident, '@')) { $domain = explode('@', $ident, 2)[1]; } $callback = function ($folder) use ($imap, $ident) { $acl = $imap->getACL($folder); if (is_array($acl) && isset($acl[$ident])) { $imap->deleteACL($folder, $ident); } }; $folders = $imap->listMailboxes('', "user/*@{$domain}"); if (!is_array($folders)) { $imap->closeConnection(); throw new \Exception("Failed to get IMAP folders"); } array_walk($folders, $callback); $folders = $imap->listMailboxes('', "shared/*@{$domain}"); if (!is_array($folders)) { $imap->closeConnection(); throw new \Exception("Failed to get IMAP folders"); } array_walk($folders, $callback); $imap->closeConnection(); } + /** + * Create a folder and set some default properties + * + * @param \rcube_imap_generic $imap The imap instance + * @param string $mailbox Mailbox name + * @param bool $subscribe Subscribe to the folder + * @param array $metadata Metadata to set on the folder + * @param array $acl Acl to set on the folder + * + * @return bool True when having a folder created, False if it already existed. + * @throws \Exception + */ + private static function createFolder($imap, string $mailbox, $subscribe = false, $metadata = null, $acl = null) + { + if (self::folderExists($imap, $mailbox)) { + return false; + } + + if (!$imap->createFolder($mailbox)) { + throw new \Exception("Failed to create mailbox {$mailbox}"); + } + + if ($acl) { + self::aclUpdate($imap, $mailbox, $acl, true); + } + + if ($subscribe) { + $imap->subscribe($mailbox); + } + + foreach ($metadata as $key => $value) { + $imap->setMetadata($mailbox, [$key => $value]); + } + + return true; + } + /** * Convert Kolab ACL into IMAP user->rights array */ private static function aclToImap($acl): array { if (empty($acl)) { return []; } return \collect($acl) ->mapWithKeys(function ($item, $key) { list($user, $rights) = explode(',', $item, 2); return [trim($user) => self::ACL_MAP[trim($rights)]]; }) ->all(); } /** * Update folder ACL */ private static function aclUpdate($imap, $mailbox, $acl, bool $isNew = false) { $imapAcl = $isNew ? [] : $imap->getACL($mailbox); if (is_array($imapAcl)) { foreach (self::aclToImap($acl) as $user => $rights) { if (empty($imapAcl[$user]) || implode('', $imapAcl[$user]) !== $rights) { $imap->setACL($mailbox, $user, $rights); } unset($imapAcl[$user]); } foreach ($imapAcl as $user => $rights) { $imap->deleteACL($mailbox, $user); } } } /** * Check if an IMAP folder exists */ private static function folderExists($imap, string $folder): bool { $folders = $imap->listMailboxes('', $folder); if (!is_array($folders)) { $imap->closeConnection(); throw new \Exception("Failed to get IMAP folders"); } return count($folders) > 0; } /** * Initialize connection to IMAP */ private static function initIMAP(array $config, string $login_as = null) { $imap = new \rcube_imap_generic(); if (\config('app.debug')) { $imap->setDebug(true, 'App\Backends\IMAP::logDebug'); } if ($login_as) { $config['options']['auth_cid'] = $config['user']; $config['options']['auth_pw'] = $config['password']; $config['options']['auth_type'] = 'PLAIN'; $config['user'] = $login_as; } $imap->connect($config['host'], $config['user'], $config['password'], $config['options']); if (!$imap->connected()) { $message = sprintf("Login failed for %s against %s. %s", $config['user'], $config['host'], $imap->error); \Log::error($message); throw new \Exception("Connection to IMAP failed"); } return $imap; } /** * Get LDAP configuration for specified access level */ private static function getConfig() { $uri = \parse_url(\config('imap.uri')); $default_port = 143; $ssl_mode = null; if (isset($uri['scheme'])) { if (preg_match('/^(ssl|imaps)/', $uri['scheme'])) { $default_port = 993; $ssl_mode = 'ssl'; } elseif ($uri['scheme'] === 'tls') { $ssl_mode = 'tls'; } } $config = [ 'host' => $uri['host'], 'user' => \config('imap.admin_login'), 'password' => \config('imap.admin_password'), 'options' => [ 'port' => !empty($uri['port']) ? $uri['port'] : $default_port, 'ssl_mode' => $ssl_mode, 'socket_options' => [ 'ssl' => [ 'verify_peer' => \config('imap.verify_peer'), 'verify_peer_name' => \config('imap.verify_peer'), 'verify_host' => \config('imap.verify_host') ], ], ], ]; return $config; } /** * Debug logging callback */ public static function logDebug($conn, $msg): void { $msg = '[IMAP] ' . $msg; \Log::debug($msg); } } diff --git a/src/config/imap.php b/src/config/imap.php index 1c5ba77c..ebe74b4c 100644 --- a/src/config/imap.php +++ b/src/config/imap.php @@ -1,12 +1,60 @@ env('IMAP_URI', 'ssl://kolab:11993'), 'admin_login' => env('IMAP_ADMIN_LOGIN', 'cyrus-admin'), 'admin_password' => env('IMAP_ADMIN_PASSWORD', null), 'verify_peer' => env('IMAP_VERIFY_PEER', true), 'verify_host' => env('IMAP_VERIFY_HOST', true), 'host' => env('IMAP_HOST', '172.18.0.5'), 'imap_port' => env('IMAP_PORT', 12143), 'guam_port' => env('IMAP_GUAM_PORT', 9143), + 'default_folders' => [ + 'Drafts' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'mail.drafts', + ], + ], + 'Sent' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'mail.sentitems', + ], + ], + 'Trash' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'mail.wastebasket', + ], + ], + 'Spam' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'mail.junkemail', + ], + ], + + 'Calendar' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'event.default' + ], + ], + 'Contacts' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'contact.default', + ], + ], + 'Tasks' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'task.default', + ], + ], + 'Notes' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'note.default', + ], + ], + 'Files' => [ + 'metadata' => [ + '/private/vendor/kolab/folder-type' => 'file.default', + ], + ], + ] ]; diff --git a/src/tests/Feature/Backends/IMAPTest.php b/src/tests/Feature/Backends/IMAPTest.php index e2596b17..6a6e7d0d 100644 --- a/src/tests/Feature/Backends/IMAPTest.php +++ b/src/tests/Feature/Backends/IMAPTest.php @@ -1,276 +1,278 @@ imap) { $this->imap->closeConnection(); $this->imap = null; } if ($this->user) { $this->deleteTestUser($this->user->email); } if ($this->group) { $this->deleteTestGroup($this->group->email); } if ($this->resource) { $this->deleteTestResource($this->resource->email); } if ($this->folder) { $this->deleteTestSharedFolder($this->folder->email); } parent::tearDown(); } /** * Test aclCleanup() * * @group imap * @group ldap */ public function testAclCleanup(): void { $this->user = $user = $this->getTestUser('test-' . time() . '@kolab.org'); $this->group = $group = $this->getTestGroup('test-group-' . time() . '@kolab.org'); // SETACL requires that the user/group exists in LDAP LDAP::createUser($user); // LDAP::createGroup($group); // First, set some ACLs that we'll expect to be removed later $imap = $this->getImap(); $this->assertTrue($imap->setACL('user/john@kolab.org', $user->email, 'lrs')); $this->assertTrue($imap->setACL('shared/Resources/Conference Room #1@kolab.org', $user->email, 'lrs')); /* $this->assertTrue($imap->setACL('user/john@kolab.org', $group->name, 'lrs')); $this->assertTrue($imap->setACL('shared/Resources/Conference Room #1@kolab.org', $group->name, 'lrs')); */ // Cleanup ACL of a user IMAP::aclCleanup($user->email); $acl = $imap->getACL('user/john@kolab.org'); $this->assertTrue(is_array($acl) && !isset($acl[$user->email])); $acl = $imap->getACL('shared/Resources/Conference Room #1@kolab.org'); $this->assertTrue(is_array($acl) && !isset($acl[$user->email])); /* // Cleanup ACL of a group IMAP::aclCleanup($group->name, 'kolab.org'); $acl = $imap->getACL('user/john@kolab.org'); $this->assertTrue(is_array($acl) && !isset($acl[$user->email])); $acl = $imap->getACL('shared/Resources/Conference Room #1@kolab.org'); $this->assertTrue(is_array($acl) && !isset($acl[$user->email])); */ } /** * Test creating/updating/deleting an IMAP account * * @group imap */ public function testUsers(): void { $this->user = $user = $this->getTestUser('test-' . time() . '@' . \config('app.domain')); $storage = \App\Sku::withEnvTenantContext()->where('title', 'storage')->first(); $user->assignSku($storage, 1, $user->wallets->first()); $expectedQuota = [ 'user/' . $user->email => [ 'storage' => [ 'used' => 0, 'total' => 1048576 ] ] ]; // Create the mailbox $result = IMAP::createUser($user); $this->assertTrue($result); $this->assertTrue(IMAP::verifyAccount($user->email)); + $this->assertTrue(IMAP::verifyDefaultFolders($user->email)); $imap = $this->getImap(); $quota = $imap->getQuota('user/' . $user->email); $this->assertSame($expectedQuota, $quota['all']); // Update the mailbox (increase quota) $user->assignSku($storage, 1, $user->wallets->first()); $expectedQuota['user/' . $user->email]['storage']['total'] = 1048576 * 2; $result = IMAP::updateUser($user); $this->assertTrue($result); $quota = $imap->getQuota('user/' . $user->email); $this->assertSame($expectedQuota, $quota['all']); // Delete the mailbox $result = IMAP::deleteUser($user); $this->assertTrue($result); $result = IMAP::verifyAccount($user->email); $this->assertFalse($result); + $this->assertFalse(IMAP::verifyDefaultFolders($user->email)); } /** * Test creating/updating/deleting a resource * * @group imap */ public function testResources(): void { $this->resource = $resource = $this->getTestResource( 'test-resource-' . time() . '@kolab.org', ['name' => 'Resource ©' . time()] ); $resource->setSetting('invitation_policy', 'manual:john@kolab.org'); // Create the resource $this->assertTrue(IMAP::createResource($resource)); $this->assertTrue(IMAP::verifySharedFolder($imapFolder = $resource->getSetting('folder'))); $imap = $this->getImap(); $expectedAcl = ['john@kolab.org' => str_split('lrswipkxtecdn')]; $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder))); // Update the resource (rename) $resource->name = 'Resource1 ©' . time(); $resource->save(); $newImapFolder = $resource->getSetting('folder'); $this->assertTrue(IMAP::updateResource($resource, ['folder' => $imapFolder])); $this->assertTrue($imapFolder != $newImapFolder); $this->assertTrue(IMAP::verifySharedFolder($newImapFolder)); $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder))); // Update the resource (acl change) $resource->setSetting('invitation_policy', 'accept'); $this->assertTrue(IMAP::updateResource($resource)); $this->assertSame([], $imap->getACL(IMAP::toUTF7($newImapFolder))); // Delete the resource $this->assertTrue(IMAP::deleteResource($resource)); $this->assertFalse(IMAP::verifySharedFolder($newImapFolder)); } /** * Test creating/updating/deleting a shared folder * * @group imap */ public function testSharedFolders(): void { $this->folder = $folder = $this->getTestSharedFolder( 'test-folder-' . time() . '@kolab.org', ['name' => 'SharedFolder ©' . time()] ); $folder->setSetting('acl', json_encode(['john@kolab.org, full', 'jack@kolab.org, read-only'])); // Create the shared folder $this->assertTrue(IMAP::createSharedFolder($folder)); $this->assertTrue(IMAP::verifySharedFolder($imapFolder = $folder->getSetting('folder'))); $imap = $this->getImap(); $expectedAcl = [ 'john@kolab.org' => str_split('lrswipkxtecdn'), 'jack@kolab.org' => str_split('lrs') ]; $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder))); // Update shared folder (acl) $folder->setSetting('acl', json_encode(['jack@kolab.org, read-only'])); $this->assertTrue(IMAP::updateSharedFolder($folder)); $expectedAcl = ['jack@kolab.org' => str_split('lrs')]; $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder))); // Update the shared folder (rename) $folder->name = 'SharedFolder1 ©' . time(); $folder->save(); $newImapFolder = $folder->getSetting('folder'); $this->assertTrue(IMAP::updateSharedFolder($folder, ['folder' => $imapFolder])); $this->assertTrue($imapFolder != $newImapFolder); $this->assertTrue(IMAP::verifySharedFolder($newImapFolder)); $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder))); // Delete the shared folder $this->assertTrue(IMAP::deleteSharedFolder($folder)); $this->assertFalse(IMAP::verifySharedFolder($newImapFolder)); } /** * Test verifying IMAP account existence (existing account) * * @group imap */ public function testVerifyAccountExisting(): void { // existing user $result = IMAP::verifyAccount('john@kolab.org'); $this->assertTrue($result); // non-existing user $result = IMAP::verifyAccount('non-existing@domain.tld'); $this->assertFalse($result); } /** * Test verifying IMAP shared folder existence * * @group imap */ public function testVerifySharedFolder(): void { // non-existing $result = IMAP::verifySharedFolder('shared/Resources/UnknownResource@kolab.org'); $this->assertFalse($result); // existing $result = IMAP::verifySharedFolder('shared/Calendar@kolab.org'); $this->assertTrue($result); } /** * Get configured/initialized rcube_imap_generic instance */ private function getImap() { if ($this->imap) { return $this->imap; } $class = new \ReflectionClass(IMAP::class); $init = $class->getMethod('initIMAP'); $config = $class->getMethod('getConfig'); $init->setAccessible(true); $config->setAccessible(true); $config = $config->invoke(null); return $this->imap = $init->invokeArgs(null, [$config]); } }