feat(ocs): notify of new messages and provide API endpoint to retrieve its contents

Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
Anna Larch 2024-06-17 18:47:14 +02:00
Родитель 46aa30c237
Коммит 71dbc51dca
12 изменённых файлов: 774 добавлений и 14 удалений

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

@ -482,5 +482,22 @@ return [
'outbox' => ['url' => '/api/outbox'],
'preferences' => ['url' => '/api/preferences'],
'smimeCertificates' => ['url' => '/api/smime/certificates'],
]
],
'ocs' => [
[
'name' => 'messageApi#get',
'url' => '/message/{id}',
'verb' => 'GET',
],
[
'name' => 'messageApi#getRaw',
'url' => '/message/{id}/raw',
'verb' => 'GET',
],
[
'name' => 'messageApi#getAttachment',
'url' => '/message/{id}/attachment/{attachmentId}',
'verb' => 'GET',
],
],
];

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

@ -48,6 +48,7 @@ use OCA\Mail\Listener\MessageCacheUpdaterListener;
use OCA\Mail\Listener\MessageKnownSinceListener;
use OCA\Mail\Listener\MoveJunkListener;
use OCA\Mail\Listener\NewMessageClassificationListener;
use OCA\Mail\Listener\NewMessagesNotifier;
use OCA\Mail\Listener\OauthTokenRefreshListener;
use OCA\Mail\Listener\OptionalIndicesListener;
use OCA\Mail\Listener\OutOfOfficeListener;
@ -127,6 +128,7 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(MessageSentEvent::class, InteractionListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, NewMessageClassificationListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, MessageKnownSinceListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, NewMessagesNotifier::class);
$context->registerEventListener(SynchronizationEvent::class, AccountSynchronizedThreadUpdaterListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, FollowUpClassifierListener::class);

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

