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:
Родитель
46aa30c237
Коммит
71dbc51dca
|
@ -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);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче