Skip to content

Commit

Permalink
fixup! feat(ocs): notify of new messages and provide API endpoint to …
Browse files Browse the repository at this point in the history
…retrieve its contents
  • Loading branch information
miaulalala committed Jul 1, 2024
1 parent fd8dd0c commit 40ff823
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 9 deletions.
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,5 +499,10 @@
'url' => '/message/{id}',
'verb' => 'GET',
],
[
'name' => 'messageApi#raw',
'url' => '/message/{id}/raw',
'verb' => 'GET',
],
],
];
141 changes: 132 additions & 9 deletions lib/Controller/MessageApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,38 @@
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
* 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\Http\AttachmentDownloadResponse;
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\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IMimeTypeDetector;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;

class MessageApiController extends OCSController {
Expand All @@ -35,9 +43,8 @@ class MessageApiController extends OCSController {

public function __construct(
string $appName,
$UserId,
?string $userId,
IRequest $request,
private IUserManager $userManager,
private AccountService $accountService,
private AliasesService $aliasesService,
private AttachmentService $attachmentService,
Expand All @@ -47,9 +54,14 @@ public function __construct(
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;
$this->userId = $userId;
}

/**
Expand All @@ -66,10 +78,67 @@ public function get(int $id): DataResponse {
$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('Forbidden', Http::STATUS_FORBIDDEN);
return new DataResponse($e, Http::STATUS_FORBIDDEN);
}

$client = $this->clientFactory->getClient($account);
try {
$imapMessage = $this->mailManager->getImapMessage(
$client,
$account,
$mailbox,
$message->getUid(), true
);
$json = $imapMessage->getFullMessage($id);
} catch (ServiceException $e) {
$this->logger->error('Message could not be loaded', ['exception' => $e->getMessage()]);
return new DataResponse($e, Http::STATUS_NOT_FOUND);
} finally {
$client->logout();
}

$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;
}

return new DataResponse($json, Http::STATUS_OK);
}

#[BruteForceProtection('mailGetRawMessage')]
#[NoAdminRequired]
#[NoCSRFRequired]
public function raw(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);
}

$message = $this->mailSearch->findMessage($account, $mailbox, $message);
$client = $this->clientFactory->getClient($account);
try {
$source = $this->mailManager->getSource(
Expand All @@ -80,11 +149,65 @@ public function get(int $id): DataResponse {
);
} catch (ServiceException $e) {
$this->logger->error('Message not found on IMAP or mail server went away', ['exception' => $e->getMessage()]);
return new DataResponse('Not found', Http::STATUS_NOT_FOUND);
return new DataResponse($e, Http::STATUS_NOT_FOUND);
} finally {
$client->logout();
}

return new DataResponse(['message' => $message, 'source' => $source], Http::STATUS_OK);
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->linkToRoute('mail.messageApi.downloadAttachment',
[
'id' => $id,
'attachmentId' => $attachment['id'],
]);
$downloadUrl = $this->urlGenerator->getAbsoluteURL($downloadUrl);
$attachment['downloadUrl'] = $downloadUrl;
return $attachment;
}

#[NoCSRFRequired]
#[NoAdminRequired]
#[TrapError]
public function downloadAttachment(int $id,
string $attachmentId): Response {
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 $e) {
return new JSONResponse($e, Http::STATUS_FORBIDDEN);
}

$attachment = $this->mailManager->getMailAttachment(
$account,
$mailbox,
$message,
$attachmentId,
);

// Body party and embedded messages do not have a name
if ($attachment->getName() === null) {
return new AttachmentDownloadResponse(
$attachment->getContent(),
$this->l10n->t('Embedded message %s', [

Check failure on line 201 in lib/Controller/MessageApiController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

UndefinedThisPropertyFetch

lib/Controller/MessageApiController.php:201:5: UndefinedThisPropertyFetch: Instance property OCA\Mail\Controller\MessageApiController::$l10n is not defined (see https://psalm.dev/041)

Check failure on line 201 in lib/Controller/MessageApiController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable29

UndefinedThisPropertyFetch

lib/Controller/MessageApiController.php:201:5: UndefinedThisPropertyFetch: Instance property OCA\Mail\Controller\MessageApiController::$l10n is not defined (see https://psalm.dev/041)

Check failure on line 201 in lib/Controller/MessageApiController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable28

UndefinedThisPropertyFetch

lib/Controller/MessageApiController.php:201:5: UndefinedThisPropertyFetch: Instance property OCA\Mail\Controller\MessageApiController::$l10n is not defined (see https://psalm.dev/041)

Check failure on line 201 in lib/Controller/MessageApiController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable27

UndefinedThisPropertyFetch

lib/Controller/MessageApiController.php:201:5: UndefinedThisPropertyFetch: Instance property OCA\Mail\Controller\MessageApiController::$l10n is not defined (see https://psalm.dev/041)
$attachmentId,
]) . '.eml',
$attachment->getType()
);
}
return new AttachmentDownloadResponse(
$attachment->getContent(),
$attachment->getName(),
$attachment->getType()
);
}
}
18 changes: 18 additions & 0 deletions lib/Service/TrustedSenderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
namespace OCA\Mail\Service;

use OCA\Mail\Contracts\ITrustedSenderService;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\TrustedSenderMapper;

class TrustedSenderService implements ITrustedSenderService {
Expand All @@ -43,6 +44,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
Expand Down

0 comments on commit 40ff823

Please sign in to comment.