feat(mailbox sharing): Read, cache and expose my ACL rights

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2022-12-05 18:18:09 +01:00 коммит произвёл greta
Родитель 839f9b7571
Коммит 76d7d7c956
11 изменённых файлов: 204 добавлений и 25 удалений

Просмотреть файл

@ -12,7 +12,7 @@
- **🙈 Were not reinventing the wheel!** Based on the great [Horde](https://horde.org) libraries.
- **📬 Want to host your own mail server?** We do not have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
<version>2.3.0-alpha.0</version>
<version>2.3.0-alpha.1</version>
<licence>agpl</licence>
<author>Greta Doçi</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>

Просмотреть файл

@ -66,6 +66,8 @@ use function strtolower;
* @method void setSpecialUse(string $specialUse)
* @method bool|null getSyncInBackground()
* @method void setSyncInBackground(bool $sync)
* @method string|null getMyAcls()
* @method void setMyAcls(string|null $acls)
*/
class Mailbox extends Entity implements JsonSerializable {
protected $name;
@ -83,6 +85,7 @@ class Mailbox extends Entity implements JsonSerializable {
protected $selectable;
protected $specialUse;
protected $syncInBackground;
protected $myAcls;
/**
* @var int
@ -144,7 +147,7 @@ class Mailbox extends Entity implements JsonSerializable {
* @return MailboxStats
*/
public function getStats(): MailboxStats {
return new MailboxStats($this->getMessages(), $this->getUnseen());
return new MailboxStats($this->getMessages(), $this->getUnseen(), $this->getMyAcls());
}
#[ReturnTypeWillChange]
@ -163,6 +166,7 @@ class Mailbox extends Entity implements JsonSerializable {
'mailboxes' => [],
'syncInBackground' => ($this->getSyncInBackground() === true),
'unread' => $this->unseen,
'myAcls' => $this->myAcls,
];
}
}

Просмотреть файл

@ -44,6 +44,8 @@ class Folder {
/** @var string[] */
private $specialUse;
private ?string $myAcls;
public function __construct(int $accountId, Horde_Imap_Client_Mailbox $mailbox, array $attributes, ?string $delimiter) {
$this->accountId = $accountId;
$this->mailbox = $mailbox;
@ -51,6 +53,7 @@ class Folder {
$this->delimiter = $delimiter;
$this->status = [];
$this->specialUse = [];
$this->myAcls = null;
}
/**
@ -99,4 +102,12 @@ class Folder {
public function getSpecialUse() {
return $this->specialUse;
}
public function setMyAcls(?string $acls) {
$this->myAcls = $acls;
}
public function getMyAcls(): ?string {
return $this->myAcls;
}
}

Просмотреть файл

@ -124,11 +124,19 @@ class FolderMapper {
}));
$status = $client->status($mailboxes);
$hasAcls = $client->capability->query('ACL');
foreach ($folders as $folder) {
if (isset($status[$folder->getMailbox()])) {
$folder->setStatus($status[$folder->getMailbox()]);
}
$acls = null;
if ($hasAcls) {
$acls = (string)$client->getMyACLRights($folder->getMailbox());
}
$folder->setMyAcls($acls);
}
}
@ -143,10 +151,15 @@ class FolderMapper {
public function getFoldersStatusAsObject(Horde_Imap_Client_Socket $client,
string $mailbox): MailboxStats {
$status = $client->status($mailbox);
$acls = null;
if ($client->capability->query('ACL')) {
$acls = (string)$client->getMyACLRights($mailbox);
}
return new MailboxStats(
$status['messages'],
$status['unseen']
$status['unseen'],
$acls
);
}

Просмотреть файл

@ -30,36 +30,34 @@ use JsonSerializable;
use ReturnTypeWillChange;
class MailboxStats implements JsonSerializable {
/** @var int */
private $total;
private int $total;
private int $unread;
private ?string $myAcls;
/** @var int */
private $unread;
public function __construct(int $total, int $unread) {
public function __construct(int $total, int $unread, ?string $myAcls) {
$this->total = $total;
$this->unread = $unread;
$this->myAcls = $myAcls;
}
/**
* @return int
*/
public function getTotal(): int {
return $this->total;
}
/**
* @return int
*/
public function getUnread(): int {
return $this->unread;
}
public function getMyAcls(): ?string {
return $this->myAcls;
}
#[ReturnTypeWillChange]
public function jsonSerialize() {
return [
'total' => $this->total,
'unread' => $this->unread,
'myAcls' => $this->myAcls,
];
}
}

Просмотреть файл

@ -205,6 +205,7 @@ class MailboxSync {
$mailbox->setUnseen($folder->getStatus()['unseen'] ?? 0);
$mailbox->setSelectable(!in_array('\noselect', $folder->getAttributes()));
$mailbox->setSpecialUse(json_encode($folder->getSpecialUse()));
$mailbox->setMyAcls($folder->getMyAcls());
$this->mailboxMapper->update($mailbox);
}
@ -218,6 +219,7 @@ class MailboxSync {
$mailbox->setUnseen($folder->getStatus()['unseen'] ?? 0);
$mailbox->setSelectable(!in_array('\noselect', $folder->getAttributes()));
$mailbox->setSpecialUse(json_encode($folder->getSpecialUse()));
$mailbox->setMyAcls($folder->getMyAcls());
$this->mailboxMapper->insert($mailbox);
}
}

Просмотреть файл

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Mail\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version2300Date20221205160349 extends SimpleMigrationStep {
private IDBConnection $db;
public function __construct(IDBConnection $db) {
$this->db = $db;
}
/**
* @param IOutput $output
* @param Closure $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('mail_mailboxes');
if (!$table->hasColumn('my_acls')) {
$table->addColumn('my_acls', Types::STRING, [
'length' => 32,
'notnull' => false,
]);
}
return $schema;
}
/**
* @param IOutput $output
* @param Closure $schemaClosure
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
$qb = $this->db->getQueryBuilder();
$qb->update('mail_accounts')
->set('last_mailbox_sync', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT));
$qb->executeStatement();
}
}

Просмотреть файл

@ -135,10 +135,11 @@ class MailboxesControllerTest extends TestCase {
$this->assertEquals($expected, $response);
}
public function testStats() {
public function testStats(): void {
$mailbox = new Mailbox();
$mailbox->setUnseen(10);
$mailbox->setMessages(42);
$mailbox->setMyAcls(null);
$this->mailManager->expects($this->once())
->method('getMailbox')
->with('john', 13)
@ -146,7 +147,7 @@ class MailboxesControllerTest extends TestCase {
$response = $this->controller->stats(13);
$stats = new MailboxStats(42, 10);
$stats = new MailboxStats(42, 10, null);
$expected = new JSONResponse($stats);
$this->assertEquals($expected, $response);
}

Просмотреть файл

@ -24,6 +24,8 @@ declare(strict_types=1);
namespace OCA\Mail\Tests\Unit\IMAP;
use Horde_Imap_Client;
use Horde_Imap_Client_Data_Acl;
use Horde_Imap_Client_Data_Capability;
use Horde_Imap_Client_Mailbox;
use Horde_Imap_Client_Socket;
use OCA\Mail\Account;
@ -125,7 +127,7 @@ class FolderMapperTest extends TestCase {
$this->assertEquals($expected, $created);
}
public function testGetFoldersStatus() {
public function testGetFoldersStatus(): void {
$folders = [
$this->createMock(Folder::class),
];
@ -144,13 +146,22 @@ class FolderMapperTest extends TestCase {
'total' => 123
],
]);
$capability = $this->createMock(Horde_Imap_Client_Data_Capability::class);
$client
->method('__get')
->with('capability')
->willReturn($capability);
$capability
->method('query')
->with('ACL')
->willReturn(false);
$folders[0]->expects($this->once())
->method('setStatus');
$this->mapper->getFoldersStatus($folders, $client);
}
public function testGetFoldersStatusNoStatusReported() {
public function testGetFoldersStatusNoStatusReported(): void {
$folders = [
$this->createMock(Folder::class),
];
@ -161,6 +172,15 @@ class FolderMapperTest extends TestCase {
$folders[0]->expects($this->once())
->method('getAttributes')
->willReturn([]);
$capability = $this->createMock(Horde_Imap_Client_Data_Capability::class);
$client
->method('__get')
->with('capability')
->willReturn($capability);
$capability
->method('query')
->with('ACL')
->willReturn(false);
$client->expects($this->once())
->method('status')
->with($this->equalTo(['folder1']))
@ -173,7 +193,7 @@ class FolderMapperTest extends TestCase {
$this->mapper->getFoldersStatus($folders, $client);
}
public function testGetFoldersStatusNotSearchable() {
public function testGetFoldersStatusNotSearchable(): void {
$folders = [
$this->createMock(Folder::class),
];
@ -188,13 +208,26 @@ class FolderMapperTest extends TestCase {
->method('status')
->with($this->equalTo([]))
->willReturn([]);
$capability = $this->createMock(Horde_Imap_Client_Data_Capability::class);
$capability
->method('query')
->with('ACL')
->willReturn(false);
$client
->method('__get')
->with('capability')
->willReturn($capability);
$capability
->method('query')
->with('ACL')
->willReturn(false);
$folders[0]->expects($this->never())
->method('setStatus');
$this->mapper->getFoldersStatus($folders, $client);
}
public function testGetFoldersStatusAsObject() {
public function testGetFoldersStatusAsObject(): void {
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$client->expects($this->once())
->method('status')
@ -203,10 +236,51 @@ class FolderMapperTest extends TestCase {
'messages' => 123,
'unseen' => 2,
]);
$capability = $this->createMock(Horde_Imap_Client_Data_Capability::class);
$client
->method('__get')
->with('capability')
->willReturn($capability);
$capability
->method('query')
->with('ACL')
->willReturn(false);
$stats = $this->mapper->getFoldersStatusAsObject($client, 'INBOX');
$expected = new MailboxStats(123, 2);
$expected = new MailboxStats(123, 2, null);
$this->assertEquals($expected, $stats);
}
public function testGetFoldersStatusAndMyAcls(): void {
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$client->expects($this->once())
->method('status')
->with('INBOX')
->willReturn([
'messages' => 123,
'unseen' => 2,
]);
$capability = $this->createMock(Horde_Imap_Client_Data_Capability::class);
$client
->method('__get')
->with('capability')
->willReturn($capability);
$capability
->method('query')
->with('ACL')
->willReturn(true);
$acl = $this->createMock(Horde_Imap_Client_Data_Acl::class);
$client->expects(self::once())
->method('getMyACLRights')
->willReturn($acl);
$acl
->method('__toString')
->willReturn('kthxbye');
$stats = $this->mapper->getFoldersStatusAsObject($client, 'INBOX');
$expected = new MailboxStats(123, 2, 'kthxbye');
$this->assertEquals($expected, $stats);
}

Просмотреть файл

@ -139,14 +139,14 @@ class MailboxSyncTest extends TestCase {
$this->sync->sync($account, new NullLogger());
}
public function testSyncStats() {
public function testSyncStats(): void {
$account = $this->createMock(Account::class);
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$this->imapClientFactory->expects($this->once())
->method('getClient')
->with($account)
->willReturn($client);
$stats = new MailboxStats(42, 10);
$stats = new MailboxStats(42, 10, null);
$mailbox = new Mailbox();
$mailbox->setName('mailbox');
$this->folderMapper->expects($this->once())

Просмотреть файл

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Richard Steinmetz <richard@steinmetz.cloud>
*
@ -112,7 +114,7 @@ class SyncServiceTest extends TestCase {
[],
[],
[],
new MailboxStats(42, 10)
new MailboxStats(42, 10, null)
);
$this->messageMapper