@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Mail\Controller;
use OCA\Mail\Contracts\IDkimService;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\Http\TrapError;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Model\SmimeData;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\Search\MailSearch;
use OCA\Mail\Service\TrustedSenderService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IMimeTypeDetector;
use OCP\IRequest;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;
class MessageApiController extends OCSController {
private ?string $userId;
public function __construct(
string $appName,
?string $userId,
IRequest $request,
private AccountService $accountService,
private AliasesService $aliasesService,
private AttachmentService $attachmentService,
private OutboxService $outboxService,
private MailSearch $mailSearch,
private MailManager $mailManager,
private IMAPClientFactory $clientFactory,
private LoggerInterface $logger,
private ITimeFactory $time,
private IURLGenerator $urlGenerator,
private IMimeTypeDetector $mimeTypeDetector,
private IDkimService $dkimService,
private ItineraryService $itineraryService,
private TrustedSenderService $trustedSenderService,
) {
parent::__construct($appName, $request);
$this->userId = $userId;
}
/**
* @param int $id
* @return DataResponse
*/
#[BruteForceProtection('mailGetMessage')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function get(int $id): DataResponse {
if ($this->userId === null) {
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
}
try {
$message = $this->mailManager->getMessage($this->userId, $id);
$mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
$account = $this->accountService->find($this->userId, $mailbox->getAccountId());
} catch (ClientException | DoesNotExistException $e) {
$this->logger->error('Message, Account or Mailbox not found', ['exception' => $e->getMessage()]);
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
}
$loadBody = true;
$client = $this->clientFactory->getClient($account);
try {
$imapMessage = $this->mailManager->getImapMessage(
$client,
$account,
$mailbox,
$message->getUid(),
true
);
} catch (ServiceException $e) {
$this->logger->error('Could not connect to IMAP server', ['exception' => $e->getMessage()]);
return new DataResponse('Could not connect to IMAP server. Please check your logs.', Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (SmimeDecryptException $e) {
$this->logger->warning('Message could not be decrypted', ['exception' => $e->getMessage()]);
$loadBody = false;
$imapMessage = $this->mailManager->getImapMessage(
$client,
$account,
$mailbox,
$message->getUid()
);
} finally {
$client->logout();
}
$json = $imapMessage->getFullMessage($id, $loadBody);
$itineraries = $this->itineraryService->getCached($account, $mailbox, $message->getUid());
if ($itineraries) {
$json['itineraries'] = $itineraries;
}
$json['attachments'] = array_map(function ($a) use ($id) {
return $this->enrichDownloadUrl(
$id,
$a
);
}, $json['attachments']);
$json['id'] = $message->getId();
$json['isSenderTrusted'] = $this->trustedSenderService->isSenderTrusted($this->userId, $message);
$smimeData = new SmimeData();
$smimeData->setIsEncrypted($message->isEncrypted() || $imapMessage->isEncrypted());
if ($imapMessage->isSigned()) {
$smimeData->setIsSigned(true);
$smimeData->setSignatureIsValid($imapMessage->isSignatureValid());
}
$json['smime'] = $smimeData;
$dkimResult = $this->dkimService->getCached($account, $mailbox, $message->getUid());
if (is_bool($dkimResult)) {
$json['dkimValid'] = $dkimResult;
}
$json['rawUrl'] = $this->urlGenerator->linkToOCSRouteAbsolute('mail.messageApi.getRaw', ['id' => $id]);
if(!$loadBody) {
return new DataResponse($json, Http::STATUS_PARTIAL_CONTENT);
}
return new DataResponse($json, Http::STATUS_OK);
}
#[BruteForceProtection('mailGetRawMessage')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function getRaw(int $id): DataResponse {
try {
$message = $this->mailManager->getMessage($this->userId, $id);
$mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
$account = $this->accountService->find($this->userId, $mailbox->getAccountId());
} catch (ClientException | DoesNotExistException $e) {
$this->logger->error('Message, Account or Mailbox not found', ['exception' => $e->getMessage()]);
return new DataResponse($e, Http::STATUS_FORBIDDEN);
}
$client = $this->clientFactory->getClient($account);
try {
$source = $this->mailManager->getSource(
$client,
$account,
$mailbox->getName(),
$message->getUid()
);
} catch (ServiceException $e) {
$this->logger->error('Message not found on IMAP or mail server went away', ['exception' => $e->getMessage()]);
return new DataResponse($e, Http::STATUS_NOT_FOUND);
} finally {
$client->logout();
}
return new DataResponse($source, Http::STATUS_OK);
}
/**
* @param int $id
* @param array $attachment
*
* @return array
*/
private function enrichDownloadUrl(int $id, array $attachment) {
$downloadUrl = $this->urlGenerator->linkToOCSRouteAbsolute('mail.messageApi.downloadAttachment',
[
'id' => $id,
'attachmentId' => $attachment['id'],
]);
$attachment['downloadUrl'] = $downloadUrl;
return $attachment;
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[TrapError]
public function getAttachment(int $id,
string $attachmentId): DataResponse {
try {
$message = $this->mailManager->getMessage($this->userId, $id);
$mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
$account = $this->accountService->find($this->userId, $mailbox->getAccountId());
} catch (DoesNotExistException | ClientException $e) {
return new DataResponse($e, Http::STATUS_FORBIDDEN);
}
try {
$attachment = $this->mailManager->getMailAttachment(
$account,
$mailbox,
$message,
$attachmentId,
);
} catch (\Horde_Imap_Client_Exception_NoSupportExtension | \Horde_Imap_Client_Exception | \Horde_Mime_Exception $e) {
$this->logger->error('Error when trying to process the attachment', ['exception' => $e]);
return new DataResponse($e, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (ServiceException | DoesNotExistException $e) {
$this->logger->error('Could not find attachment', ['exception' => $e]);
return new DataResponse($e, Http::STATUS_NOT_FOUND);
}
// Body party and embedded messages do not have a name
if ($attachment->getName() === null) {
return new DataResponse([
'name' => $attachmentId . '.eml',
'mime' => $attachment->getType(),
'size' => $attachment->getSize(),
'content' => $attachment->getContent()
]);
}
return new DataResponse([
'name' => $attachment->getName(),
'mime' => $attachment->getType(),
'size' => $attachment->getSize(),
'content' => $attachment->getContent()
]);
}
}

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

@ -311,7 +311,7 @@ class MessageMapper extends QBMapper {
$qb1->setParameter('flag_mdnsent', $message->getFlagMdnsent(), IQueryBuilder::PARAM_BOOL);
$qb1->executeStatement();
$messageId = $qb1->getLastInsertId();
$message->setId($qb1->getLastInsertId());
$recipientTypes = [
Address::TYPE_FROM => $message->getFrom(),
Address::TYPE_TO => $message->getTo(),
@ -325,7 +325,7 @@ class MessageMapper extends QBMapper {
continue;
}
$qb2->setParameter('message_id', $messageId, IQueryBuilder::PARAM_INT);
$qb2->setParameter('message_id', $message->getId(), IQueryBuilder::PARAM_INT);
$qb2->setParameter('type', $type, IQueryBuilder::PARAM_INT);
$qb2->setParameter('label', mb_strcut($recipient->getLabel(), 0, 255), IQueryBuilder::PARAM_STR);
$qb2->setParameter('email', mb_strcut($recipient->getEmail(), 0, 255), IQueryBuilder::PARAM_STR);

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

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Mail\Events;
use OCP\EventDispatcher\Event;
class NewMessageReceivedEvent extends Event {
public function __construct(private string $uri) {
parent::__construct();
}
public function getUri(): string {
return $this->uri;
}
}

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

@ -22,6 +22,7 @@ use Horde_Mime_Headers;
use Horde_Mime_Part;
use OCA\Mail\AddressList;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\IMAP\Charset\Converter;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\Html;
@ -115,6 +116,7 @@ class ImapMessageFetcher {
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws Horde_Mime_Exception
* @throws ServiceException
* @throws SmimeDecryptException
*/
public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null): IMAPMessage {
$ids = new Horde_Imap_Client_Ids($this->uid);

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

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Mail\Listener;
use OCA\Mail\Db\Message;
use OCA\Mail\Events\NewMessageReceivedEvent;
use OCA\Mail\Events\NewMessagesSynchronized;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
use OCP\IURLGenerator;
/**
* @template-implements IEventListener<Event|NewMessagesSynchronized>
*/
class NewMessagesNotifier implements IEventListener {
public function __construct(private IEventDispatcher $eventDispatcher,
private IURLGenerator $urlGenerator,
) {
}
/**
* @inheritDoc
*/
public function handle(Event $event): void {
if(!$event instanceof NewMessagesSynchronized) {
return;
}
/** @var Message $message */
foreach($event->getMessages() as $message) {
$uri = $this->urlGenerator->linkToOCSRouteAbsolute('mail.messageApi.get', ['id' => $message->getId()]);
$this->eventDispatcher->dispatchTyped(new NewMessageReceivedEvent($uri));
}
}
}

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

@ -268,21 +268,27 @@ class IMAPMessage implements IMessage, JsonSerializable {
*
* @return array
*/
public function getFullMessage(int $id): array {
public function getFullMessage(int $id, bool $loadBody = true): array {
$mailBody = $this->plainMessage;
$data = $this->jsonSerialize();
if ($this->hasHtmlMessage) {
$data['hasHtmlBody'] = true;
if($this->hasHtmlMessage && $loadBody) {
$data['body'] = $this->getHtmlBody($id);
$data['attachments'] = $this->attachments;
} else {
$mailBody = $this->htmlService->convertLinks($mailBody);
[$mailBody, $signature] = $this->htmlService->parseMailBody($mailBody);
$data['body'] = $mailBody;
$data['signature'] = $signature;
$data['attachments'] = array_merge($this->attachments, $this->inlineAttachments);
}
if ($this->hasHtmlMessage) {
$data['hasHtmlBody'] = true;
$data['attachments'] = $this->attachments;
return $data;
}
$mailBody = $this->htmlService->convertLinks($mailBody);
[$mailBody, $signature] = $this->htmlService->parseMailBody($mailBody);
$data['signature'] = $signature;
$data['attachments'] = array_merge($this->attachments, $this->inlineAttachments);
if($loadBody) {
$data['body'] = $mailBody;
}
return $data;
}

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

@ -30,6 +30,7 @@ use OCA\Mail\Events\MessageDeletedEvent;
use OCA\Mail\Events\MessageFlaggedEvent;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\Exception\TrashMailboxNotSetException;
use OCA\Mail\Folder;
use OCA\Mail\IMAP\FolderMapper;
@ -173,6 +174,7 @@ class MailManager implements IMailManager {
* @return IMAPMessage
*
* @throws ServiceException
* @throws SmimeDecryptException
*/
public function getImapMessage(Horde_Imap_Client_Socket $client,
Account $account,

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

@ -522,7 +522,7 @@ class SmimeService {
}
if ($decryptionResult === null) {
throw new ServiceException('Failed to find a suitable S/MIME certificate for decryption');
throw new SmimeDecryptException('Failed to find a suitable S/MIME certificate for decryption');
}
return $decryptionResult;

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

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Mail\Service;
use OCA\Mail\Contracts\ITrustedSenderService;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\TrustedSenderMapper;
class TrustedSenderService implements ITrustedSenderService {
@ -27,6 +28,23 @@ class TrustedSenderService implements ITrustedSenderService {
);
}
public function isSenderTrusted(string $uid, Message $message): bool {
$from = $message->getFrom();
$first = $from->first();
if ($first === null) {
return false;
}
$email = $first->getEmail();
if ($email === null) {
return false;
}
return $this->mapper->exists(
$uid,
$email
);
}
public function trust(string $uid, string $email, string $type, ?bool $trust = true): void {
if ($trust && $this->isTrusted($uid, $email)) {
// Nothing to do

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

@ -0,0 +1,410 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Unit\Controller;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Account;
use OCA\Mail\Controller\MessageApiController;
use OCA\Mail\Db\LocalMessage;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Model\SmimeData;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\DkimService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\Search\MailSearch;
use OCA\Mail\Service\TrustedSenderService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IMimeTypeDetector;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
class MessageApiControllerTest extends TestCase {
private string $appName;
private string $userId;
private AliasesService|MockObject $aliasesService;
private AttachmentService|MockObject $attachmentService;
private OutboxService|MockObject $outboxService;
private LoggerInterface|MockObject $logger;
private MockObject|ITimeFactory $time;
private MessageApiController $controller;
private MockObject|AccountService $accountService;
private OutboxService|MockObject $service;
private MockObject|IRequest $request;
private string $fromEmail = 'john@test.com';
private int $accountId = 1;
private Account $account;
private LocalMessage $message;
private IUserManager|MockObject $userManager;
private TrustedSenderService|MockObject $trustedSenderService;
private MailManager|MockObject $mailManager;
private MailSearch|MockObject $mailSearch;
private MockObject|IURLGenerator $urlGenerator;
private MockObject|IMimeTypeDetector $mimeTypeDetector;
private IMAPClientFactory|MockObject $imapClientFactory;
private DkimService|MockObject $dkimService;
private MockObject|ItineraryService $itineraryService;
private int $messageId = 100;
private int $mailboxId = 42;
protected function setUp(): void {
parent::setUp();
$this->appName = 'mail';
$this->service = $this->createMock(OutboxService::class);
$this->userId = 'john';
$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->aliasesService = $this->createMock(AliasesService::class);
$this->attachmentService = $this->createMock(AttachmentService::class);
$this->outboxService = $this->createMock(OutboxService::class);
$this->mailManager = $this->createMock(MailManager::class);
$this->mailSearch = $this->createMock(MailSearch::class);
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->attachmentService = $this->createMock(AttachmentService::class);
$this->outboxService = $this->createMock(OutboxService::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->dkimService = $this->createMock(DkimService::class);
$this->itineraryService = $this->createMock(ItineraryService::class);
$this->trustedSenderService = $this->createMock(TrustedSenderService::class);
$this->controller = new MessageApiController($this->appName,
$this->userId,
$this->request,
$this->accountService,
$this->aliasesService,
$this->attachmentService,
$this->outboxService,
$this->mailSearch,
$this->mailManager,
$this->imapClientFactory,
$this->logger,
$this->time,
$this->urlGenerator,
$this->mimeTypeDetector,
$this->dkimService,
$this->itineraryService,
$this->trustedSenderService,
);
$mailAccount = new MailAccount();
$mailAccount->setId($this->accountId);
$mailAccount->setEmail($this->fromEmail);
$this->account = new Account($mailAccount);
$this->message = new LocalMessage();
$this->message->setAccountId($this->accountId);
$this->message->setSubject('');
$this->message->setBody('');
$this->message->setHtml(true);
$this->message->setType(LocalMessage::TYPE_OUTGOING);
}
/**
* @dataProvider getDataProvider
*/
public function testGet(bool $encrypted, bool $signed, array $json): void {
$message = new Message();
$message->setId($this->messageId);
$message->setMailboxId($this->mailboxId);
$message->setUid(1);
$mailbox = new Mailbox();
$mailbox->setAccountId($this->accountId);
$client = $this->createMock(\Horde_Imap_Client_Socket::class);
$imapMessage = $this->createMock(IMAPMessage::class);
$this->logger->expects(self::never())
->method('warning');
$this->logger->expects(self::never())
->method('error');
$this->mailManager->expects(self::once())
->method('getMessage')
->with($this->userId, $this->messageId)
->willReturn($message);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with($this->userId, $this->mailboxId)
->willReturn($mailbox);
$this->accountService->expects(self::once())
->method('find')
->with($this->userId, $this->accountId)
->willReturn($this->account);
$this->imapClientFactory->expects(self::once())
->method('getClient')
->willReturn($client);
$this->mailManager->expects(self::once())
->method('getImapMessage')
->willReturn($imapMessage);
$client->expects(self::once())
->method('logout');
$imapMessage->expects(self::once())
->method('getFullMessage')
->with($this->messageId, true)
->willReturn(['id' => $this->messageId, 'attachments' => []]);
$this->itineraryService->expects(self::once())
->method('getCached')
->willReturn(null);
$this->trustedSenderService->expects(self::once())
->method('isSenderTrusted')
->willReturn(false);
$imapMessage->expects(self::once())
->method('isEncrypted')
->willReturn($encrypted);
$imapMessage->expects(self::once())
->method('isSigned')
->willReturn($signed);
if ($signed) {
$imapMessage->expects(self::once())
->method('isSignatureValid')
->willReturn(true);
}
$this->dkimService->expects(self::once())
->method('getCached')
->willReturn(null);
$this->urlGenerator->expects(self::once())
->method('linkToOCSRouteAbsolute')
->willReturn('http://rawUrl');
$expected = new DataResponse($json, Http::STATUS_OK);
$actual = $this->controller->get($this->messageId);
$this->assertEquals($expected, $actual);
}
public function getDataProvider(): array {
$smime1 = new SmimeData();
$smime1->setIsEncrypted(true);
$smime1->setIsSigned(false);
$smime2 = new SmimeData();
$smime2->setIsEncrypted(false);
$smime2->setIsSigned(true);
$smime2->setSignatureIsValid(true);
return [
[
'encrypted' => false,
'signed' => false,
'json' => [
'attachments' => [],
'id' => $this->messageId,
'isSenderTrusted' => false,
'smime' => new SmimeData(),
'rawUrl' => 'http://rawUrl',
]
],
[
'encrypted' => true,
'signed' => false,
'json' => [
'attachments' => [],
'id' => $this->messageId,
'isSenderTrusted' => false,
'smime' => $smime1,
'rawUrl' => 'http://rawUrl',
]
],
[
'encrypted' => false,
'signed' => true,
'json' => [
'attachments' => [],
'id' => $this->messageId,
'isSenderTrusted' => false,
'smime' => $smime2,
'rawUrl' => 'http://rawUrl',
]
]
];
}
public function testGetWithSmimeEncryptionFailed(): void {
$message = new Message();
$message->setId($this->messageId);
$message->setMailboxId($this->mailboxId);
$message->setEncrypted(true);
$message->setUid(1);
$mailbox = new Mailbox();
$mailbox->setAccountId($this->accountId);
$client = $this->createMock(\Horde_Imap_Client_Socket::class);
$imapMessage = $this->createMock(IMAPMessage::class);
$smime = new SmimeData();
$smime->setIsEncrypted(true);
$json = [
'attachments' => [],
'id' => $this->messageId,
'isSenderTrusted' => false,
'smime' => $smime,
'rawUrl' => 'http://rawUrl',
];
$this->logger->expects(self::once())
->method('warning');
$this->logger->expects(self::never())
->method('error');
$this->mailManager->expects(self::once())
->method('getMessage')
->with($this->userId, $this->messageId)
->willReturn($message);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with($this->userId, $this->mailboxId)
->willReturn($mailbox);
$this->accountService->expects(self::once())
->method('find')
->with($this->userId, $this->accountId)
->willReturn($this->account);
$this->imapClientFactory->expects(self::once())
->method('getClient')
->willReturn($client);
$this->mailManager->expects(self::exactly(2))
->method('getImapMessage')
->willReturnCallback(function ($client, $account, $mailbox, $uid, $loadBody) use ($imapMessage) {
if ($loadBody) {
throw new SmimeDecryptException();
}
return $imapMessage;
});
$client->expects(self::once())
->method('logout');
$imapMessage->expects(self::once())
->method('getFullMessage')
->with($this->messageId, false)
->willReturn(['id' => $this->messageId, 'attachments' => []]);
$this->itineraryService->expects(self::once())
->method('getCached')
->willReturn(null);
$this->trustedSenderService->expects(self::once())
->method('isSenderTrusted')
->willReturn(false);
$imapMessage->expects(self::once())
->method('isSigned')
->willReturn(false);
$imapMessage->expects(self::never())
->method('isSignatureValid');
$this->dkimService->expects(self::once())
->method('getCached')
->willReturn(null);
$this->urlGenerator->expects(self::once())
->method('linkToOCSRouteAbsolute')
->willReturn('http://rawUrl');
$expected = new DataResponse($json, Http::STATUS_PARTIAL_CONTENT);
$actual = $this->controller->get($this->messageId);
$this->assertEquals($expected, $actual);
}
public function testGetWithSmimeException(): void {
$message = new Message();
$message->setId($this->messageId);
$message->setMailboxId($this->mailboxId);
$message->setEncrypted(true);
$message->setUid(1);
$mailbox = new Mailbox();
$mailbox->setAccountId($this->accountId);
$client = $this->createMock(\Horde_Imap_Client_Socket::class);
$this->logger->expects(self::never())
->method('warning');
$this->logger->expects(self::once())
->method('error');
$this->mailManager->expects(self::once())
->method('getMessage')
->with($this->userId, $this->messageId)
->willReturn($message);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with($this->userId, $this->mailboxId)
->willReturn($mailbox);
$this->accountService->expects(self::once())
->method('find')
->with($this->userId, $this->accountId)
->willReturn($this->account);
$this->imapClientFactory->expects(self::once())
->method('getClient')
->willReturn($client);
$this->mailManager->expects(self::once())
->method('getImapMessage')
->willThrowException(new ServiceException());
$client->expects(self::once())
->method('logout');
$this->itineraryService->expects(self::never())
->method('getCached');
$this->trustedSenderService->expects(self::never())
->method('isSenderTrusted');
$this->dkimService->expects(self::never())
->method('getCached');
$this->urlGenerator->expects(self::never())
->method('linkToOCSRouteAbsolute');
$expected = new DataResponse('Could not connect to IMAP server. Please check your logs.', Http::STATUS_INTERNAL_SERVER_ERROR);
$actual = $this->controller->get($this->messageId);
$this->assertEquals($expected, $actual);
}
public function testMailboxNotFound(): void {
$message = new Message();
$message->setId($this->messageId);
$message->setMailboxId($this->mailboxId);
$message->setEncrypted(true);
$message->setUid(1);
$this->logger->expects(self::never())
->method('warning');
$this->logger->expects(self::once())
->method('error');
$this->mailManager->expects(self::once())
->method('getMessage')
->with($this->userId, $this->messageId)
->willReturn($message);
$this->mailManager->expects(self::once())
->method('getMailbox')
->with($this->userId, $this->mailboxId)
->willThrowException(new ClientException(''));
$this->accountService->expects(self::never())
->method('find');
$this->imapClientFactory->expects(self::never())
->method('getClient');
$this->mailManager->expects(self::never())
->method('getImapMessage');
$this->itineraryService->expects(self::never())
->method('getCached');
$this->trustedSenderService->expects(self::never())
->method('isSenderTrusted');
$this->dkimService->expects(self::never())
->method('getCached');
$this->urlGenerator->expects(self::never())
->method('linkToOCSRouteAbsolute');
$expected = new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
$actual = $this->controller->get($this->messageId);
$this->assertEquals($expected, $actual);
}
}