diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php --- a/src/app/Backends/IMAP.php +++ b/src/app/Backends/IMAP.php @@ -6,6 +6,7 @@ use App\Resource; use App\SharedFolder; use App\User; +use App\Utils; class IMAP { @@ -218,14 +219,16 @@ $settings = $resource->getSettings(['invitation_policy', 'folder']); $mailbox = self::toUTF7($settings['folder']); + $metadata = ['/shared/vendor/kolab/folder-type' => 'event']; - $acl = null; + $acl = []; if (!empty($settings['invitation_policy'])) { if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) { $acl = ["{$m[1]}, full"]; } } - self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => 'event'], $acl); + + self::createFolder($imap, $mailbox, false, $metadata, Utils::ensureAclPostPermission($acl)); $imap->closeConnection(); @@ -268,7 +271,8 @@ $acl = ["{$m[1]}, full"]; } } - self::aclUpdate($imap, $mailbox, $acl); + + self::aclUpdate($imap, $mailbox, Utils::ensureAclPostPermission($acl)); $imap->closeConnection(); @@ -316,10 +320,11 @@ $imap = self::initIMAP($config); $settings = $folder->getSettings(['acl', 'folder']); - $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null; + $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : []; $mailbox = self::toUTF7($settings['folder']); + $metadata = ['/shared/vendor/kolab/folder-type' => $folder->type]; - self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => $folder->type], $acl); + self::createFolder($imap, $mailbox, false, $metadata, Utils::ensureAclPostPermission($acl)); $imap->closeConnection(); @@ -359,7 +364,7 @@ // Note: Shared folder type does not change // ACL - self::aclUpdate($imap, $mailbox, $acl); + self::aclUpdate($imap, $mailbox, Utils::ensureAclPostPermission($acl)); $imap->closeConnection(); @@ -629,7 +634,7 @@ throw new \Exception("Failed to create mailbox {$mailbox}"); } - if ($acl) { + if (!empty($acl)) { self::aclUpdate($imap, $mailbox, $acl, true); } @@ -656,7 +661,8 @@ return \collect($acl) ->mapWithKeys(function ($item, $key) { list($user, $rights) = explode(',', $item, 2); - return [trim($user) => self::ACL_MAP[trim($rights)]]; + $rights = trim($rights); + return [trim($user) => self::ACL_MAP[$rights] ?? $rights]; }) ->all(); } diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -7,6 +7,7 @@ use App\Resource; use App\SharedFolder; use App\User; +use App\Utils; class LDAP { @@ -1015,7 +1016,7 @@ $entry['cn'] = $resource->name; $entry['owner'] = null; $entry['kolabinvitationpolicy'] = null; - $entry['acl'] = ''; + $entry['acl'] = []; $settings = $resource->getSettings(['invitation_policy', 'folder']); @@ -1044,13 +1045,15 @@ } elseif (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) { if (self::getUserEntry($ldap, $m[1], $userDN)) { $entry['owner'] = $userDN; - $entry['acl'] = $m[1] . ', full'; + $entry['acl'] = [$m[1] . ', full']; $entry['kolabinvitationpolicy'] = 'ACT_MANUAL'; } else { $entry['kolabinvitationpolicy'] = 'ACT_ACCEPT'; } } } + + $entry['acl'] = Utils::ensureAclPostPermission($entry['acl']); } /** @@ -1060,10 +1063,12 @@ { $settings = $folder->getSettings(['acl', 'folder']); + $acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : []; + $entry['cn'] = $folder->name; $entry['kolabfoldertype'] = $folder->type; $entry['kolabtargetfolder'] = $settings['folder'] ?? ''; - $entry['acl'] = !empty($settings['acl']) ? json_decode($settings['acl'], true) : ''; + $entry['acl'] = Utils::ensureAclPostPermission($acl); $entry['alias'] = $folder->aliases()->pluck('alias')->all(); } diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -146,6 +146,32 @@ return strtolower($email); } + /** + * Make sure that IMAP folder access rights contains "anyone: p" permission + * + * @param array $acl ACL (in form of "user, permission" records) + * + * @return array ACL list + */ + public static function ensureAclPostPermission(array $acl): array + { + foreach ($acl as $idx => $entry) { + if (str_starts_with($entry, 'anyone,')) { + if (strpos($entry, 'read-only')) { + $acl[$idx] = 'anyone, lrsp'; + } elseif (strpos($entry, 'read-write')) { + $acl[$idx] = 'anyone, lrswitednp'; + } + + return $acl; + } + } + + $acl[] = 'anyone, p'; + + return $acl; + } + /** * Generate a passphrase. Not intended for use in production, so limited to environments that are not production. * diff --git a/src/tests/Feature/Backends/IMAPTest.php b/src/tests/Feature/Backends/IMAPTest.php --- a/src/tests/Feature/Backends/IMAPTest.php +++ b/src/tests/Feature/Backends/IMAPTest.php @@ -205,10 +205,9 @@ $this->assertTrue(IMAP::verifySharedFolder($imapFolder = $resource->getSetting('folder'))); $imap = $this->getImap(); - $expectedAcl = ['john@kolab.org' => str_split('lrswipkxtecdn')]; + $expectedAcl = ['anyone' => ['p'], 'john@kolab.org' => str_split('lrswipkxtecdn')]; $acl = $imap->getACL(IMAP::toUTF7($imapFolder)); - $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org'])); - $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']); + $this->assertSame($expectedAcl, $acl); // Update the resource (rename) $resource->name = 'Resource1 ©' . time(); @@ -219,13 +218,12 @@ $this->assertTrue($imapFolder != $newImapFolder); $this->assertTrue(IMAP::verifySharedFolder($newImapFolder)); $acl = $imap->getACL(IMAP::toUTF7($newImapFolder)); - $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org'])); - $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']); + $this->assertSame($expectedAcl, $acl); // Update the resource (acl change) $resource->setSetting('invitation_policy', 'accept'); $this->assertTrue(IMAP::updateResource($resource)); - $this->assertSame([], $imap->getACL(IMAP::toUTF7($newImapFolder))); + $this->assertSame(['anyone' => ['p']], $imap->getACL(IMAP::toUTF7($newImapFolder))); // Delete the resource $this->assertTrue(IMAP::deleteResource($resource)); @@ -252,27 +250,28 @@ $imap = $this->getImap(); $expectedAcl = [ + 'anyone' => ['p'], + 'jack@kolab.org' => str_split('lrs'), 'john@kolab.org' => str_split('lrswipkxtecdn'), - 'jack@kolab.org' => str_split('lrs') ]; $acl = $imap->getACL(IMAP::toUTF7($imapFolder)); - $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org'])); - $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']); - $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org'])); - $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']); + ksort($acl); + $this->assertSame($expectedAcl, $acl); // 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')]; + $expectedAcl = [ + 'anyone' => ['p'], + 'jack@kolab.org' => str_split('lrs'), + ]; $acl = $imap->getACL(IMAP::toUTF7($imapFolder)); - $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org'])); - $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']); - $this->assertTrue(!isset($acl['john@kolab.org'])); + ksort($acl); + $this->assertSame($expectedAcl, $acl); // Update the shared folder (rename) $folder->name = 'SharedFolder1 ©' . time(); @@ -284,8 +283,8 @@ $this->assertTrue(IMAP::verifySharedFolder($newImapFolder)); $acl = $imap->getACL(IMAP::toUTF7($newImapFolder)); - $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org'])); - $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']); + ksort($acl); + $this->assertSame($expectedAcl, $acl); // Delete the shared folder $this->assertTrue(IMAP::deleteSharedFolder($folder)); diff --git a/src/tests/Feature/Backends/LDAPTest.php b/src/tests/Feature/Backends/LDAPTest.php --- a/src/tests/Feature/Backends/LDAPTest.php +++ b/src/tests/Feature/Backends/LDAPTest.php @@ -247,7 +247,7 @@ 'kolabtargetfolder' => 'shared/Resources/Test1@kolab.org', 'kolabinvitationpolicy' => null, 'owner' => null, - 'acl' => null, + 'acl' => 'anyone, p', ]; foreach ($expected as $attr => $value) { @@ -267,7 +267,7 @@ $expected['owner'] = 'uid=john@kolab.org,ou=People,ou=kolab.org,' . $root_dn; $expected['dn'] = 'cn=Te(\\3dść)1,ou=Resources,ou=kolab.org,' . $root_dn; $expected['cn'] = 'Te(=ść)1'; - $expected['acl'] = 'john@kolab.org, full'; + $expected['acl'] = ['john@kolab.org, full', 'anyone, p']; $ldap_resource = LDAP::getResource($resource->email); @@ -281,7 +281,7 @@ LDAP::updateResource($resource); - $expected['acl'] = null; + $expected['acl'] = 'anyone, p'; $expected['kolabinvitationpolicy'] = null; $expected['owner'] = null; @@ -330,7 +330,7 @@ ], 'kolabfoldertype' => 'event', 'kolabtargetfolder' => 'shared/test-folder@kolab.org', - 'acl' => null, + 'acl' => 'anyone, p', 'alias' => null, ]; @@ -349,7 +349,7 @@ LDAP::updateSharedFolder($folder); $expected['kolabtargetfolder'] = 'shared/Te(=ść)1@kolab.org'; - $expected['acl'] = ['john@kolab.org, read-write', 'anyone, read-only']; + $expected['acl'] = ['john@kolab.org, read-write', 'anyone, lrsp']; $expected['dn'] = 'cn=Te(\\3dść)1,ou=Shared Folders,ou=kolab.org,' . $root_dn; $expected['cn'] = 'Te(=ść)1'; $expected['alias'] = $aliases; diff --git a/src/tests/Unit/UtilsTest.php b/src/tests/Unit/UtilsTest.php --- a/src/tests/Unit/UtilsTest.php +++ b/src/tests/Unit/UtilsTest.php @@ -58,6 +58,24 @@ $this->assertSame('shared+shared/Test@test.tld', Utils::emailToLower('shared+shared/Test@Test.Tld')); } + /** + * Test for Utils::ensureAclPostPermission() + */ + public function testEnsureAclPostPermission(): void + { + $acl = []; + $this->assertSame(['anyone, p'], Utils::ensureAclPostPermission($acl)); + + $acl = ['anyone, full']; + $this->assertSame(['anyone, full'], Utils::ensureAclPostPermission($acl)); + + $acl = ['anyone, read-only']; + $this->assertSame(['anyone, lrsp'], Utils::ensureAclPostPermission($acl)); + + $acl = ['anyone, read-write']; + $this->assertSame(['anyone, lrswitednp'], Utils::ensureAclPostPermission($acl)); + } + /** * Test for Utils::money() */