Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnna Larch <anna@nextcloud.com>2021-06-23 14:03:52 +0300
committerAnna Larch <anna@nextcloud.com>2021-08-24 13:41:14 +0300
commit2422e15270ab69b1da62f01231018c078738a66d (patch)
tree65bb81dd0153972c48f740dfb72f65e07b179872
parent251d6f0d23a3dcaa41daaf84d12fd10a85095515 (diff)
Create anti spam report feature
Signed-off-by: Anna Larch <anna@nextcloud.com>
-rw-r--r--appinfo/routes.php10
-rw-r--r--lib/AppInfo/Application.php4
-rw-r--r--lib/Controller/SettingsController.php28
-rw-r--r--lib/Listener/HamReportListener.php64
-rw-r--r--lib/Listener/SpamReportListener.php64
-rw-r--r--lib/Model/IMAPMessage.php10
-rw-r--r--lib/Model/IMessage.php10
-rw-r--r--lib/Model/Message.php17
-rw-r--r--lib/Service/AntiSpamService.php123
-rw-r--r--lib/Service/MailTransmission.php29
-rw-r--r--lib/Settings/AdminSettings.php18
-rw-r--r--src/components/settings/AdminSettings.vue27
-rw-r--r--src/components/settings/AntiSpamSettings.vue186
-rw-r--r--src/service/SettingsService.js10
-rw-r--r--tests/Integration/Framework/ImapTestAccount.php1
-rw-r--r--tests/Integration/Service/AntiSpamServiceIntegrationTest.php109
-rw-r--r--tests/Unit/Listener/HamReportListenerTest.php156
-rw-r--r--tests/Unit/Listener/SpamReportListenerTest.php149
-rw-r--r--tests/Unit/Service/AntiSpamServiceTest.php204
-rw-r--r--tests/Unit/Settings/AdminSettingsTest.php17
-rw-r--r--tests/psalm-baseline.xml21
21 files changed, 1246 insertions, 11 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index ce6107c54..6242fe0e2 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -260,6 +260,16 @@ return [
'verb' => 'DELETE'
],
[
+ 'name' => 'settings#setAntiSpamEmail',
+ 'url' => '/api/settings/antispam',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'settings#deleteAntiSpamEmail',
+ 'url' => '/api/settings/antispam',
+ 'verb' => 'DELETE'
+ ],
+ [
'name' => 'trusted_senders#setTrusted',
'url' => '/api/trustedsenders/{email}',
'verb' => 'PUT'
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 65c7e68d2..3228834e5 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -45,6 +45,8 @@ use OCA\Mail\HordeTranslationHandler;
use OCA\Mail\Http\Middleware\ErrorMiddleware;
use OCA\Mail\Http\Middleware\ProvisioningMiddleware;
use OCA\Mail\Listener\AddressCollectionListener;
+use OCA\Mail\Listener\HamReportListener;
+use OCA\Mail\Listener\SpamReportListener;
use OCA\Mail\Listener\DeleteDraftListener;
use OCA\Mail\Listener\FlagRepliedMessageListener;
use OCA\Mail\Listener\InteractionListener;
@@ -101,6 +103,8 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(DraftSavedEvent::class, DeleteDraftListener::class);
$context->registerEventListener(MailboxesSynchronizedEvent::class, MailboxesSynchronizedSpecialMailboxesUpdater::class);
$context->registerEventListener(MessageFlaggedEvent::class, MessageCacheUpdaterListener::class);
+ $context->registerEventListener(MessageFlaggedEvent::class, SpamReportListener::class);
+ $context->registerEventListener(MessageFlaggedEvent::class, HamReportListener::class);
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
$context->registerEventListener(MessageSentEvent::class, DeleteDraftListener::class);
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index dd1a19dc4..d88705f23 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -28,6 +28,7 @@ namespace OCA\Mail\Controller;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Exception\ValidationException;
use OCA\Mail\Http\JsonResponse as HttpJsonResponse;
+use OCA\Mail\Service\AntiSpamService;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
@@ -39,10 +40,15 @@ class SettingsController extends Controller {
/** @var ProvisioningManager */
private $provisioningManager;
+ /** @var AntiSpamService */
+ private $antiSpamService;
+
public function __construct(IRequest $request,
- ProvisioningManager $provisioningManager) {
+ ProvisioningManager $provisioningManager,
+ AntiSpamService $antiSpamService) {
parent::__construct(Application::APP_ID, $request);
$this->provisioningManager = $provisioningManager;
+ $this->antiSpamService = $antiSpamService;
}
public function index(): JSONResponse {
@@ -88,4 +94,24 @@ class SettingsController extends Controller {
return new JSONResponse([]);
}
+
+ /**
+ *
+ * @return JSONResponse
+ */
+ public function setAntiSpamEmail(string $spam, string $ham): JSONResponse {
+ $this->antiSpamService->setSpamEmail($spam);
+ $this->antiSpamService->setHamEmail($ham);
+ return new JSONResponse([]);
+ }
+
+ /**
+ * Store the credentials used for SMTP in the config
+ *
+ * @return JSONResponse
+ */
+ public function deleteAntiSpamEmail(): JSONResponse {
+ $this->antiSpamService->deleteConfig();
+ return new JSONResponse([]);
+ }
}
diff --git a/lib/Listener/HamReportListener.php b/lib/Listener/HamReportListener.php
new file mode 100644
index 000000000..35590a994
--- /dev/null
+++ b/lib/Listener/HamReportListener.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Listener;
+
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Service\AntiSpamService;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+class HamReportListener implements IEventListener {
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var AntiSpamService */
+ private $antiSpamService;
+
+ public function __construct(LoggerInterface $logger,
+ AntiSpamService $antiSpamService) {
+ $this->logger = $logger;
+ $this->antiSpamService = $antiSpamService;
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof MessageFlaggedEvent || $event->getFlag() !== '$notjunk') {
+ return;
+ }
+
+ if (!$event->isSet()) {
+ return;
+ }
+
+ // Send message to reporting service
+ try {
+ $this->antiSpamService->sendReportEmail($event->getAccount(), $event->getMailbox(), $event->getUid(), $event->getFlag());
+ } catch (\Throwable $e) {
+ $this->logger->error("Could not send spam report: " . $e->getMessage(), ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/Listener/SpamReportListener.php b/lib/Listener/SpamReportListener.php
new file mode 100644
index 000000000..5188d3807
--- /dev/null
+++ b/lib/Listener/SpamReportListener.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Listener;
+
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Service\AntiSpamService;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+class SpamReportListener implements IEventListener {
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var AntiSpamService */
+ private $antiSpamService;
+
+ public function __construct(LoggerInterface $logger,
+ AntiSpamService $antiSpamService) {
+ $this->logger = $logger;
+ $this->antiSpamService = $antiSpamService;
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof MessageFlaggedEvent || $event->getFlag() !== '$junk') {
+ return;
+ }
+
+ if (!$event->isSet()) {
+ return;
+ }
+
+ // Send message to reporting service
+ try {
+ $this->antiSpamService->sendReportEmail($event->getAccount(), $event->getMailbox(), $event->getUid(), $event->getFlag());
+ } catch (\Throwable $e) {
+ $this->logger->error("Could not send spam report: " . $e->getMessage(), ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php
index a3d38df18..0b3c38f50 100644
--- a/lib/Model/IMAPMessage.php
+++ b/lib/Model/IMAPMessage.php
@@ -652,6 +652,16 @@ class IMAPMessage implements IMessage, JsonSerializable {
}
/**
+ * @param string $name
+ * @param string $content
+ *
+ * @return void
+ */
+ public function addEmbeddedMessageAttachment(string $name, string $content): void {
+ throw new Exception('IMAP message is immutable');
+ }
+
+ /**
* @param File $file
*
* @return void
diff --git a/lib/Model/IMessage.php b/lib/Model/IMessage.php
index 74aa64916..7ee3108a2 100644
--- a/lib/Model/IMessage.php
+++ b/lib/Model/IMessage.php
@@ -127,11 +127,19 @@ interface IMessage {
/**
* @param string $name
- * @param string $content
+ * @param string $content attached with mime type 'application/octet-stream'
*/
public function addRawAttachment(string $name, string $content): void;
/**
+ * @param string $name
+ * @param string $content attached with mime type 'message/rfc822'
+ *
+ * @return void
+ */
+ public function addEmbeddedMessageAttachment(string $name, string $content): void;
+
+ /**
* @param File $file
*/
public function addAttachmentFromFiles(File $file);
diff --git a/lib/Model/Message.php b/lib/Model/Message.php
index 37d231c4b..5466fbf2f 100644
--- a/lib/Model/Message.php
+++ b/lib/Model/Message.php
@@ -237,6 +237,23 @@ class Message implements IMessage {
}
/**
+ * @param string $name
+ * @param string $content
+ *
+ * @return void
+ */
+ public function addEmbeddedMessageAttachment(string $name, string $content): void {
+ $mime = 'message/rfc822';
+ $part = new Horde_Mime_Part();
+ $part->setCharset('us-ascii');
+ $part->setDisposition('attachment');
+ $part->setName($name);
+ $part->setContents($content);
+ $part->setType($mime);
+ $this->attachments[] = $part;
+ }
+
+ /**
* @param File $file
*
* @return void
diff --git a/lib/Service/AntiSpamService.php b/lib/Service/AntiSpamService.php
new file mode 100644
index 000000000..f9292fe53
--- /dev/null
+++ b/lib/Service/AntiSpamService.php
@@ -0,0 +1,123 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * Mail
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Mail\Service;
+
+use OCA\Mail\Account;
+use OCA\Mail\Contracts\IMailTransmission;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MessageMapper;
+use OCA\Mail\Exception\SentMailboxNotSetException;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Model\NewMessageData;
+use OCP\IConfig;
+
+class AntiSpamService {
+ private const NAME = 'antispam_reporting';
+ private const MESSAGE_TYPE = 'message/rfc822';
+
+ /** @var IConfig */
+ private $config;
+
+ /** @var MessageMapper */
+ private $messageMapper;
+
+ /** @var IMailTransmission */
+ private $transmission;
+
+ public function __construct(IConfig $config,
+ MessageMapper $messageMapper,
+ IMailTransmission $transmission) {
+ $this->config = $config;
+ $this->messageMapper = $messageMapper;
+ $this->transmission = $transmission;
+ }
+
+ public function getSpamEmail(): string {
+ return $this->config->getAppValue('mail', self::NAME . '_spam');
+ }
+
+ public function getHamEmail(): string {
+ return $this->config->getAppValue('mail', self::NAME. '_ham');
+ }
+
+ public function getSpamSubject(): string {
+ return 'Learn as Junk';
+ }
+
+ public function getHamSubject(): string {
+ return 'Learn as Not Junk';
+ }
+
+ public function setSpamEmail(string $email): void {
+ $this->config->setAppValue('mail', self::NAME . '_spam', $email);
+ }
+
+ public function setHamEmail(string $email): void {
+ $this->config->setAppValue('mail', self::NAME. '_ham', $email);
+ }
+
+ public function deleteConfig(): void {
+ $this->config->deleteAppValue('mail', self::NAME . '_spam');
+ $this->config->deleteAppValue('mail', self::NAME . '_ham');
+ }
+
+ /**
+ * @param Account $account
+ * @param Mailbox $mailbox
+ * @param int $uid
+ * @param string $flag
+ * @throws ServiceException
+ */
+ public function sendReportEmail(Account $account, Mailbox $mailbox, int $uid, string $flag): void {
+ $reportEmail = ($flag === '$junk') ? $this->getSpamEmail() : $this->getHamEmail();
+ if ($reportEmail === '') {
+ return;
+ }
+ $subject = ($flag === '$junk') ? $this->getSpamSubject() : $this->getHamSubject();
+
+ // Message to attach not found
+ $messageId = $this->messageMapper->getIdForUid($mailbox, $uid);
+ if ($messageId === null) {
+ throw new ServiceException('Could not find reported message');
+ }
+
+ $messageData = NewMessageData::fromRequest(
+ $account,
+ $reportEmail,
+ null,
+ null,
+ $subject,
+ $subject, // add any message body - not all IMAP servers accept empty emails
+ [['id' => $messageId, 'type' => self::MESSAGE_TYPE]]
+ );
+
+ try {
+ $this->transmission->sendMessage($messageData);
+ } catch (SentMailboxNotSetException | ServiceException $e) {
+ throw new ServiceException('Could not send report email from anti spam email service', 0, $e);
+ }
+ }
+}
diff --git a/lib/Service/MailTransmission.php b/lib/Service/MailTransmission.php
index ea31e5e2e..154cff9a0 100644
--- a/lib/Service/MailTransmission.php
+++ b/lib/Service/MailTransmission.php
@@ -327,6 +327,9 @@ class MailTransmission implements IMailTransmission {
} elseif (isset($attachment['type']) && $attachment['type'] === 'message') {
// Adds another message as attachment
$this->handleForwardedMessageAttachment($account, $attachment, $message);
+ } elseif (isset($attachment['type']) && $attachment['type'] === 'message/rfc822') {
+ // Adds another message as attachment with mime type 'message/rfc822
+ $this->handleEmbeddedMessageAttachments($account, $attachment, $message);
} elseif (isset($attachment['type']) && $attachment['type'] === 'message-attachment') {
// Adds an attachment from another email (use case is, eg., a mail forward)
$this->handleForwardedAttachment($account, $attachment, $message);
@@ -394,6 +397,32 @@ class MailTransmission implements IMailTransmission {
* @param mixed[] $attachment
* @param IMessage $message
*/
+ private function handleEmbeddedMessageAttachments(Account $account, array $attachment, IMessage $message): void {
+ // Gets original of other message
+ $userId = $account->getMailAccount()->getUserId();
+ $attachmentMessage = $this->mailManager->getMessage($userId, (int)$attachment['id']);
+ $mailbox = $this->mailManager->getMailbox($userId, $attachmentMessage->getMailboxId());
+
+ $fullText = $this->messageMapper->getFullText(
+ $this->imapClientFactory->getClient($account),
+ $mailbox->getName(),
+ $attachmentMessage->getUid()
+ );
+
+ $message->addEmbeddedMessageAttachment(
+ $attachment['displayName'] ?? $attachmentMessage->getSubject() . '.eml',
+ $fullText
+ );
+ }
+
+
+ /**
+ * Adds an attachment that's coming from another message's attachment (typical use case: email forwarding)
+ *
+ * @param Account $account
+ * @param mixed[] $attachment
+ * @param IMessage $message
+ */
private function handleForwardedAttachment(Account $account, array $attachment, IMessage $message): void {
// Gets attachment from other message
$userId = $account->getMailAccount()->getUserId();
diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php
index 428201244..2bb715200 100644
--- a/lib/Settings/AdminSettings.php
+++ b/lib/Settings/AdminSettings.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Mail\Settings;
use OCA\Mail\AppInfo\Application;
+use OCA\Mail\Service\AntiSpamService;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IInitialStateService;
@@ -40,10 +41,15 @@ class AdminSettings implements ISettings {
/** @var ProvisioningManager */
private $provisioningManager;
+ /** @var AntiSpamService */
+ private $antiSpamService;
+
public function __construct(IInitialStateService $initialStateService,
- ProvisioningManager $provisioningManager) {
+ ProvisioningManager $provisioningManager,
+ AntiSpamService $antiSpamService) {
$this->initialStateService = $initialStateService;
$this->provisioningManager = $provisioningManager;
+ $this->antiSpamService = $antiSpamService;
}
public function getForm() {
@@ -53,6 +59,16 @@ class AdminSettings implements ISettings {
$this->provisioningManager->getConfigs()
);
+ $this->initialStateService->provideInitialState(
+ Application::APP_ID,
+ 'antispam_setting',
+ [
+ 'spam' => $this->antiSpamService->getSpamEmail(),
+ 'ham' => $this->antiSpamService->getHamEmail(),
+ ]
+ );
+
+
$this->initialStateService->provideLazyInitialState(
Application::APP_ID,
'ldap_aliases_integration',
diff --git a/src/components/settings/AdminSettings.vue b/src/components/settings/AdminSettings.vue
index 3f3076b70..6657d8b57 100644
--- a/src/components/settings/AdminSettings.vue
+++ b/src/components/settings/AdminSettings.vue
@@ -31,7 +31,7 @@
)
}}
</p>
- <div class="ap-description">
+ <div class="app-description">
<h3>Account provisioning</h3>
<article>
<p>
@@ -111,6 +111,27 @@
:submit="saveSettings"
:disable="deleteProvisioning" />
</p>
+ <div class="app-description">
+ <h3>Anti Spam Service</h3>
+ <article>
+ <p>
+ {{
+ t(
+ 'mail',
+ 'You can set up an anti spam service email address here.'
+ )
+ }}
+ <br>
+ {{
+ t(
+ 'mail',
+ 'Any email that is marked as spam will not only be flagged as such but additionally sent to the anti spam service.'
+ )
+ }}
+ </p>
+ </article>
+ <AntiSpamSettings />
+ </div>
</SettingsSection>
</template>
@@ -118,6 +139,7 @@
import logger from '../../logger'
import { showError, showSuccess } from '@nextcloud/dialogs'
import ProvisioningSettings from './ProvisioningSettings'
+import AntiSpamSettings from './AntiSpamSettings'
import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection'
import {
disableProvisioning,
@@ -129,6 +151,7 @@ import {
export default {
name: 'AdminSettings',
components: {
+ AntiSpamSettings,
ProvisioningSettings,
SettingsSection,
},
@@ -218,7 +241,7 @@ export default {
}
</script>
<style lang="scss" scoped>
- .ap-description {
+ .app-description {
margin-bottom: 24px;
}
.config-button {
diff --git a/src/components/settings/AntiSpamSettings.vue b/src/components/settings/AntiSpamSettings.vue
new file mode 100644
index 000000000..ca323479a
--- /dev/null
+++ b/src/components/settings/AntiSpamSettings.vue
@@ -0,0 +1,186 @@
+<!--
+ - @copyright 2021 Anna Larch <anna@nextcloud.com>
+ -
+ - @author Anna Larch <anna@nextcloud.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<template>
+ <div class="section">
+ <h4>{{ t('mail','Anti Spam') }}</h4>
+ <p>
+ {{ t('mail', 'Add the email address of your anti spam report service here.') }}
+ </p>
+ <p>
+ {{ t('mail', 'When using this setting, a report email will be sent to the SPAM report server when a user clicks "Mark as spam".') }}
+ {{ t('mail', 'The original message will be attached as a "message/rfc822" attachment.') }}
+ </p>
+ <div class="form-preview-row">
+ <form id="antispam-form" @submit.prevent.stop="submitForm">
+ <div class="settings-group">
+ <div class="group-inputs">
+ <label for="mail-antispam-email-spam"> {{ t('mail', '"Mark as Spam" Email Address') }}* </label>
+ <br>
+ <input
+ id="mail-antispam-email-spam"
+ v-model="email.spam"
+ :disabled="loading"
+ name="spam"
+ type="email">
+ <br>
+ <label for="mail-antispam-email-ham"> {{ t('mail', '"Mark Not Junk" Email Address') }}* </label>
+ <br>
+ <input
+ id="mail-antispam-email-ham"
+ v-model="email.ham"
+ :disabled="loading"
+ name="ham"
+ type="email">
+ <br>
+ <input
+ :disabled="loading"
+ :value="t('mail', 'Save')"
+ class="button config-button icon-upload"
+ type="submit">
+ <input
+ :disabled="loading"
+ :value="t('mail', 'Reset')"
+ class="button config-button icon-delete"
+ type="button"
+ @click="resetForm()">
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</template>
+<script>
+import logger from '../../logger'
+import { loadState } from '@nextcloud/initial-state'
+import { setAntiSpamEmail, deleteAntiSpamEmail } from '../../service/SettingsService'
+import { showError, showSuccess } from '@nextcloud/dialogs'
+
+const email = loadState('mail', 'antispam_setting', '[]')
+
+export default {
+ name: 'AntiSpamSettings',
+ data() {
+ return {
+ email,
+ loading: false,
+ }
+ },
+ methods: {
+ async submitForm() {
+ this.loading = true
+
+ try {
+ await setAntiSpamEmail(email)
+ logger.info('anti spam email updated')
+ showSuccess(t('mail', 'Successfully set up anti spam email addresses'))
+ } catch (error) {
+ logger.error('Could not save email setting', { error })
+ showError(t('mail', 'Error saving anti spam email addresses'))
+ } finally {
+ this.loading = false
+ }
+ },
+ async resetForm() {
+ this.loading = true
+ try {
+ await deleteAntiSpamEmail()
+ logger.info('anti spam email deleted')
+ showSuccess(t('mail', 'Successfully deleted anti spam reporting email'))
+ } catch (error) {
+ logger.error('Could not delete email setting', { error })
+ showError(t('mail', 'Error deleting anti spam reporting email'))
+
+ } finally {
+ this.loading = false
+ this.email = []
+ }
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.form-preview-row {
+ display: flex;
+
+ div:last-child {
+ margin-top: 10px;
+ }
+}
+
+.settings-group {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+
+ .group-title {
+ min-width: 100px;
+ text-align: right;
+ margin: 10px;
+ font-weight: bold;
+ }
+ .group-inputs {
+ margin: 10px;
+ flex-grow: 1;
+
+ input[type='text'] {
+ min-width: 200px;
+ }
+ .config-button {
+ line-height: 24px;
+ padding-left: 48px;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ background-position: 24px;
+ }
+ }
+}
+
+h4 {
+ font-weight: bold;
+}
+
+.previews {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ margin: 0 -10px;
+
+ .preview-item {
+ flex-grow: 1;
+ margin: 10px;
+ padding: 25px;
+ }
+}
+input[type='radio'] {
+ display: none;
+}
+
+.flex-row {
+ display: flex;
+}
+form {
+ label {
+ color: var(--color-text-maxcontrast);
+ }
+}
+</style>
diff --git a/src/service/SettingsService.js b/src/service/SettingsService.js
index 73cfd3f42..7a6509dcb 100644
--- a/src/service/SettingsService.js
+++ b/src/service/SettingsService.js
@@ -58,3 +58,13 @@ export const disableProvisioning = (id) => {
})
return axios.delete(url).then((resp) => resp.data)
}
+
+export const setAntiSpamEmail = (email) => {
+ return axios.post(generateUrl('/apps/mail/api/settings/antispam'), { spam: email.spam, ham: email.ham })
+ .then((resp) => resp.data)
+}
+
+export const deleteAntiSpamEmail = () => {
+ return axios.delete(generateUrl('/apps/mail/api/settings/antispam'))
+ .then((resp) => resp.data)
+}
diff --git a/tests/Integration/Framework/ImapTestAccount.php b/tests/Integration/Framework/ImapTestAccount.php
index 846f332fd..0dac89327 100644
--- a/tests/Integration/Framework/ImapTestAccount.php
+++ b/tests/Integration/Framework/ImapTestAccount.php
@@ -48,6 +48,7 @@ trait ImapTestAccount {
$mailAccount = new MailAccount();
$mailAccount->setUserId($this->getTestAccountUserId());
+ $mailAccount->setName('Tester');
$mailAccount->setEmail('user@domain.tld');
$mailAccount->setInboundHost('127.0.0.1');
$mailAccount->setInboundPort(993);
diff --git a/tests/Integration/Service/AntiSpamServiceIntegrationTest.php b/tests/Integration/Service/AntiSpamServiceIntegrationTest.php
new file mode 100644
index 000000000..0f5d14a93
--- /dev/null
+++ b/tests/Integration/Service/AntiSpamServiceIntegrationTest.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Mail\Tests\Integration\Service;
+
+use Horde_Imap_Client;
+use OC;
+use OCA\Mail\Account;
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Contracts\IMailManager;
+use OCA\Mail\Db\MessageMapper;
+use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper;
+use OCA\Mail\Service\AntiSpamService;
+use OCA\Mail\Service\Sync\SyncService;
+use OCA\Mail\Tests\Integration\Framework\ImapTest;
+use OCA\Mail\Tests\Integration\Framework\ImapTestAccount;
+
+class AntiSpamServiceIntegrationTest extends TestCase {
+ use ImapTest,
+ ImapTestAccount;
+
+ /** @var AntiSpamService */
+ private $service;
+
+ public function setUp():void {
+ parent::setUp();
+ $this->service = OC::$server->get(AntiSpamService::class);
+ $this->service->setSpamEmail('spam@domain.tld');
+ $this->service->setHamEmail('notspam@domain.tld');
+ }
+
+ public function tearDown(): void {
+ $this->resetImapAccount();
+ $this->service->deleteConfig();
+ }
+
+ public function testFlagJunkWithSpamReportActive(): void {
+ // First, set up account and retrieve sync token
+ $this->resetImapAccount();
+ $account = $this->createTestAccount();
+
+ /** @var SyncService $syncService */
+ $syncService = OC::$server->get(SyncService::class);
+ /** @var ImapMessageMapper $imapMessageMapper */
+ $imapMessageMapper = OC::$server->get(ImapMessageMapper::class);
+ /** @var MessageMapper $messageMapper */
+ $messageMapper = OC::$server->get(MessageMapper::class);
+ /** @var IMailManager $mailManager */
+ $mailManager = OC::$server->get(IMailManager::class);
+ $mailBoxes = $mailManager->getMailboxes(new Account($account));
+ $inbox = null;
+ foreach ($mailBoxes as $mailBox) {
+ if ($mailBox->getName() === 'INBOX') {
+ $inbox = $mailBox;
+ break;
+ }
+ }
+
+ // Second, put a new message into the mailbox
+ $message = $this->getMessageBuilder()
+ ->from('buffington@domain.tld')
+ ->to('user@domain.tld')
+ ->finish();
+ $newUid = $this->saveMessage($inbox->getName(), $message, $account);
+
+ // sync in between creating and flagging otherwise it can't be found
+ $syncService->syncMailbox(
+ new Account($account),
+ $inbox,
+ Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS,
+ null,
+ false
+ );
+
+ // now we flag this message as junk
+ $mailManager->flagMessage(new Account($account), $inbox->getName(), $newUid, 'junk', true);
+
+ // if everything runs through, we can assert the run has been fine,
+ // but we can't really test if Listener and Transmission have actually sent the message
+ $this->addToAssertionCount(1);
+
+ // now we flag this message as not junk
+ $mailManager->flagMessage(new Account($account), $inbox->getName(), $newUid, 'notjunk', true);
+
+ // same as before
+ $this->addToAssertionCount(1);
+ }
+}
diff --git a/tests/Unit/Listener/HamReportListenerTest.php b/tests/Unit/Listener/HamReportListenerTest.php
new file mode 100644
index 000000000..71c70f4c6
--- /dev/null
+++ b/tests/Unit/Listener/HamReportListenerTest.php
@@ -0,0 +1,156 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Tests\Unit\Listener;
+
+use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Account;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\Tag;
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Listener\HamReportListener;
+use OCA\Mail\Listener\SpamReportListener;
+use OCP\EventDispatcher\Event;
+
+class HamReportListenerTest extends TestCase {
+
+ /** @var ServiceMockObject */
+ private $serviceMock;
+
+ /** @var SpamReportListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->serviceMock = $this->createServiceMock(HamReportListener::class);
+ $this->listener = $this->serviceMock->getService();
+ }
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('sendReportEmail');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleNotFlaggedJunk(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ Tag::LABEL_IMPORTANT,
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('sendReportEmail');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleNotUnflaggingNonjunk(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$notjunk',
+ false
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('getHamEmail');
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('sendReportEmail');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleJunkExceptionOnSend() : void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$notjunk',
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::once())
+ ->method('sendReportEmail')
+ ->with($account, $mailbox, 123, '$notjunk')
+ ->willThrowException(new ServiceException());
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::once())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandle(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$notjunk',
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::once())
+ ->method('sendReportEmail')
+ ->with($account, $mailbox, 123, '$notjunk');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/Unit/Listener/SpamReportListenerTest.php b/tests/Unit/Listener/SpamReportListenerTest.php
new file mode 100644
index 000000000..d7068990b
--- /dev/null
+++ b/tests/Unit/Listener/SpamReportListenerTest.php
@@ -0,0 +1,149 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Mail\Tests\Unit\Listener;
+
+use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Account;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\Tag;
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Listener\SpamReportListener;
+use OCP\EventDispatcher\Event;
+
+class SpamReportListenerTest extends TestCase {
+
+ /** @var ServiceMockObject */
+ private $serviceMock;
+
+ /** @var SpamReportListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->serviceMock = $this->createServiceMock(SpamReportListener::class);
+ $this->listener = $this->serviceMock->getService();
+ }
+
+ public function testHandleUnrelated(): void {
+ $event = new Event();
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('sendReportEmail');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleNotFlaggedJunk(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ Tag::LABEL_IMPORTANT,
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::never())
+ ->method('sendReportEmail');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleNotUnflaggingJunk(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$junk',
+ false
+ );
+
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandleJunkExceptionOnSend(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$junk',
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::once())
+ ->method('sendReportEmail')
+ ->with($account, $mailbox, 123, '$junk')
+ ->willThrowException(new ServiceException());
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::once())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+
+ public function testHandle(): void {
+ $account = $this->createMock(Account::class);
+ $mailbox = $this->createMock(Mailbox::class);
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 123,
+ '$junk',
+ true
+ );
+
+ $this->serviceMock->getParameter('antiSpamService')
+ ->expects(self::once())
+ ->method('sendReportEmail')
+ ->with($account, $mailbox, 123, '$junk');
+ $this->serviceMock->getParameter('logger')
+ ->expects(self::never())
+ ->method('error');
+
+ $this->listener->handle($event);
+ }
+}
diff --git a/tests/Unit/Service/AntiSpamServiceTest.php b/tests/Unit/Service/AntiSpamServiceTest.php
new file mode 100644
index 000000000..d8da88b42
--- /dev/null
+++ b/tests/Unit/Service/AntiSpamServiceTest.php
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * @copyright 2021 Anna Larch <anna@nextcloud.com>
+ *
+ * @author Anna Larch <anna@nextcloud.com>
+ *
+ * Mail
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Mail\Tests\Unit\Service;
+
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Account;
+use OCA\Mail\Contracts\IMailTransmission;
+use OCA\Mail\Db\MessageMapper;
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Model\NewMessageData;
+use OCA\Mail\Service\AntiSpamService;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class AntiSpamServiceTest extends TestCase {
+
+ /** @var AntiSpamService */
+ private $service;
+
+ /** @var IConfig|MockObject */
+ private $config;
+
+ /** @var MessageMapper|MockObject */
+ private $messageMapper;
+
+ /** @var IMailTransmission|MockObject */
+ private $transmission;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->messageMapper = $this->createMock(MessageMapper::class);
+ $this->transmission = $this->createMock(IMailTransmission::class);
+
+ $this->service = new AntiSpamService(
+ $this->config,
+ $this->messageMapper,
+ $this->transmission
+ );
+ }
+
+ public function testSendReportEmailNoEmailFound(): void {
+ $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [
+ 'getAccount' => $this->createMock(Account::class),
+ 'getMailbox' => $this->createMock(Mailbox::class),
+ 'getFlag' => '$junk'
+ ]);
+
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('mail', 'antispam_reporting_spam')
+ ->willReturn('');
+ $this->messageMapper->expects(self::never())
+ ->method('getIdForUid');
+ $this->transmission->expects(self::never())
+ ->method('sendMessage');
+
+ $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag());
+ }
+
+ public function testSendReportEmailNoMessageFound(): void {
+ $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [
+ 'getAccount' => $this->createMock(Account::class),
+ 'getMailbox' => $this->createMock(Mailbox::class),
+ 'getFlag' => '$junk'
+ ]);
+
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('mail', 'antispam_reporting_spam')
+ ->willReturn('test@test.com');
+ $this->messageMapper->expects(self::once())
+ ->method('getIdForUid')
+ ->with($event->getMailbox(), 123)
+ ->willReturn(null);
+ $this->expectException(ServiceException::class);
+ $this->transmission->expects(self::never())
+ ->method('sendMessage');
+
+ $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag());
+ }
+
+ public function testSendReportEmailTransmissionError(): void {
+ $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [
+ 'getAccount' => $this->createMock(Account::class),
+ 'getMailbox' => $this->createMock(Mailbox::class),
+ 'getFlag' => '$junk'
+ ]);
+
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('mail', 'antispam_reporting_spam')
+ ->willReturn('test@test.com');
+ $messageData = NewMessageData::fromRequest(
+ $event->getAccount(),
+ 'test@test.com',
+ null,
+ null,
+ 'Learn as Junk',
+ 'Learn as Junk',
+ [['id' => 123, 'type' => 'message/rfc822']]
+ );
+
+ $this->messageMapper->expects(self::once())
+ ->method('getIdForUid')
+ ->with($event->getMailbox(), 123)
+ ->willReturn(123);
+ $this->transmission->expects(self::once())
+ ->method('sendMessage')
+ ->with($messageData)
+ ->willThrowException(new ServiceException());
+ $this->expectException(ServiceException::class);
+
+ $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag());
+ }
+
+ public function testSendReportEmail(): void {
+ $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [
+ 'getAccount' => $this->createMock(Account::class),
+ 'getMailbox' => $this->createMock(Mailbox::class),
+ 'getFlag' => '$junk'
+ ]);
+
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('mail', 'antispam_reporting_spam')
+ ->willReturn('test@test.com');
+ $messageData = NewMessageData::fromRequest(
+ $event->getAccount(),
+ 'test@test.com',
+ null,
+ null,
+ 'Learn as Junk',
+ 'Learn as Junk',
+ [['id' => 123, 'type' => 'message/rfc822']]
+ );
+
+ $this->messageMapper->expects(self::once())
+ ->method('getIdForUid')
+ ->with($event->getMailbox(), 123)
+ ->willReturn(123);
+ $this->transmission->expects(self::once())
+ ->method('sendMessage')
+ ->with($messageData);
+
+ $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag());
+ }
+
+ public function testSendReportEmailForHam(): void {
+ $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [
+ 'getAccount' => $this->createMock(Account::class),
+ 'getMailbox' => $this->createMock(Mailbox::class),
+ 'getFlag' => '$notjunk'
+ ]);
+
+ $this->config->expects(self::once())
+ ->method('getAppValue')
+ ->with('mail', 'antispam_reporting_ham')
+ ->willReturn('test@test.com');
+ $messageData = NewMessageData::fromRequest(
+ $event->getAccount(),
+ 'test@test.com',
+ null,
+ null,
+ 'Learn as Not Junk',
+ 'Learn as Not Junk',
+ [['id' => 123, 'type' => 'message/rfc822']]
+ );
+
+ $this->messageMapper->expects(self::once())
+ ->method('getIdForUid')
+ ->with($event->getMailbox(), 123)
+ ->willReturn(123);
+ $this->transmission->expects(self::once())
+ ->method('sendMessage')
+ ->with($messageData);
+
+ $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag());
+ }
+}
diff --git a/tests/Unit/Settings/AdminSettingsTest.php b/tests/Unit/Settings/AdminSettingsTest.php
index 7cdfca83a..a2eea685d 100644
--- a/tests/Unit/Settings/AdminSettingsTest.php
+++ b/tests/Unit/Settings/AdminSettingsTest.php
@@ -54,12 +54,19 @@ class AdminSettingsTest extends TestCase {
}
public function testGetForm() {
- $this->serviceMock->getParameter('initialStateService')->expects($this->once())
+ $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(2))
->method('provideInitialState')
- ->with(
- Application::APP_ID,
- 'provisioning_settings',
- $this->anything()
+ ->withConsecutive(
+ [
+ Application::APP_ID,
+ 'provisioning_settings',
+ $this->anything()
+ ],
+ [
+ Application::APP_ID,
+ 'antispam_setting',
+ $this->anything()
+ ]
);
$expected = new TemplateResponse(Application::APP_ID, 'settings-admin');
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
index a8707bdf4..19d2f3e35 100644
--- a/tests/psalm-baseline.xml
+++ b/tests/psalm-baseline.xml
@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.x-dev@">
<file src="lib/AppInfo/Application.php">
- <MissingDependency occurrences="12">
+ <MissingDependency occurrences="14">
<code>DraftSavedEvent</code>
<code>MailboxesSynchronizedEvent</code>
<code>MessageDeletedEvent</code>
<code>MessageFlaggedEvent</code>
+ <code>MessageFlaggedEvent</code>
+ <code>MessageFlaggedEvent</code>
<code>MessageSentEvent</code>
<code>MessageSentEvent</code>
<code>MessageSentEvent</code>
@@ -228,6 +230,12 @@
<code>MessageSentEvent</code>
</MissingDependency>
</file>
+ <file src="lib/Listener/HamReportListener.php">
+ <MissingDependency occurrences="2">
+ <code>HamReportListener</code>
+ <code>MessageFlaggedEvent</code>
+ </MissingDependency>
+ </file>
<file src="lib/Listener/InteractionListener.php">
<MissingDependency occurrences="2">
<code>InteractionListener</code>
@@ -258,12 +266,23 @@
<code>SaveSentMessageListener</code>
</MissingDependency>
</file>
+ <file src="lib/Listener/SpamReportListener.php">
+ <MissingDependency occurrences="2">
+ <code>MessageFlaggedEvent</code>
+ <code>SpamReportListener</code>
+ </MissingDependency>
+ </file>
<file src="lib/Listener/UserDeletedListener.php">
<MissingDependency occurrences="2">
<code>UserDeletedEvent</code>
<code>UserDeletedListener</code>
</MissingDependency>
</file>
+ <file src="lib/Service/AntiSpamService.php">
+ <MissingDependency occurrences="1">
+ <code>MessageFlaggedEvent</code>
+ </MissingDependency>
+ </file>
<file src="lib/Service/Classification/ImportanceClassifier.php">
<InvalidScalarArgument occurrences="1">
<code>$predictedValidationLabel</code>