From 71dbc51dca58d51f419e09cc4bff7bed49adbbd4 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Mon, 17 Jun 2024 18:47:14 +0200 Subject: [PATCH] feat(ocs): notify of new messages and provide API endpoint to retrieve its contents Signed-off-by: Anna Larch --- appinfo/routes.php | 19 +- lib/AppInfo/Application.php | 2 + lib/Controller/MessageApiController.php | 240 ++++++++++ lib/Db/MessageMapper.php | 4 +- lib/Events/NewMessageReceivedEvent.php | 21 + lib/IMAP/ImapMessageFetcher.php | 2 + lib/Listener/NewMessagesNotifier.php | 42 ++ lib/Model/IMAPMessage.php | 22 +- lib/Service/MailManager.php | 2 + lib/Service/SmimeService.php | 2 +- lib/Service/TrustedSenderService.php | 18 + .../Controller/MessageApiControllerTest.php | 410 ++++++++++++++++++ 12 files changed, 772 insertions(+), 12 deletions(-) create mode 100644 lib/Controller/MessageApiController.php create mode 100644 lib/Events/NewMessageReceivedEvent.php create mode 100644 lib/Listener/NewMessagesNotifier.php create mode 100644 tests/Unit/Controller/MessageApiControllerTest.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 99250b1983..558f45849d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -482,5 +482,22 @@ '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', + ], + ], ]; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index dfb781d6c9..6e4ae2dfde 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -48,6 +48,7 @@ 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 @@ public function register(IRegistrationContext $context): void { $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); diff --git a/lib/Controller/MessageApiController.php b/lib/Controller/MessageApiController.php new file mode 100644 index 0000000000..f8019cad96 --- /dev/null +++ b/lib/Controller/MessageApiController.php @@ -0,0 +1,240 @@ +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() + ]); + } +} diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index c9da207d16..8ea5555f2c 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -311,7 +311,7 @@ public function insertBulk(Account $account, Message ...$messages): void { $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 @@ public function insertBulk(Account $account, Message ...$messages): void { 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); diff --git a/lib/Events/NewMessageReceivedEvent.php b/lib/Events/NewMessageReceivedEvent.php new file mode 100644 index 0000000000..7b27b8fee7 --- /dev/null +++ b/lib/Events/NewMessageReceivedEvent.php @@ -0,0 +1,21 @@ +uri; + } +} diff --git a/lib/IMAP/ImapMessageFetcher.php b/lib/IMAP/ImapMessageFetcher.php index a1c99ac57c..afebc421fa 100644 --- a/lib/IMAP/ImapMessageFetcher.php +++ b/lib/IMAP/ImapMessageFetcher.php @@ -22,6 +22,7 @@ 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 @@ public function withPhishingCheck(bool $value): 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); diff --git a/lib/Listener/NewMessagesNotifier.php b/lib/Listener/NewMessagesNotifier.php new file mode 100644 index 0000000000..def7380f2f --- /dev/null +++ b/lib/Listener/NewMessagesNotifier.php @@ -0,0 +1,42 @@ + + */ +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)); + } + } +} diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php index d1f67eec53..3cfe87ba8c 100644 --- a/lib/Model/IMAPMessage.php +++ b/lib/Model/IMAPMessage.php @@ -268,21 +268,27 @@ public function getSentDate(): Horde_Imap_Client_DateTime { * * @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 && $loadBody) { + $data['body'] = $this->getHtmlBody($id); + } + if ($this->hasHtmlMessage) { $data['hasHtmlBody'] = true; - $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); + 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; } diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index 37fbbf19ca..73841d2c5f 100644 --- a/lib/Service/MailManager.php +++ b/lib/Service/MailManager.php @@ -30,6 +30,7 @@ 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 @@ public function createMailbox(Account $account, string $name): Mailbox { * @return IMAPMessage * * @throws ServiceException + * @throws SmimeDecryptException */ public function getImapMessage(Horde_Imap_Client_Socket $client, Account $account, diff --git a/lib/Service/SmimeService.php b/lib/Service/SmimeService.php index 6cd7d1ff9d..3b3c7f1520 100644 --- a/lib/Service/SmimeService.php +++ b/lib/Service/SmimeService.php @@ -522,7 +522,7 @@ public function decryptDataFetch(Horde_Imap_Client_Data_Fetch $message, } 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; diff --git a/lib/Service/TrustedSenderService.php b/lib/Service/TrustedSenderService.php index d7e35f06d9..76d9afeef9 100644 --- a/lib/Service/TrustedSenderService.php +++ b/lib/Service/TrustedSenderService.php @@ -10,6 +10,7 @@ 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 @@ public function isTrusted(string $uid, string $email): bool { ); } + 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 diff --git a/tests/Unit/Controller/MessageApiControllerTest.php b/tests/Unit/Controller/MessageApiControllerTest.php new file mode 100644 index 0000000000..c28ea52cfa --- /dev/null +++ b/tests/Unit/Controller/MessageApiControllerTest.php @@ -0,0 +1,410 @@ +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); + } +}