feat(mailbox sharing): Read, cache and expose my ACL rights
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Родитель
839f9b7571
Коммит
76d7d7c956
|
@ -12,7 +12,7 @@
|
|||
- **🙈 We’re 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
|
||||
|
|
Загрузка…
Ссылка в новой задаче