Skip to content

Commit

Permalink
feat(ocs): send a message via api
Browse files Browse the repository at this point in the history
Signed-off-by: Anna Larch <[email protected]>
  • Loading branch information
miaulalala committed Jul 10, 2024
1 parent 1ce7b4d commit 048e86d
Show file tree
Hide file tree
Showing 7 changed files with 517 additions and 6 deletions.
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,11 @@
'smimeCertificates' => ['url' => '/api/smime/certificates'],
],
'ocs' => [
[
'name' => 'messageApi#send',
'url' => '/message/send',
'verb' => 'POST',
],
[
'name' => 'messageApi#get',
'url' => '/message/{id}',
Expand Down
127 changes: 127 additions & 0 deletions lib/Controller/MessageApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
*/
namespace OCA\Mail\Controller;

use OCA\Mail\Db\LocalMessage;
use OCA\Mail\Contracts\IDkimService;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\UploadException;
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\Attachment\UploadedFile;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\OutboxService;
Expand All @@ -27,13 +30,16 @@
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
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;
use Throwable;
use function array_merge;

class MessageApiController extends OCSController {

Expand Down Expand Up @@ -62,6 +68,127 @@ public function __construct(
$this->userId = $userId;
}

#[UserRateLimit(limit: 5, period: 100)]
#[NoAdminRequired]
#[NoCSRFRequired]
public function send(
int $accountId,
string $fromEmail,
string $subject,
string $body,
bool $isHtml,
array $to,
array $cc = [],
array $bcc = [],
array $references = [],
): DataResponse {
if ($this->userId === null) {
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
}

try {
$mailAccount = $this->accountService->find($this->userId, $accountId);
} catch (ClientException $e) {
$this->logger->error("Mail account #$accountId not found", ['exception' => $e]);
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
}

if ($fromEmail !== $mailAccount->getEmail()) {
try {
$alias = $this->aliasesService->findByAliasAndUserId($fromEmail, $this->userId);
} catch (DoesNotExistException $e) {
$this->logger->error("Alias $fromEmail for mail account $accountId not found", ['exception' => $e]);
// Cannot send from this email as it is not configured as an alias
return new DataResponse("Could not find alias $fromEmail. Please check the logs.", Http::STATUS_NOT_FOUND);
}
}

if (empty($to)) {
return new DataResponse('Recipients cannot be empty.', Http::STATUS_BAD_REQUEST);
}

$attachments = $this->request->getUploadedFile('attachments');
$numberOfAttachments = count($attachments['name'] ?? []);
$localAttachments = [];
$messageAttachments = [];
$attachmentErrors = false;
while ($numberOfAttachments > 0) {
$numberOfAttachments--;
$filedata = [
'name' => $attachments['name'][$numberOfAttachments],
'type' => $attachments['type'][$numberOfAttachments],
'size' => $attachments['size'][$numberOfAttachments],
'tmp_name' => $attachments['tmp_name'][$numberOfAttachments],
];
$file = new UploadedFile($filedata);
try {
$localAttachment = $this->attachmentService->addFile($this->userId, $file);
$messageAttachments[] = $localAttachment;
$localAttachments[] = ['type' => 'local', 'id' => $localAttachment->getId()];
} catch (UploadException $e) {
$this->logger->error('Could not convert attachment to local attachment.', ['exception' => $e]);
$attachmentErrors = true;
}
}

if ($attachmentErrors) {
foreach ($localAttachments as $localAttachment) {
// Handle possible dangling local attachments
$this->attachmentService->deleteAttachment($this->userId, $localAttachment['id']);
}
return new DataResponse('Could not convert attachment(s) to local attachment(s). Please check the logs.', Http::STATUS_INTERNAL_SERVER_ERROR);
}

$message = new LocalMessage();
$message->setType(LocalMessage::TYPE_OUTGOING);
$message->setAccountId($accountId);
$message->setSubject($subject);
$message->setBody($body);
$message->setEditorBody($body);
$message->setHtml($isHtml);
$message->setSendAt($this->time->getTime());
$message->setType(LocalMessage::TYPE_OUTGOING);

if (isset($alias)) {
$message->setAliasId($alias->getId());
}
if (!empty($references)) {
$message->setInReplyToMessageId($references[0]);
}
if (!empty($attachments)) {
$message->setAttachments($messageAttachments);
}

$recipients = array_merge($to, $cc, $bcc);
foreach($recipients as $recipient) {
if (!is_array($recipient)) {
return new DataResponse('Recipient address must be an array.', Http::STATUS_BAD_REQUEST);
}

if (!isset($recipient['email'])) {
return new DataResponse('Recipient address must contain an email address.', Http::STATUS_BAD_REQUEST);
}
}
$localMessage = $this->outboxService->saveMessage($mailAccount, $message, $to, $cc, $bcc, $localAttachments);
try {
$localMessage = $this->outboxService->sendMessage($localMessage, $mailAccount);
} catch (ServiceException $e) {
$this->logger->error('Processing error: could not send message', ['exception' => $e]);
return new DataResponse('Processing error: could not send message. Please check the logs', Http::STATUS_BAD_REQUEST);
} catch (Throwable $e) {
$this->logger->error('SMTP error: could not send message', ['exception' => $e]);
return new DataResponse('Fatal SMTP error: could not send message, and no resending is possible. Please check the mail server logs.', Http::STATUS_INTERNAL_SERVER_ERROR);
}

return match ($localMessage->getStatus()) {
LocalMessage::STATUS_PROCESSED => new DataResponse('', Http::STATUS_OK),
LocalMessage::STATUS_NO_SENT_MAILBOX => new DataResponse('Configuration error: Cannot send message without sent mailbox.', Http::STATUS_FORBIDDEN),
LocalMessage::STATUS_SMPT_SEND_FAIL => new DataResponse('SMTP error: could not send message. Message sending will be retried. Please check the logs.', Http::STATUS_INTERNAL_SERVER_ERROR),
LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL => new DataResponse('Email was sent but could not be copied to sent mailbox. Copying will be retried. Please check the logs.', Http::STATUS_ACCEPTED),
default => new DataResponse('An error occured. Please check the logs.', Http::STATUS_INTERNAL_SERVER_ERROR),
};
}

/**
* @param int $id
* @return DataResponse
Expand Down
6 changes: 3 additions & 3 deletions lib/Send/Chain.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __construct(private SentMailboxHandler $sentMailboxHandler,
* @throws Exception
* @throws ServiceException
*/
public function process(Account $account, LocalMessage $localMessage): void {
public function process(Account $account, LocalMessage $localMessage): LocalMessage {
$handlers = $this->sentMailboxHandler;
$handlers->setNext($this->antiAbuseHandler)
->setNext($this->sendHandler)
Expand All @@ -50,8 +50,8 @@ public function process(Account $account, LocalMessage $localMessage): void {
if ($result->getStatus() === LocalMessage::STATUS_PROCESSED) {
$this->attachmentService->deleteLocalMessageAttachments($account->getUserId(), $result->getId());
$this->localMessageMapper->deleteWithRecipients($result);
return;
return $localMessage;
}
$this->localMessageMapper->update($result);
return $this->localMessageMapper->update($result);
}
}
10 changes: 10 additions & 0 deletions lib/Service/AliasesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public function find(int $aliasId, string $currentUserId): Alias {
return $this->aliasMapper->find($aliasId, $currentUserId);
}

/**
* @param string $aliasEmail
* @param string $userId
* @return Alias
* @throws DoesNotExistException
*/
public function findByAliasAndUserId(string $aliasEmail, string $userId): Alias {
return $this->aliasMapper->findByAlias($aliasEmail, $userId);
}

/**
* @param string $userId
* @param int $accountId
Expand Down
10 changes: 8 additions & 2 deletions lib/Service/OutboxService.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
use OCA\Mail\Db\Recipient;
use OCA\Mail\Events\OutboxMessageCreatedEvent;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Send\Chain;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface;
use Throwable;
Expand Down Expand Up @@ -115,9 +117,13 @@ public function deleteMessage(string $userId, LocalMessage $message): void {
$this->mapper->deleteWithRecipients($message);
}

/**
* @throws Throwable
* @throws Exception
* @throws ServiceException
*/
public function sendMessage(LocalMessage $message, Account $account): LocalMessage {
$this->sendChain->process($account, $message);
return $message;
return $this->sendChain->process($account, $message);
}

/**
Expand Down
Loading

0 comments on commit 048e86d

Please sign in to comment.